页面实现

hall.html

<!DOCTYPE html>
<html lang="ch">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>游戏大厅</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/hall.css">
</head>
<body><div class="nav">五子棋匹配大厅</div><div class="container"><div class="dialog"><!-- 展示用户信息 --><div id="screen"></div><!-- 开始匹配 --><button id="match" onclick="findMatch()">开始匹配</button></div></div><script src="js/jquery.min.js"></script></body>
</html>

 hall.html

.container {height: calc(100% - 50px);display: flex;justify-content: center;align-items: center;
}.container .dialog {height: 350px;width: 299px;background-color: white;border-radius: 20px;padding-top: 30px;display: flex;justify-content: center;/* align-items: center; */flex-wrap: wrap
}.dialog *{display: flex;justify-content: center;align-items: center;
}.dialog #screen {width: 250px;height: 150px;background-color: wheat;border-radius: 10px;
}.dialog #match {width: 150px;height: 40px;background-color: rgb(255, 159, 33);border-radius: 10px;
}.dialog #match:active {background-color: rgb(204, 128, 21);
}

获取用户信息接口

当用户进入 游戏大厅时,就应该获取到登录用户的信息显示到页面上,我们使用js代码从访问后端接口获取信息

  <script src="js/jquery.min.js"></script><script> $.ajax({url:"/user/getUserInfo",type:"get",success: function(result) {if(result.username != null) {let screen = document.querySelector("#screen");screen.innerHTML = '当前玩家:' + result.username + '<br>天梯积分:' + result.score + '<br>比赛场次:' + result.totalCount + '<br>获胜场次:' + result.winCount;}else{alert("获取用户信息失败,请重新登录");location.href = "/login.html";}},error: function() {alert("获取用户信息失败");}})</script>

WebSocket前端代码

当用户点击匹配按钮时,需要告知服务器该用户要进行匹配,服务器如果接收到则立即回复表示正在匹配,当匹配成功服务器则又需要发送匹配信息给客户端。这里涉及到服务器主动给客户端发送消息的场景,所以我们使用websocket实现

 初始化websocket
   var webSocket= new WebSocket("ws://localhost:8080/game"); webSocket.onopen = function() {console.log("连接成功");}webSocket.onclose = function() {console.log("连接关闭");}webSocket.onerror = function() {console.log("error");}//页面关闭时释放webSocketwindow.onbeforeunload = function() {webSocket.close();}//处理服务器发送的消息webSocket.onmessage = function(e) {}
实现findMatch()方法

点击开始匹配按钮后就会执行findMatch方法,进入匹配状态,此时我们可以把开始匹配按钮替换成取消匹配按钮,再次点击则会向服务器发送取消匹配请求

 function findMatch() {//检查websocket连接if(webSocket.readyState == webSocket.OPEN) {if($("#match").text() == '开始匹配') {console.log("开始匹配");webSocket.send(JSON.stringify({message: 'startMatch' //约定startMatch表示开始匹配}));}else if($("#match").text() == '匹配中...') {console.log("停止匹配");webSocket.send(JSON.stringify({message: 'stopMatch' //约定stopMatch表示停止匹配}));}}else{alert("连接断开,请重新登录");location.href = "/login.html";}}
实现onmessage

我们约定服务器返回的响应为包含以下三个字段的json:

ok: true/false,  //表示请求成功还是失败
errMsg: "错误信息",  //请求失败返回错误信息
message: 'startMatch' 开始匹配 / 'stopMatch' 停止匹配/ 'success' 匹配成功 / 'no_login' 用户未登录 / ’repeat_login'该账号重复登录

 webSocket.onmessage = function(e) {//解析json字符串为js对象let resp = JSON.parse(e.data);if(resp.message == 'startMatch') {//开始匹配请求发送成功正在匹配//替换按钮描述$("#match").text("匹配中...");}else if(resp.message == 'stopMatch') {//取消匹配请求发送成功已取消匹配//替换按钮描述$("#match").text("开始匹配");}else if(resp.message == 'success'){//匹配成功console.log("匹配成功! 进入游戏房间");location.assign("/room.html");console.log("进入游戏房间");}else if(resp.message == 'repeat_login') {alert("该账号已在别处登录");location.href = "/login.html";}else if(resp.message == 'no_login') {alert("当前还未登录");location.href = "/login.html";}else {alert("非法响应 errMsg:" + resp.errMsg);}}

WebSocket后端代码

注册websocket  

创建TextWebSocketHandler子类,重写如下方法: 

package org.ting.j20250110_gobang.websocket;import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;@Component
public class MatchWebSocket extends TextWebSocketHandler {//连接成功后执行@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {super.afterConnectionEstablished(session);}//接收到请求后执行@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {super.handleTextMessage(session, message);}//连接异常时执行@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {super.handleTransportError(session, exception);}//连接正常断开后执行@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {super.afterConnectionClosed(session, status);}
}

注册socket:

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {@Autowiredprivate TextWebSocketHandler textWebSocketHandler;@Autowiredprivate MatchWebSocket matchWebSocket;@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(textWebSocketHandler, "/test");registry.addHandler(matchWebSocket, "/findMatch") //注意路径和前端对应//添加拦截器获取到session,方便获取session中的用户信息.addInterceptors(new HttpSessionHandshakeInterceptor());}
}

维护在线用户

在用户登录成功后,我们可以维护好用户的websocket会话,把用户表示为在线状态,方便获取到用户的websocket会话

@Component
public class OnlineUserManager {//使用ConcurrentHashMap保证线程安全private Map<Integer, WebSocketSession> onlineUser = new ConcurrentHashMap<>();public void enterGameHall(int userId, WebSocketSession session) {//用户上线onlineUser.put(userId, session);}public void exitGameHall(int userId) {//用户下线onlineUser.remove(userId);}public WebSocketSession getFromHall(int userId) {//获取用户的websocket会话return onlineUser.get(userId);}
}

实现webSocket相关方法

上期我们定义了webSocket的处理类,但是并没有完成重写的方法,接下来我们借助维护的在线用户具体实现如下方法

在实现这些方法之前,我们还需要按照上期约定好的信息交互形式定义两个实体类,代表请求和响应:

public class MatchRequest {private String message;public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}
}
@Data
public class MatchResponse {private boolean ok;private String errMsg;private String message;}
连接成功
  public void afterConnectionEstablished(WebSocketSession session) throws Exception {try {User user = (User) session.getAttributes().get("user");if (onlineUserManager.getFromHall(user.getUserId()) != null) {MatchResponse response = new MatchResponse();response.setOk(false);response.setErrMsg("已经在别处登录");response.setMessage("repeat_login");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));// 此处直接关闭有些太激进了, 还是返回一个特殊的 message , 供客户端来进行判定, 由客户端负责进行处理// session.close();return;} else {onlineUserManager.enterGameHall(user.getUserId(), session);System.out.println("用户:" + user.getUsername() + " 已上线");}}catch (NullPointerException e){System.out.println("[MatchAPI.afterConnectionEstablished] 当前用户未登录!");// e.printStackTrace();// 出现空指针异常, 说明当前用户的身份信息是空, 用户未登录呢.// 把当前用户尚未登录这个信息给返回回去~~MatchResponse response = new MatchResponse();response.setOk(false);response.setErrMsg("您尚未登录! 不能进行后续匹配功能!");response.setMessage("no_login");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));}}
连接断开
 public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {try {User user = (User)session.getAttributes().get("user");//防止重复登录时删除正常登录的在线信息if(onlineUserManager.getFromHall(user.getUserId()).equals(session)) {onlineUserManager.exitGameHall(user.getUserId());System.out.println("用户:" + user.getUsername() + " 已下线");}}catch (NullPointerException e) {System.out.println("[MatchAPI.handleTransportError] 当前用户未登录!");MatchResponse response = new MatchResponse();response.setOk(false);response.setErrMsg("用户未登录");response.setMessage("no_login");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));}}//连接正常断开后执行@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {try {User user = (User)session.getAttributes().get("user");//防止重复登录时删除正常登录的在线信息if(onlineUserManager.getFromHall(user.getUserId()).equals(session)) {onlineUserManager.exitGameHall(user.getUserId());System.out.println("用户:" + user.getUsername() + " 已下线");}}catch (NullPointerException e) {System.out.println("[MatchAPI.afterConnectionClosed] 当前用户未登录!");MatchResponse response = new MatchResponse();response.setOk(false);response.setErrMsg("用户未登录");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));}}
}
定义Mather类
@Component
public class Matcher {// 创建三个匹配队列private Queue<User> normalQueue = new LinkedList<>();private Queue<User> highQueue = new LinkedList<>();private Queue<User> veryHighQueue = new LinkedList<>();@Autowiredprivate OnlineUserManager onlineUserManager;private ObjectMapper objectMapper = new ObjectMapper();// 操作匹配队列的方法.// 把玩家放到匹配队列中public void add(User user) {if (user.getScore() < 2000) {synchronized (normalQueue) {normalQueue.offer(user);normalQueue.notify();}System.out.println("把玩家 " + user.getUsername() + " 加入到了 normalQueue 中!");} else if (user.getScore() >= 2000 && user.getScore() < 3000) {synchronized (highQueue) {highQueue.offer(user);highQueue.notify();}System.out.println("把玩家 " + user.getUsername() + " 加入到了 highQueue 中!");} else {synchronized (veryHighQueue) {veryHighQueue.offer(user);veryHighQueue.notify();}System.out.println("把玩家 " + user.getUsername() + " 加入到了 veryHighQueue 中!");}}// 当玩家点击停止匹配的时候, 就需要把玩家从匹配队列中删除public void remove(User user) {if (user.getScore() < 2000) {synchronized (normalQueue) {normalQueue.remove(user);}System.out.println("把玩家 " + user.getUsername() + " 移除了 normalQueue!");} else if (user.getScore() >= 2000 && user.getScore() < 3000) {synchronized (highQueue) {highQueue.remove(user);}System.out.println("把玩家 " + user.getUsername() + " 移除了 highQueue!");} else {synchronized (veryHighQueue) {veryHighQueue.remove(user);}System.out.println("把玩家 " + user.getUsername() + " 移除了 veryHighQueue!");}}}
处理匹配请求
@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {User user = (User) session.getAttributes().get("user");// 获取到客户端给服务器发送的数据String payload = message.getPayload();// 当前这个数据载荷是一个 JSON 格式的字符串, 就需要把它转成 Java 对象. MatchRequestMatchRequest request = objectMapper.readValue(payload, MatchRequest.class);MatchResponse response = new MatchResponse();if (request.getMessage().equals("startMatch")) {// 进入匹配队列matcher.add(user);// 把玩家信息放入匹配队列之后, 就可以返回一个响应给客户端了.response.setOk(true);response.setMessage("startMatch");} else if (request.getMessage().equals("stopMatch")) {// 退出匹配队列matcher.remove(user);// 移除之后, 就可以返回一个响应给客户端了.response.setOk(true);response.setMessage("stopMatch");} else {response.setOk(false);response.setErrMsg("非法的匹配请求");}String jsonString = objectMapper.writeValueAsString(response);session.sendMessage(new TextMessage(jsonString));}
 游戏房间实体类
package com.example.demo;import com.example.demo.dao.User;import java.util.UUID;
//每个房间都是不通的,所以不能给spring管理
public class Room {private String roomId;private User user1;private User user2;// 先手方的玩家 idprivate int whiteUser;public int getWhiteUser() {return whiteUser;}public void setWhiteUser(int whiteUser) {this.whiteUser = whiteUser;}public Room() {roomId = UUID.randomUUID().toString();}public String getRoomId() {return roomId;}public void setRoomId(String roomId) {this.roomId = roomId;}public User getUser1() {return user1;}public void setUser1(User user1) {this.user1 = user1;}public User getUser2() {return user2;}public void setUser2(User user2) {this.user2 = user2;}
}
实现匹配功能
创建线程扫描队列

我们为每个匹配队列创建一个线程,用来实现匹配功能,我们在构造方法中创建线程:

 public Matcher() {// 创建三个线程, 分别针对这三个匹配队列, 进行操作.Thread t1 = new Thread() {@Overridepublic void run() {// 扫描 normalQueuewhile (true) {handlerMatch(normalQueue);}}};t1.start();Thread t2 = new Thread(){@Overridepublic void run() {while (true) {handlerMatch(highQueue);}}};t2.start();Thread t3 = new Thread() {@Overridepublic void run() {while (true) {handlerMatch(veryHighQueue);}}};t3.start();}
实现handlerMatch()方法进行匹配
 public void handlerMatch(Queue<User> matchQueue) {try {//对操作的队列加锁保证线程安全synchronized (matchQueue) {//1.检测队列中是否有两个元素while(matchQueue.size() < 2) {matchQueue.wait();}//2.从队列中取出两个玩家User user1 = matchQueue.poll();User user2 = matchQueue.poll();//3.获取到两个玩家的会话信息WebSocketSession session1 = onlineUserManager.getFromHall(user1.getUserId());WebSocketSession session2 = onlineUserManager.getFromHall(user2.getUserId());//4.todo 把两个玩家放到一个游戏房间中//5.给用户返回匹配成功的响应MatchResponse response = new MatchResponse();response.setOk(true);response.setMessage("success");String json = objectMapper.writeValueAsString(response);session1.sendMessage(new TextMessage(json));session2.sendMessage(new TextMessage(json));}}catch (IOException | InterruptedException e) {e.printStackTrace();}}
修改websocket后端代码
@Component
public class MatchWebSocket extends TextWebSocketHandler {@Autowiredprivate Matcher matcher;@Autowiredprivate OnlineUserManager onlineUserManager;ObjectMapper objectMapper=new ObjectMapper();//连接成功后执行@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {try {User user = (User) session.getAttributes().get("user");if (onlineUserManager.getFromHall(user.getUserId()) != null) {MatchResponse response = new MatchResponse();response.setOk(true);response.setErrMsg("已经在别处登录");response.setMessage("repeat_login");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));// 此处直接关闭有些太激进了, 还是返回一个特殊的 message , 供客户端来进行判定, 由客户端负责进行处理// session.close();return;} else {onlineUserManager.enterGameHall(user.getUserId(), session);System.out.println("用户:" + user.getUsername() + " 已上线");}}catch (NullPointerException e){System.out.println("[MatchAPI.afterConnectionEstablished] 当前用户未登录!");// e.printStackTrace();// 出现空指针异常, 说明当前用户的身份信息是空, 用户未登录呢.// 把当前用户尚未登录这个信息给返回回去~~MatchResponse response = new MatchResponse();response.setOk(true);response.setErrMsg("您尚未登录! 不能进行后续匹配功能!");response.setMessage("no_login");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));}}//接收到请求后执行@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {User user = (User) session.getAttributes().get("user");// 获取到客户端给服务器发送的数据String payload = message.getPayload();// 当前这个数据载荷是一个 JSON 格式的字符串, 就需要把它转成 Java 对象. MatchRequestMatchRequest request = objectMapper.readValue(payload, MatchRequest.class);MatchResponse response = new MatchResponse();if (request.getMessage().equals("startMatch")) {// 进入匹配队列matcher.add(user);// 把玩家信息放入匹配队列之后, 就可以返回一个响应给客户端了.response.setOk(true);response.setMessage("startMatch");} else if (request.getMessage().equals("stopMatch")) {// 退出匹配队列matcher.remove(user);// 移除之后, 就可以返回一个响应给客户端了.response.setOk(true);response.setMessage("stopMatch");} else {response.setOk(false);response.setErrMsg("非法的匹配请求");}String jsonString = objectMapper.writeValueAsString(response);session.sendMessage(new TextMessage(jsonString));}//连接异常时执行@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {try {User user = (User)session.getAttributes().get("user");//防止重复登录时删除正常登录的在线信息if(onlineUserManager.getFromHall(user.getUserId()).equals(session)) {onlineUserManager.exitGameHall(user.getUserId());System.out.println("用户:" + user.getUsername() + " 已下线");matcher.remove(user);}}catch (NullPointerException e) {System.out.println("[MatchAPI.handleTransportError] 当前用户未登录!");MatchResponse response = new MatchResponse();response.setOk(false);response.setErrMsg("用户未登录");response.setMessage("no_login");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));}}//连接正常断开后执行@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {try {User user = (User)session.getAttributes().get("user");//防止重复登录时删除正常登录的在线信息if(onlineUserManager.getFromHall(user.getUserId()).equals(session)) {onlineUserManager.exitGameHall(user.getUserId());System.out.println("用户:" + user.getUsername() + " 已下线");matcher.remove(user);}}catch (NullPointerException e) {System.out.println("[MatchAPI.afterConnectionClosed] 当前用户未登录!");MatchResponse response = new MatchResponse();response.setOk(false);response.setErrMsg("用户未登录");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));}}}
房间管理器实体类

这里我们创建了两个哈希表,一个维护房间id到游戏房间的映射,一个维护用户id到游戏房间的映射,此时我们就可以通过add方法把两个用户加入一个游戏房间内

package com.example.demo;import org.springframework.stereotype.Component;import java.util.concurrent.ConcurrentHashMap;@Component
public class RoomManager {//通过房间id来获得房间private ConcurrentHashMap<String, Room> roomIdToRoom = new ConcurrentHashMap<>();//通过用户id来获取房间id,然后再获取房间private ConcurrentHashMap<Integer, String> userIdToRoomId = new ConcurrentHashMap<>();public void add(String roomId, Room room, Integer userId1, Integer userId2) {roomIdToRoom.put(roomId, room);userIdToRoomId.put(userId1, roomId);userIdToRoomId.put(userId2, roomId);}public void remove(String roomId, int userId1, int userId2) {roomIdToRoom.remove(roomId);userIdToRoomId.remove(userId1);userIdToRoomId.remove(userId2);}public Room getRoomByRoomId(String roomId) {return roomIdToRoom.get(roomId);}public Room getRoomByUserId(Integer userId) {return roomIdToRoom.get(userIdToRoomId.get(userId));}
}
进入房间代码
Room room = new Room();
roomManager.add(room.getRoomId(), room, user1.getUserId(), user2.getUserId());
游戏房间前端代码

room.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>游戏房间</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/game_room.css">
</head>
<body><div class="nav">五子棋对战</div><div class="container"><div><!-- 棋盘区域, 需要基于 canvas 进行实现 --><canvas id="chess" width="450px" height="450px"></canvas><!-- 显示区域 --><div id="screen"> 等待玩家连接中... </div></div></div><script src="js/script.js"></script></body>
</html>

common.css

/* 公共样式 */* {margin: 0;padding: 0;box-sizing: border-box;
}html, body {height: 100%;background-image: url(../img/kk.png);background-repeat: no-repeat;background-position: center;background-size: cover;
}.nav {height: 50px;background-color: gray;color: white;font-size: 20px;display: flex;justify-content: center;align-items: center;
}
.container {width: 100%;height: calc(100% - 50px);display: flex;align-items: center;justify-content: center;
}

game_room.css

#screen {width: 450px;height: 50px;margin-top: 10px;background-color: #fff;font-size: 22px;line-height: 50px;text-align: center;
}.return-btn {width: 450px;height: 50px;margin-top: 5px;background-color: orange;color: #fff;font-size: 22px;line-height: 50px;text-align: center;
}

script.js

gameInfo = {roomId: null,thisUserId: null,thatUserId: null,isWhite: true,
}//////////////////////////////////////////////////
// 设定界面显示相关操作
//////////////////////////////////////////////////function setScreenText(me) {let screen = document.querySelector('#screen');if (me) {screen.innerHTML = "轮到你落子了!";} else {screen.innerHTML = "轮到对方落子了!";}
}//////////////////////////////////////////////////
// 初始化 websocket
//////////////////////////////////////////////////
// TODO//////////////////////////////////////////////////
// 初始化一局游戏
//////////////////////////////////////////////////
function initGame() {// 是我下还是对方下. 根据服务器分配的先后手情况决定let me = gameInfo.isWhite;// 游戏是否结束let over = false;let chessBoard = [];//初始化chessBord数组(表示棋盘的数组)for (let i = 0; i < 15; i++) {chessBoard[i] = [];for (let j = 0; j < 15; j++) {chessBoard[i][j] = 0;}}let chess = document.querySelector('#chess');let context = chess.getContext('2d');context.strokeStyle = "#BFBFBF";// 背景图片let logo = new Image();logo.src = "img/sky.jpeg";logo.onload = function () {context.drawImage(logo, 0, 0, 450, 450);initChessBoard();}// 绘制棋盘网格function initChessBoard() {for (let i = 0; i < 15; i++) {context.moveTo(15 + i * 30, 15);context.lineTo(15 + i * 30, 430);context.stroke();context.moveTo(15, 15 + i * 30);context.lineTo(435, 15 + i * 30);context.stroke();}}// 绘制一个棋子, me 为 truefunction oneStep(i, j, isWhite) {context.beginPath();context.arc(15 + i * 30, 15 + j * 30, 13, 0, 2 * Math.PI);context.closePath();var gradient = context.createRadialGradient(15 + i * 30 + 2, 15 + j * 30 - 2, 13, 15 + i * 30 + 2, 15 + j * 30 - 2, 0);if (!isWhite) {gradient.addColorStop(0, "#0A0A0A");gradient.addColorStop(1, "#636766");} else {gradient.addColorStop(0, "#D1D1D1");gradient.addColorStop(1, "#F9F9F9");}context.fillStyle = gradient;context.fill();}chess.onclick = function (e) {if (over) {return;}if (!me) {return;}let x = e.offsetX;let y = e.offsetY;// 注意, 横坐标是列, 纵坐标是行let col = Math.floor(x / 30);let row = Math.floor(y / 30);if (chessBoard[row][col] == 0) {// TODO 发送坐标给服务器, 服务器要返回结果oneStep(col, row, gameInfo.isWhite);chessBoard[row][col] = 1;}}
}initGame();

下篇文章我们来写对战的相关代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/91608.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/91608.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/91608.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL分析步

MySQL分析 -- 库名 set dbName bsa_crmeb_bak; -- 表名 set tableName bsa_crmeb_bak;-- 查看bsa_crmeb_bak数据库基本信息 SELECTSCHEMA_NAME AS 数据库名,DEFAULT_CHARACTER_SET_NAME AS 字符集,DEFAULT_COLLATION_NAME AS 排序规则 FROM information_schema.SCHEMATA WHER…

工程化(二):为什么你的下一个项目应该使用Monorepo?(pnpm / Lerna实战)

工程化(二)&#xff1a;为什么你的下一个项目应该使用Monorepo&#xff1f;&#xff08;pnpm / Lerna实战&#xff09; 引子&#xff1a;前端项目的“孤岛困境” 随着你的项目或团队不断成长&#xff0c;一个棘手的问题会逐渐浮现&#xff1a;代码该如何组织&#xff1f; 最…

应用药品注册证识别技术,为医药行业的合规、高效与创新发展提供核心驱动力

在医药行业的庞杂数据海洋中&#xff0c;药品注册证&#xff08;如中国的“国药准字”、美国的NDA/ANDA批号&#xff09;是药品合法上市流通的“身份证”。面对海量的证书审核、录入与验证需求&#xff0c;传统人工处理方式不仅效率低下、成本高昂&#xff0c;更易因疲劳导致差…

Spring Boot 2.1.18 集成 Elasticsearch 6.6.2 实战指南

Spring Boot 2.1.18 集成 Elasticsearch 6.6.2 实战指南前言&#xff1a;一. JAVA客户端对比二. 导入数据2.1 分析创建索引2.2 代码实现三. ElasticSearch 查询3.1 matchAll 查询3.2 term查询3.3 match查询3.4 模糊查询3.5 范围查询3.6 字符串查询3.7 布尔查询3.8 分页与排序3.…

向量投影计算,举例说明

向量投影计算,举例说明 向量投影是指将一个向量(设为向量b\mathbf{b}b)投射到另一个向量(设为向量a\mathbf{a}a)所在直线上,得到一个与a\mathbf{a}

如何在技术世界中保持清醒和高效

“抽象泄露&#xff0c;是存在的&#xff0c;但你需要了解多少&#xff0c;需要理解多深&#xff0c;这一点是因人而异的&#xff0c;绝对不是别人能够建议的。每个人只会站在自己的立场上去建议别人怎么做。”在写下这句话时&#xff0c;身为一个技术开发者&#xff0c;我似乎…

服装公司数字化转型如何做?

WL贸易集团公司&#xff08;以下简称WL&#xff09;自2012年成立以来&#xff0c;在十余年的发展历程中不断蜕变与升级。公司始终秉持“时尚与品质优先”的核心经营理念&#xff0c;通过严格执行高标准、严要求&#xff0c;牢牢把握产品品质与交货周期两大关键&#xff0c;赢得…

GM DC Monitor 之 银河麒麟 Docker 部署安装手册

官方网站&#xff1a;www.gm-monitor.com 本手册以银河麒麟为例&#xff0c;介绍在 Linux 系统上安装和配置DOCKER服务的详细步骤 一、以root用户执行以下操作命令 1、环境优化 modprobe br_netfilter cat <<EOF > /etc/sysctl.d/docker.conf net.bridge.bridge-n…

网络编程接口bind学习

1、概述下面2个问题你会怎么回答呢?1、bind如果绑定0号端口&#xff0c;可以工作么&#xff0c;如果能正常工作&#xff0c;绑定的什么端口 2、客户端可以调用bind么2、解析2.1、bind如果绑定0号端口&#xff0c;可以工作么&#xff0c;如果能正常工作&#xff0c;绑定的什么端…

FinOps X 2025 核心发布:AI 时代下的 FinOps 转型

2025年&#xff0c;人工智能技术的突破性发展正深刻重塑商业与技术格局&#xff0c;智能技术已成为各领域创新的核心驱动力。在此背景下&#xff0c;FinOps X 2025 围绕 AI 技术对财务运营&#xff08;FinOps&#xff09;的革新作用展开深度探讨&#xff0c;重点呈现了以下关键…

使用Min-Max进行数据特征标准化

在数据处理过程中&#xff0c;标准化是非常重要的步骤之一&#xff0c;特别是在机器学习和数据分析中。Min-Max标准化&#xff08;也称为归一化&#xff09;是一种常用的数据标准化方法&#xff0c;它通过将数据缩放到一个指定的范围&#xff08;通常是0到1之间&#xff09;&am…

【Dart 教程系列第 51 篇】Iterable 中 reduce 函数的用法

这是【Dart 教程系列第 51 篇】,如果觉得有用的话,欢迎关注专栏。 博文当前所用 Dart SDK:3.5.4 文章目录 一:reduce 作用 二:举例说明 1:求和 2:查找最大/最小值 3:字符串拼接 4:自定义对象合并 三:注意事项 一:reduce 作用 reduce 是 Iterable 的一个方法,用于…

使用VSCode配置Flutter

本周&#xff08;学期第四周&#xff09;任务&#xff1a; 1.简单学习Flutter&#xff0c;完成环境安装与配置 2.探索Flutter与Unity集成方案 一、Flutter环境配置 根据Flutter官方文档进行环境配置&#xff1a;开发 Android 应用 | Flutter 中文文档 - Flutter 中文开发者网…

React 开发中遇见的低级错误

1.useState不起效果 异步 改用 useRef2.map循环{ WechatQuestionnaireData && WechatQuestionnaireData?.questions?.map((item: any) > (<div className{styles[title]}>{item.questionTitle}</div>))}注意这里的 》 后面是括号 我开始写成{} 好久…

iphone手机使用charles代理,chls.pro/ssl 后回车 提示浏览器打不开该网页

iphone手机使用charles代理,chls.pro/ssl 后回车 提示浏览器打不开该网页) 1、问题现状&#xff1a; Charles安装证书异常问题&#xff0c;网页访问chls.pro/ssl提示网页打不开&#xff0c;在charles页面有链接&#xff0c;可以看到http请求和https就是看不到详细内容 2、解决方…

第11届蓝桥杯Python青少组_国赛_高级组_2020年10月真题

第11届蓝桥杯Python青少组_国赛_高级组_2020年10月真题 更多内容请查看网站&#xff1a;【试卷中心 -----> 蓝桥杯----> Python ----> 国赛】 网站链接 青少年软件编程历年真题模拟题实时更新 一、选择题 第 1 题 执行以下程序,输出的结果是 ( )。 print( 0.1 …

如何处理Y2K38问题

一、什么是Y2K38问题Y2K38 问题&#xff0c;也称为 2038年问题&#xff0c;是一个类似于Y2K问题的计算机日期处理问题。1、什么是Y2K38 问题&#xff1f;Y2K38 问题是指在计算机系统中&#xff0c;某些使用 32位有符号整数 来存储时间的程序&#xff0c;将在 2038年1月19日03时…

LeetCode热题100——146. LRU 缓存

https://leetcode.cn/problems/lru-cache/description/?envTypestudy-plan-v2&envIdtop-100-liked 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓…

一个Pycharm窗口添加多个项目来满足运行多个项目的需求

需求&#xff1a;此前项目文件只有D:\pythonProject 现在进行了如下操作 同时显示两个文件夹D:\pythonProject D:\pythonProject-gh操作步骤如下&#xff1a;最终结果如图所示

mars3d实现省界线宽度>市界线宽度效果

效果图&#xff1a; 实现代码&#xff1a; export function showChinaLine() {map.basemap 2017graphicLayer new mars3d.layer.GeoJsonLayer({name: "全国省界",url: "https://data.mars3d.cn/file/geojson/areas/420000_full.json",format: simplifyG…