From 2a311ca25fbcd401efd39eba628051092a1fd69e Mon Sep 17 00:00:00 2001 From: Azure <983547216@qq.com> Date: Tue, 23 Jun 2026 14:32:19 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handler/GlobalExceptionHandler.java | 11 +++++++++ .../sse/SseConnectionManager.java | 6 ++++- .../webgamebackend/sse/SseController.java | 4 ++-- .../ws/RoomWebSocketHandler.java | 23 ++++++++++++++++++- 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/webgame/webgamebackend/common/handler/GlobalExceptionHandler.java b/src/main/java/com/webgame/webgamebackend/common/handler/GlobalExceptionHandler.java index ccd0f05..c9aabe0 100644 --- a/src/main/java/com/webgame/webgamebackend/common/handler/GlobalExceptionHandler.java +++ b/src/main/java/com/webgame/webgamebackend/common/handler/GlobalExceptionHandler.java @@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.context.request.async.AsyncRequestTimeoutException; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.context.request.async.AsyncRequestNotUsableException; import org.springframework.web.servlet.resource.NoResourceFoundException; /** @@ -49,6 +50,16 @@ public class GlobalExceptionHandler { log.warn("SaToken 上下文未初始化(异步分派场景): {}", ex.getMessage()); } + /** + * 异步请求响应已不可用(SSE 连接断开后心跳试图写入) + * + * 返回 void 避免 Spring 尝试以 JSON 写入已损坏的 text/event-stream 响应。 + */ + @ExceptionHandler(AsyncRequestNotUsableException.class) + public void handleAsyncRequestNotUsable(AsyncRequestNotUsableException ex) { + // SSE 连接已断开后心跳的残留异常,无需处理 + } + // ==================== 业务异常 ==================== /** diff --git a/src/main/java/com/webgame/webgamebackend/sse/SseConnectionManager.java b/src/main/java/com/webgame/webgamebackend/sse/SseConnectionManager.java index ee3c9e1..144e22b 100644 --- a/src/main/java/com/webgame/webgamebackend/sse/SseConnectionManager.java +++ b/src/main/java/com/webgame/webgamebackend/sse/SseConnectionManager.java @@ -57,6 +57,9 @@ public class SseConnectionManager { /** * 移除连接 * + * 先移除映射再尝试关闭,避免回调递归。 + * 响应已损坏时 complete() 可能抛出 RuntimeException,静默忽略。 + * * @param userId 用户 accountId */ public void remove(String userId) { @@ -64,7 +67,8 @@ public class SseConnectionManager { if (removed != null) { try { removed.complete(); - } catch (Exception ignored) { + } catch (Throwable ignored) { + // 响应已损坏(AsyncRequestNotUsableException 等),忽略 } log.info("[SSE] 用户连接已移除, userId={}, 当前在线={}", userId, emitters.size()); } diff --git a/src/main/java/com/webgame/webgamebackend/sse/SseController.java b/src/main/java/com/webgame/webgamebackend/sse/SseController.java index a8a6bf3..305c7c4 100644 --- a/src/main/java/com/webgame/webgamebackend/sse/SseController.java +++ b/src/main/java/com/webgame/webgamebackend/sse/SseController.java @@ -69,8 +69,8 @@ public class SseController { emitter.send(SseEmitter.event() .name("heartbeat") .data("")); - } catch (IOException e) { - // 客户端已断开,清理 + } catch (Exception e) { + // 客户端已断开(IOException)或响应已不可用(AsyncRequestNotUsableException) connectionManager.remove(userId); heartbeat.shutdown(); } diff --git a/src/main/java/com/webgame/webgamebackend/ws/RoomWebSocketHandler.java b/src/main/java/com/webgame/webgamebackend/ws/RoomWebSocketHandler.java index 0b9dcfd..c6406f1 100644 --- a/src/main/java/com/webgame/webgamebackend/ws/RoomWebSocketHandler.java +++ b/src/main/java/com/webgame/webgamebackend/ws/RoomWebSocketHandler.java @@ -158,14 +158,35 @@ public class RoomWebSocketHandler extends TextWebSocketHandler { return null; } + /** 聊天消息最大长度 */ + private static final int CHAT_MAX_LENGTH = 500; + /** * 聊天消息处理(暂不持久化,直接广播) + * + * 校验规则: + * - content 不能为 null 或空字符串 + * - content 长度不能超过 CHAT_MAX_LENGTH(500 字符) + * - 移除 HTML 标签防止 XSS */ private void handleChat(String roomId, String userId, String content) { + // 内容为空或超长时直接丢弃 + if (content == null || content.isBlank()) { + return; + } + if (content.length() > CHAT_MAX_LENGTH) { + content = content.substring(0, CHAT_MAX_LENGTH); + } + + // 简易 XSS 防护:移除 HTML 标签 + String sanitized = content + .replace("<", "<") + .replace(">", ">"); + // TODO: 后续可增加消息持久化 broadcastToRoom(roomId, new WsMessage(WsOutboundMessage.CHAT_BROADCAST, Map.of( "userId", userId, - "content", content, + "content", sanitized, "timestamp", System.currentTimeMillis() ))); }