標準項目-----網頁五子棋(4)-----游戲大廳+匹配+房間代碼

頁面實現

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…