【萌笔趣棋】项目开发
一.准备工作
用户表——user
CREATE TABLE user (userId INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,username VARCHAR(50) NULL UNIQUE,password VARCHAR(50) NULL,score INT(11) NULL,totalCount INT(11) NULL,winCount INT(11) NULL
);
插入数据
二.创建项目
1.创建springboot项目,选择如下依赖

2.创建对应目录

3.准备前端页面
把博客系统静态⻚⾯拷⻉到static⽬录下
4.配置application.yml
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/java_gobang?characterEncoding=utf8&useSSL=falseusername: rootpassword: 111111driver-class-name: com.mysql.cj.jdbc.Drivermybatis:mapper-locations: classpath:mapper/**Mapper.xml实体类——model
User类
用于:封装用户信息(为一个对象)
public class User {private int userId;private String username;private String password;private int score;private int totalCount;private int winCount;public int getUserId() {return userId;}public void setUserId(int userId) {this.userId = userId;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public int getScore() {return score;}public void setScore(int score) {this.score = score;}public int getTotalCount() {return totalCount;}public void setTotalCount(int totalCount) {this.totalCount = totalCount;}public int getWinCount() {return winCount;}public void setWinCount(int winCount) {this.winCount = winCount;}
}UserMapper接口
配合 MyBatis 等持久层框架,实现 Java 对象与数据库表的字段映射,从而操作 user表
@Mapper
是持久层框架—MyBaits里提供的一个接口注解。接口上标注了这个注解,就可以和xml映射文件关联起来。从而通过调用接口方法 实现对数据库表的增删查改。(xml文件中具体实现了接口里定义的方法)
@Mapper
public interface UserMapper {User selectByName(String username);int insert(User user);void userWin(User user);void userLose(User user);}
3.实现xml映射文件
用于:实现 UserMapper接口中定义的方法
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.model.UserMapper"><select id="selectByName" resultType="com.example.demo.model.User">select * from user where username = #{username}</select><select id="selectById" resultType="com.example.demo.model.User">select * from user where userId = #{userId}</select><insert id="insert">insert into user values(null, #{username}, #{password}, 1000, 0, 0)</insert><update id="userWin">update user set score = score + 25, totalCount = totalCount + 1, winCount = winCount + 1 where userId = #{userId}</update><update id="userLose">update user set score = score - 25, totalCount = totalCount + 1 where userId = #{userId}</update>
</mapper>Game
GameReadyResponse
封装游戏准备阶段 的响应信息
这个类是一个数据传输对象,用于在游戏服务器和客户端之间传递游戏准备阶段的信息

public class GameReadyResponse {private String message = "gameReady";private boolean ok = true;private String reason = "";private String roomId = "";private int thisUserId = 0;private int thatUserId = 0;private int whiteUserId = 0;public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public boolean isOk() {return ok;}public void setOk(boolean ok) {this.ok = ok;}public String getReason() {return reason;}public void setReason(String reason) {this.reason = reason;}public String getRoomId() {return roomId;}public void setRoomId(String roomId) {this.roomId = roomId;}public int getThisUserId() {return thisUserId;}public void setThisUserId(int thisUserId) {this.thisUserId = thisUserId;}public int getThatUserId() {return thatUserId;}public void setThatUserId(int thatUserId) {this.thatUserId = thatUserId;}public int getWhiteUserId() {return whiteUserId;}public void setWhiteUserId(int whiteUserId) {this.whiteUserId = whiteUserId;}
}
GameRequest
封装 游戏操作 (如落子)的请求信息
这个类是一个数据传输对象(DTO),用于在游戏服务器和客户端之间传递游戏操作的结果


public class GameRequest {// 如果不给 message 设置 getter / setter, 则不会被 jackson 序列化private String message = "putChess";private int userId;private int row;private int col;public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public int getUserId() {return userId;}public void setUserId(int userId) {this.userId = userId;}public int getRow() {return row;}public void setRow(int row) {this.row = row;}public int getCol() {return col;}public void setCol(int col) {this.col = col;}
}GameResponse
用于封装游戏操作的响应信息
这个类是一个数据传输对象(DTO),用于在游戏服务器和客户端之间传递游戏操作的结果

public class GameResponse {// 如果不给 message 设置 getter / setter, 则不会被 jackson 序列化private String message = "putChess";private int userId;private int row;private int col;private int winner; // 胜利玩家的 userIdpublic String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public int getUserId() {return userId;}public void setUserId(int userId) {this.userId = userId;}public int getRow() {return row;}public void setRow(int row) {this.row = row;}public int getCol() {return col;}public void setCol(int col) {this.col = col;}public int getWinner() {return winner;}public void setWinner(int winner) {this.winner = winner;}
}MathRequest
用于封装匹配的请求信息
public class MatchRequest {private String message = "";public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}
}
MathResponse
用于封装匹配的响应信息
public class MatchResponse {private boolean ok = true;private String reason = "";private String message = "";public boolean isOk() {return ok;}public void setOk(boolean ok) {this.ok = ok;}public String getReason() {return reason;}public void setReason(String reason) {this.reason = reason;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}
}OnlineUserManager
管理在线用户会话
@component
是 Spring 框架中用于标记组件类的注解,它的作用是告诉 Spring 容器:“这个类是一个需要管理的组件,请创建它的实例并纳入容器管理。” 那么这个实例就可以过 @Autowired 或 @Resource 注解注入到其他类中
ConcurrentHashMap
线程安全的哈希表:支持多线程并发读写
保证多线程环境下的安全操作,避免了显式同步锁

WebSocketSession
实现客户端和服务器之间的通信

@Component
public class OnlineUserManager {//成员变量//表示 “游戏大厅” 场景,存储等待匹配或未进入游戏房间的用户会话private ConcurrentHashMap<Integer, WebSocketSession> gameHall = new ConcurrentHashMap<>();//表示 “游戏房间” 场景,存储正在游戏中的用户会话private ConcurrentHashMap<Integer, WebSocketSession> gameRoom = new ConcurrentHashMap<>();//进入游戏大厅public void enterGameHall(int userId, WebSocketSession session) {gameHall.put(userId, session);}//离开游戏大厅public void exitGameHall(int userId) {gameHall.remove(userId);}//获取游戏大厅中的用户会话public WebSocketSession getSessionFromGameHall(int userId) {return gameHall.get(userId);}//进入游戏房间public void enterGameRoom(int userId, WebSocketSession session) {gameRoom.put(userId, session);}//离开游戏房间public void exitGameRoom(int userId) {gameRoom.remove(userId);}//获取游戏房间中的用户会话public WebSocketSession getSessionFromGameRoom(int userId) {return gameRoom.get(userId);}
}RoomManager
用于管理游戏房间和用户关系,实现了房间和用户的映射关系
@Component
public class RoomManager {//存储所有活跃的游戏房间,通过房间 ID 可以快速查找对应的房间对象//键 (Key):房间 ID(String 类型)//值 (Value):Room 对象private ConcurrentHashMap<String, Room> rooms = new ConcurrentHashMap<>();//记录每个用户当前所在的房间,通过用户 ID 可以快速查找其所在房间//键 (Key):用户 ID(Integer 类型)//值 (Value):房间 ID(String 类型)private ConcurrentHashMap<Integer, String> userIdToRoomId = new ConcurrentHashMap<>();/*** 创建一个新房间并将两个用户加入该房间* */public void addRoom(Room room, int userId1, int userId2) {rooms.put(room.getRoomId(), room);userIdToRoomId.put(userId1, room.getRoomId());userIdToRoomId.put(userId2, room.getRoomId());}/*** 通过房间 ID 获取对应的 Room 对象* */public Room getRoomByRoomId(String roomId) {return rooms.get(roomId);}/*** 通过用户 ID 获取其所在的房间* */public Room getRoomByUserId(int userId) {String roomId = userIdToRoomId.get(userId);if (roomId == null) {return null;}return getRoomByRoomId(roomId);}/*** 移除指定房间并解除两个用户与该房间的关联* */public void removeRoom(String roomId, int userId1, int userId2) {rooms.remove(roomId);userIdToRoomId.remove(userId1);userIdToRoomId.remove(userId2);}public ConcurrentHashMap<String, Room> getRooms() {return rooms;}public void setRooms(ConcurrentHashMap<String, Room> rooms) {this.rooms = rooms;}public ConcurrentHashMap<Integer, String> getUserIdToRoomId() {return userIdToRoomId;}public void setUserIdToRoomId(ConcurrentHashMap<Integer, String> userIdToRoomId) {this.userIdToRoomId = userIdToRoomId;}
}Room
objectMapper

实现了一个完整的五子棋游戏房间功能
public class Room {//房间与玩家信息private String roomId;// 玩家1private User user1;// 玩家2private User user2;// 先手方的用户 idprivate int whiteUserId = 0;// 棋盘结构 15*15private static final int MAX_ROW = 15;private static final int MAX_COL = 15;private int[][] chessBoard = new int[MAX_ROW][MAX_COL];private ObjectMapper objectMapper = new ObjectMapper();@Autowiredprivate OnlineUserManager onlineUserManager;@Autowiredprivate RoomManager roomManager;@Resourceprivate UserMapper userMapper;/*** 构造方法* */public Room() {// 生成唯一房间IDroomId = UUID.randomUUID().toString();onlineUserManager = DemoApplication.ac.getBean(OnlineUserManager.class);
// roomManager = DemoApplication.ac.getBean(RoomManager.class);
// userMapper = DemoApplication.ac.getBean(UserMapper.class);System.out.println("create Room: " + roomId + ", roomManager: " + roomManager);}// getter / setter 方法略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 int getWhiteUserId() {return whiteUserId;}public void setWhiteUserId(int whiteUserId) {this.whiteUserId = whiteUserId;}public int[][] getChessBoard() {return chessBoard;}public void setChessBoard(int[][] chessBoard) {this.chessBoard = chessBoard;}public ObjectMapper getObjectMapper() {return objectMapper;}public void setObjectMapper(ObjectMapper objectMapper) {this.objectMapper = objectMapper;}public OnlineUserManager getOnlineUserManager() {return onlineUserManager;}public void setOnlineUserManager(OnlineUserManager onlineUserManager) {this.onlineUserManager = onlineUserManager;}/*** 落子处理方法* */public void putChess(String message) throws IOException {//通过 ObjectMapper 将客户端发送的 JSON 消息解析为 GameRequest 对象,GameRequest req = objectMapper.readValue(message, GameRequest.class);GameResponse response = new GameResponse();int chess = req.getUserId() == user1.getUserId() ? 1 : 2;// 确定落子玩家(1=玩家1,2=玩家2)int row = req.getRow();int col = req.getCol();if (chessBoard[row][col] != 0) {System.out.println("落子位置有误! " + req);return;}chessBoard[row][col] = chess;// 更新棋盘状态printChessBoard();//检查游戏胜负int winner = checkWinner(chess, row, col);// 设置响应数据response.setUserId(req.getUserId());response.setRow(row);response.setCol(col);response.setWinner(winner);// 获取两位玩家的 WebSocket 会话WebSocketSession session1 = onlineUserManager.getSessionFromGameRoom(user1.getUserId());WebSocketSession session2 = onlineUserManager.getSessionFromGameRoom(user2.getUserId());if (session1 == null) {// 玩家1 掉线, 直接认为玩家2 获胜response.setWinner(user2.getUserId());System.out.println("玩家1 掉线!");}if (session2 == null) {// 玩家2 掉线, 直接认为玩家1 获胜response.setWinner(user1.getUserId());System.out.println("玩家2 掉线!");}//将响应序列化为 JSON 字符串String responseJson = objectMapper.writeValueAsString(response);if (session1 != null) {session1.sendMessage(new TextMessage(responseJson));}if (session2 != null) {session2.sendMessage(new TextMessage(responseJson));}if (response.getWinner() != 0) {//胜负已分// 更新数据库:获胜方 +1 胜场,失败方 +1 负场userMapper.userWin(response.getWinner() == user1.getUserId() ? user1 : user2);userMapper.userLose(response.getWinner() == user1.getUserId() ? user2 : user1);// 从房间管理器中移除房间roomManager.removeRoom(roomId, user1.getUserId(), user2.getUserId());System.out.println("游戏结束, 房间已经销毁! roomId: " + roomId + " 获胜方为: " + response.getWinner());}}private void printChessBoard() {System.out.println("打印棋盘信息: ");System.out.println("===========================");for (int r = 0; r < MAX_ROW; r++) {for (int c = 0; c < MAX_COL; c++) {System.out.print(chessBoard[r][c] + " ");}System.out.println();}System.out.println("===========================");}
// 判定棋盘形式, 找出胜利的玩家.
// 如果游戏分出胜负, 则返回玩家的 id.
// 如果未分出胜负, 则返回 0
// chess 值为 1 表示玩家1 的落子. 为 2 表示玩家2 的落子private int checkWinner(int chess, int row, int col) {// 以 row, col 为中心boolean done = false;// 1. 检查所有的行(循环五次)for (int c = col - 4; c <= col; c++) {if (c < 0 || c >= MAX_COL) {continue;}if (chessBoard[row][c] == chess&& chessBoard[row][c + 1] == chess&& chessBoard[row][c + 2] == chess&& chessBoard[row][c + 3] == chess&& chessBoard[row][c + 4] == chess) {done = true;}}// 2. 检查所有的列(循环五次)for (int r = row - 4; r <= row; r++) {if (r < 0 || r >= MAX_ROW) {continue;}if (chessBoard[r][col] == chess&& chessBoard[r + 1][col] == chess&& chessBoard[r + 2][col] == chess&& chessBoard[r + 3][col] == chess&& chessBoard[r + 4][col] == chess) {done = true;}}// 3. 检查左对角线for (int r = row - 4, c = col - 4; r <= row && c <= col; r++, c++) {if (r < 0 || r >= MAX_ROW || c < 0 || c >= MAX_COL) {continue;}if (chessBoard[r][c] == chess&& chessBoard[r + 1][c + 1] == chess&& chessBoard[r + 2][c + 2] == chess&& chessBoard[r + 3][c + 3] == chess&& chessBoard[r + 4][c + 4] == chess) {done = true;}}// 4. 检查右对角线for (int r = row - 4, c = col + 4; r <= row && c >= col; r++, c--) {if (r < 0 || r >= MAX_ROW || c < 0 || c >= MAX_COL) {continue;}if (chessBoard[r][c] == chess&& chessBoard[r + 1][c - 1] == chess&& chessBoard[r + 2][c - 2] == chess&& chessBoard[r + 3][c - 3] == chess&& chessBoard[r + 4][c - 4] == chess) {done = true;}}if (!done) {return 0;}return chess == 1 ? user1.getUserId() : user2.getUserId();}
}Matcher
基于多线程的游戏匹配系统,核心功能是根据玩家分数将其分配到不同队列进行匹配

@Componentpublic class Matcher {@Autowiredprivate RoomManager roomManager;@Autowiredprivate OnlineUserManager onlineUserManager;private ObjectMapper objectMapper = new ObjectMapper();//使用 LinkedList 作为匹配队列private Queue<User> normalQueue = new LinkedList<>();private Queue<User> highQueue = new LinkedList<>();private Queue<User> veryHighQueue = new LinkedList<>();/*** 启动三个匹配线程,分别处理不同分数段的队列* */private Matcher() {//使用内部匿名线程为每个队列(普通、高级、顶级)创建独立的匹配线程,实现并行匹配,避免不同分数段玩家相互阻塞。// while (true) 确保线程持续运行,循环处理队列中的匹配请求。new Thread() {@Overridepublic void run() {while (true) {handlerMatch(normalQueue);}}}.start();new Thread() {@Overridepublic void run() {while (true) {handlerMatch(highQueue);}}}.start();new Thread() {@Overridepublic void run() {while (true) {handlerMatch(veryHighQueue);}}}.start();}private void handlerMatch(Queue<User> matchQueue) {synchronized (matchQueue) {try {// 保证只有一个玩家在队列的时候, 不会被出队列. 从而能支持取消功能.while (matchQueue.size() < 2) {matchQueue.wait();}// 1. 尝试获取两个元素User player1 = matchQueue.poll();User player2 = matchQueue.poll();System.out.println("匹配出两个玩家: " + player1.getUserId() + ", " + player2.getUserId());// 2. 检查玩家在线状态(可能在匹配中玩家突然关闭页面)WebSocketSession session1 = onlineUserManager.getSessionFromGameHall(player1.getUserId());WebSocketSession session2 = onlineUserManager.getSessionFromGameHall(player2.getUserId());if (session1 == null) {// 如果玩家1 下线, 则把玩家2 放回匹配队列matchQueue.offer(player2);return;}if (session2 == null) {// 如果玩家2 下线, 则把玩家1 放回匹配队列matchQueue.offer(player1);return;}if (session1 == session2) {// 如果得到的两个 session 相同, 说明是同一个玩家两次进入匹配队列// 例如玩家点击开始匹配后, 刷新页面, 重新再点开始匹配// 此时也把玩家放回匹配队列matchQueue.offer(player1);return;}// 3. 将这两个玩家加入到游戏房间中.// TODO 一会再写Room room = new Room();roomManager.addRoom(room, player1.getUserId(), player2.getUserId());// 4. 给玩家1 发回响应数据MatchResponse response1 = new MatchResponse();response1.setMessage("matchSuccess");session1.sendMessage(new TextMessage(objectMapper.writeValueAsString(response1)));// 5. 给玩家2 发回响应数据MatchResponse response2 = new MatchResponse();response2.setMessage("matchSuccess");session2.sendMessage(new TextMessage(objectMapper.writeValueAsString(response2)));} catch (InterruptedException | IOException e) {e.printStackTrace();}}}/*** 玩家根据分数进入不同匹配队列* *///同步机制和唤醒机制//使用 synchronized 锁定队列对象,确保同一时间只有一个线程能修改队列//notify() 唤醒可能正在等待该队列的匹配线程public void add(User user) throws InterruptedException {if (user.getScore() < 2000) {synchronized (normalQueue) {normalQueue.offer(user);normalQueue.notify();}System.out.println("[Matcher] " + user.getUserId() + " 进入 normalQueue!");} else if (user.getScore() < 3000) {synchronized (highQueue) {highQueue.offer(user);highQueue.notify();}System.out.println("[Matcher] " + user.getUserId() + " 进入 highQueue!");} else {synchronized (veryHighQueue) {veryHighQueue.offer(user);veryHighQueue.notify();}System.out.println("[Matcher] " + user.getUserId() + " 进入 veryHighQueue!");}}public void remove(User user) {if (user.getScore() < 2000) {removeFromQueue(normalQueue, user);System.out.println("[Matcher] " + user.getUserId() + " 移出 normalQueue!");} else if (user.getScore() < 3000) {removeFromQueue(highQueue, user);System.out.println("[Matcher] " + user.getUserId() + " 移出 highQueue!");} else {removeFromQueue(veryHighQueue, user);System.out.println("[Matcher] " + user.getUserId() + " 移出 veryHighQueue!");}}private void removeFromQueue(Queue<User> queue, User user) {synchronized (queue) {queue.remove(user);}}}API
UserAPI
HttpServletRequest:用于获取或创建用户会话@ResponseBody自动将返回对象序列化为 JSON@RestController:标记该类为 REST 控制器,自动将方法返回值序列化为 JSON(或其他格式)响应
@RestController
public class UserAPI {@Resourceprivate UserMapper userMapper;@PostMapping("/login")@ResponseBodypublic Object login(String username, String password, HttpServletRequest req) {User user = userMapper.selectByName(username);System.out.println("login! user=" + user);if (user == null || !user.getPassword().equals(password)) {return new User();}HttpSession session = req.getSession(true);session.setAttribute("user", user);return user;}@PostMapping("/register")@ResponseBodypublic Object register(String username, String password) {User user = null;try {user = new User();user.setUsername(username);user.setPassword(password);System.out.println("register! user=" + user);int ret = userMapper.insert(user);System.out.println("ret: " + ret);} catch (org.springframework.dao.DuplicateKeyException e) {user = new User();}return user;}@GetMapping("/userInfo")@ResponseBodypublic Object getUserInfo(HttpServletRequest req) {// 从 session 中拿到用户信息HttpSession session = req.getSession(false);if (session == null) {return new User();}User user = (User) session.getAttribute("user");if (user == null) {return new User();}return user;}
}GameAPI
@Component
public class GameAPI extends TextWebSocketHandler {private ObjectMapper objectMapper = new ObjectMapper();@Autowiredprivate RoomManager roomManager;// 这个是管理 game 页面的会话@Autowiredprivate OnlineUserManager onlineUserManager;@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {GameReadyResponse resp = new GameReadyResponse();User user = (User) session.getAttributes().get("user");if (user == null) {resp.setOk(false);resp.setReason("用户尚未登录!");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));return;}Room room = roomManager.getRoomByUserId(user.getUserId());if (room == null) {resp.setOk(false);resp.setReason("用户并未匹配成功! 不能开始游戏!");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));return;}System.out.println("连接游戏! roomId=" + room.getRoomId() + ", userId=" + user.getUserId());// 先判定用户是不是已经在游戏中了.if (onlineUserManager.getSessionFromGameHall(user.getUserId()) != null|| onlineUserManager.getSessionFromGameRoom(user.getUserId()) != null) {resp.setOk(false);resp.setReason("禁止多开游戏页面!");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));return;}// 更新会话onlineUserManager.enterGameRoom(user.getUserId(), session);// 同一个房间的两个玩家, 同时连接时要考虑线程安全问题.synchronized (room) {if (room.getUser1() == null) {room.setUser1(user);// 设置 userId1 为先手方room.setWhiteUserId(user.getUserId());System.out.println("userId=" + user.getUserId() + " 玩家1准备就绪!");return;}if (room.getUser2() == null) {room.setUser2(user);System.out.println("userId=" + user.getUserId() + " 玩家2准备就绪!");// 通知玩家1 就绪noticeGameReady(room, room.getUser1().getUserId(), room.getUser2().getUserId());// 通知玩家2 就绪noticeGameReady(room, room.getUser2().getUserId(), room.getUser1().getUserId());return;}}// 房间已经满了!resp.setOk(false);String log = "roomId=" + room.getRoomId() + " 已经满了! 连接游戏失败!";resp.setReason(log);session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));System.out.println(log);}private void noticeGameReady(Room room, int thisUserId, int thatUserId) throws IOException {GameReadyResponse resp = new GameReadyResponse();resp.setRoomId(room.getRoomId());resp.setThisUserId(thisUserId);resp.setThatUserId(thatUserId);resp.setWhiteUserId(room.getWhiteUserId());WebSocketSession session1 = onlineUserManager.getSessionFromGameRoom(thisUserId);session1.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));}@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {User user = (User) session.getAttributes().get("user");if (user == null) {return;}Room room = roomManager.getRoomByUserId(user.getUserId());room.putChess(message.getPayload());}@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {User user = (User) session.getAttributes().get("user");if (user == null) {return;}WebSocketSession existSession = onlineUserManager.getSessionFromGameRoom(user.getUserId());if (existSession != session) {System.out.println("当前的会话不是玩家游戏中的会话, 不做任何处理!");return;}System.out.println("连接出错! userId=" + user.getUserId());onlineUserManager.exitGameRoom(user.getUserId());}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {User user = (User) session.getAttributes().get("user");if (user == null) {return;}WebSocketSession existSession = onlineUserManager.getSessionFromGameRoom(user.getUserId());if (existSession != session) {System.out.println("当前的会话不是玩家游戏中的会话, 不做任何处理!");return;}System.out.println("用户退出! userId=" + user.getUserId());onlineUserManager.exitGameRoom(user.getUserId());}// 通知另外一个玩家直接获胜!private void noticeThatUserWin(User user) throws IOException {Room room = roomManager.getRoomByUserId(user.getUserId());if (room == null) {System.out.println("房间已经释放, 无需通知!");return;}User thatUser = (user == room.getUser1() ? room.getUser2() : room.getUser1());WebSocketSession session = onlineUserManager.getSessionFromGameRoom(thatUser.getUserId());if (session == null) {System.out.println(thatUser.getUserId() + " 该玩家已经下线, 无需通知!");return;}GameResponse resp = new GameResponse();resp.setUserId(thatUser.getUserId());resp.setWinner(thatUser.getUserId());session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));}
}
MatchAPI
@Component
public class MatchAPI extends TextWebSocketHandler {//将 Java 对象转换为 JSON 字符串(序列化)。//将 JSON 字符串转换为 Java 对象(反序列化)private ObjectMapper objectMapper = new ObjectMapper();@Autowiredprivate OnlineUserManager onlineUserManager;@Autowiredprivate Matcher matcher;/*** 处理连接异常* */@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {// 1. 获取用户信息User user = (User) session.getAttributes().get("user");if (user == null) {return; // 用户未登录,忽略}// 2. 验证是否为当前活跃会话WebSocketSession existSession = onlineUserManager.getSessionFromGameHall(user.getUserId());if (existSession != session) {return; // 非活跃会话,忽略}// 3. 记录异常并清理状态System.out.println("匹配页面连接异常: " + user.getUserId() + ", message: " + exception.getMessage());onlineUserManager.exitGameHall(user.getUserId()); // 从游戏大厅移除matcher.remove(user); // 从匹配队列移除}/*** 处理连接关闭* */@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {// 1. 获取用户信息User user = (User) session.getAttributes().get("user");if (user == null) {return; // 用户未登录,忽略}// 2. 验证是否为当前活跃会话WebSocketSession existSession = onlineUserManager.getSessionFromGameHall(user.getUserId());if (existSession != session) {return; // 非活跃会话,忽略}// 3. 记录关闭并清理状态System.out.println("玩家离开匹配页面: " + user.getUserId());onlineUserManager.exitGameHall(user.getUserId()); // 从游戏大厅移除matcher.remove(user); // 从匹配队列移除}/*** 连接建立* */@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {// 1. 拿到用户信息.User user = (User) session.getAttributes().get("user");if (user == null) {// 拿不到用户的登录信息, 说明玩家未登录就进入游戏大厅了.// 则返回错误信息并关闭连接MatchResponse response = new MatchResponse();response.setOk(false);response.setReason("玩家尚未登录!");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));return;}// 2. 检查玩家的上线状态if (onlineUserManager.getSessionFromGameHall(user.getUserId()) != null|| onlineUserManager.getSessionFromGameRoom(user.getUserId()) != null) {MatchResponse response = new MatchResponse();response.setOk(false);response.setReason("禁止多开游戏大厅页面!");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));return;}// 3. 设置玩家上线状态onlineUserManager.enterGameHall(user.getUserId(), session);System.out.println("玩家进入匹配页面: " + user.getUserId());}/*** 处理客户端发送的文本消息,解析为匹配请求* */@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {// 1. 拿到用户信息.User user = (User) session.getAttributes().get("user");if (user == null) {System.out.println("[onMessage] 玩家尚未登录!");return;}System.out.println("开始匹配: " + user.getUserId() + " message: " + message.toString());// 2. 解析读到的数据为 json 对象MatchRequest request = objectMapper.readValue(message.getPayload(), MatchRequest.class);MatchResponse response = new MatchResponse();if (request.getMessage().equals("startMatch")) {matcher.add(user);response.setMessage("startMatch");} else if (request.getMessage().equals("stopMatch")) {matcher.remove(user);response.setMessage("stopMatch");} else {// 匹配失败response.setOk(false);response.setReason("非法的匹配请求!");}session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));}}TestAPI
@Component
public class TestAPI extends TextWebSocketHandler {public TestAPI() {System.out.println("TestAPI load!");}@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {System.out.println("onOpen!");}@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {System.out.println("onError!");}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {System.out.println("onClose!");}@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {System.out.println("onMessage: " + message.toString());session.sendMessage(message);}
}
