feat: 添加 WebSocket Handler 与会话管理器

This commit is contained in:
2026-06-23 10:55:06 +08:00
parent 6ef11b8d5b
commit be37d51930
2 changed files with 278 additions and 6 deletions

View File

@@ -1,32 +1,198 @@
package com.webgame.webgamebackend.ws;
import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.webgame.webgamebackend.service.GomokuGameService;
import com.webgame.webgamebackend.ws.dto.WsInboundMessage;
import com.webgame.webgamebackend.ws.dto.WsMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
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;
import java.io.IOException;
import java.net.URI;
import java.util.Map;
/**
* 房间 WebSocket 处理器
*
* 管理游戏房间内的实时通信,包括玩家加入、离开、游戏状态同步等
* 后续任务将实现具体业务逻辑
* 处理客户端连接、断开与消息路由。消息类型路由到不同的业务逻辑
* 通过 URL 参数传递 roomId 和 token 完成认证与房间绑定
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class RoomWebSocketHandler extends TextWebSocketHandler {
private final RoomSessionManager sessionManager;
private final GomokuGameService gomokuGameService;
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// 后续任务实现
// 从 URL 参数中提取 roomId 和 token
URI uri = session.getUri();
if (uri == null) {
session.close(CloseStatus.BAD_DATA);
return;
}
String query = uri.getQuery();
if (query == null) {
session.close(CloseStatus.BAD_DATA);
return;
}
String roomId = getQueryParam(query, "roomId");
String token = getQueryParam(query, "token");
if (roomId == null || token == null) {
session.close(CloseStatus.BAD_DATA);
return;
}
// 通过 token 获取用户 ID
Object loginId = StpUtil.getLoginIdByToken(token);
if (loginId == null) {
session.close(CloseStatus.POLICY_VIOLATION);
return;
}
String userId = loginId.toString();
// 注册会话
sessionManager.addSession(roomId, userId, session);
log.info("[WS] 连接建立, roomId={}, userId={}, sessionId={}", roomId, userId, session.getId());
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
// 后续任务实现
String payload = message.getPayload();
String userId = sessionManager.getUserId(session);
String roomId = sessionManager.getRoomId(session);
if (userId == null || roomId == null) {
sendError(session, "未加入房间");
return;
}
log.info("[WS] 收到消息, roomId={}, userId={}, payload={}", roomId, userId, payload);
try {
JSONObject json = JSON.parseObject(payload);
String type = json.getString("type");
Object data = json.get("data");
switch (type) {
case WsInboundMessage.PLACE_STONE -> {
// data 包含 row, col
int row = ((JSONObject) data).getIntValue("row");
int col = ((JSONObject) data).getIntValue("col");
gomokuGameService.placeStone(roomId, userId, row, col, sessionManager);
}
case WsInboundMessage.CHAT -> {
String content = ((JSONObject) data).getString("content");
handleChat(roomId, userId, content);
}
case WsInboundMessage.READY -> {
gomokuGameService.toggleReady(roomId, userId, sessionManager);
}
case WsInboundMessage.RESIGN -> {
gomokuGameService.resign(roomId, userId, sessionManager);
}
case WsInboundMessage.DRAW_REQUEST -> {
gomokuGameService.requestDraw(roomId, userId, sessionManager);
}
case WsInboundMessage.DRAW_RESPONSE -> {
boolean accept = ((JSONObject) data).getBooleanValue("accept");
gomokuGameService.respondToDraw(roomId, userId, accept, sessionManager);
}
case WsInboundMessage.LEAVE -> {
gomokuGameService.leaveRoom(roomId, userId, sessionManager);
}
case WsInboundMessage.START -> {
gomokuGameService.startGame(roomId, userId, sessionManager);
}
default -> sendError(session, "未知消息类型: " + type);
}
} catch (Exception e) {
log.error("[WS] 处理消息异常, roomId={}, userId={}", roomId, userId, e);
sendError(session, e.getMessage());
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
// 后续任务实现
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
String userId = sessionManager.getUserId(session);
String roomId = sessionManager.getRoomId(session);
log.info("[WS] 连接关闭, roomId={}, userId={}, sessionId={}, status={}",
roomId, userId, session.getId(), status);
if (userId != null && roomId != null) {
// 通知游戏服务玩家断开
gomokuGameService.handleDisconnect(roomId, userId, sessionManager);
}
sessionManager.removeSession(session);
}
// ==================== 辅助方法 ====================
/**
* 解析 URL Query 参数
*/
private String getQueryParam(String query, String key) {
for (String param : query.split("&")) {
String[] pair = param.split("=", 2);
if (pair.length == 2 && pair[0].equals(key)) {
return pair[1];
}
}
return null;
}
/**
* 聊天消息处理(暂不持久化,直接广播)
*/
private void handleChat(String roomId, String userId, String content) {
// TODO: 后续可增加消息持久化
broadcastToRoom(roomId, new WsMessage(WsOutboundMessage.CHAT_BROADCAST, Map.of(
"userId", userId,
"content", content,
"timestamp", System.currentTimeMillis()
)));
}
/**
* 向房间内所有会话广播消息
*/
private void broadcastToRoom(String roomId, WsMessage message) {
String json = JSON.toJSONString(message);
for (WebSocketSession ws : sessionManager.getRoomSessions(roomId)) {
if (ws.isOpen()) {
try {
ws.sendMessage(new TextMessage(json));
} catch (IOException e) {
log.error("[WS] 发送消息失败, sessionId={}", ws.getId(), e);
}
}
}
}
/**
* 向指定会话发送错误消息
*/
private void sendError(WebSocketSession session, String errorMsg) {
if (session.isOpen()) {
try {
String json = JSON.toJSONString(
new WsMessage(WsOutboundMessage.ERROR, Map.of("message", errorMsg)));
session.sendMessage(new TextMessage(json));
} catch (IOException e) {
log.error("[WS] 发送错误消息失败", e);
}
}
}
}