fix: 暂存
This commit is contained in:
@@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
|
|||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
|
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
|
||||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||||
|
import org.springframework.web.context.request.async.AsyncRequestNotUsableException;
|
||||||
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,6 +50,16 @@ public class GlobalExceptionHandler {
|
|||||||
log.warn("SaToken 上下文未初始化(异步分派场景): {}", ex.getMessage());
|
log.warn("SaToken 上下文未初始化(异步分派场景): {}", ex.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步请求响应已不可用(SSE 连接断开后心跳试图写入)
|
||||||
|
*
|
||||||
|
* 返回 void 避免 Spring 尝试以 JSON 写入已损坏的 text/event-stream 响应。
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(AsyncRequestNotUsableException.class)
|
||||||
|
public void handleAsyncRequestNotUsable(AsyncRequestNotUsableException ex) {
|
||||||
|
// SSE 连接已断开后心跳的残留异常,无需处理
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 业务异常 ====================
|
// ==================== 业务异常 ====================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ public class SseConnectionManager {
|
|||||||
/**
|
/**
|
||||||
* 移除连接
|
* 移除连接
|
||||||
*
|
*
|
||||||
|
* 先移除映射再尝试关闭,避免回调递归。
|
||||||
|
* 响应已损坏时 complete() 可能抛出 RuntimeException,静默忽略。
|
||||||
|
*
|
||||||
* @param userId 用户 accountId
|
* @param userId 用户 accountId
|
||||||
*/
|
*/
|
||||||
public void remove(String userId) {
|
public void remove(String userId) {
|
||||||
@@ -64,7 +67,8 @@ public class SseConnectionManager {
|
|||||||
if (removed != null) {
|
if (removed != null) {
|
||||||
try {
|
try {
|
||||||
removed.complete();
|
removed.complete();
|
||||||
} catch (Exception ignored) {
|
} catch (Throwable ignored) {
|
||||||
|
// 响应已损坏(AsyncRequestNotUsableException 等),忽略
|
||||||
}
|
}
|
||||||
log.info("[SSE] 用户连接已移除, userId={}, 当前在线={}", userId, emitters.size());
|
log.info("[SSE] 用户连接已移除, userId={}, 当前在线={}", userId, emitters.size());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ public class SseController {
|
|||||||
emitter.send(SseEmitter.event()
|
emitter.send(SseEmitter.event()
|
||||||
.name("heartbeat")
|
.name("heartbeat")
|
||||||
.data(""));
|
.data(""));
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
// 客户端已断开,清理
|
// 客户端已断开(IOException)或响应已不可用(AsyncRequestNotUsableException)
|
||||||
connectionManager.remove(userId);
|
connectionManager.remove(userId);
|
||||||
heartbeat.shutdown();
|
heartbeat.shutdown();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,14 +158,35 @@ public class RoomWebSocketHandler extends TextWebSocketHandler {
|
|||||||
return null;
|
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) {
|
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: 后续可增加消息持久化
|
// TODO: 后续可增加消息持久化
|
||||||
broadcastToRoom(roomId, new WsMessage(WsOutboundMessage.CHAT_BROADCAST, Map.of(
|
broadcastToRoom(roomId, new WsMessage(WsOutboundMessage.CHAT_BROADCAST, Map.of(
|
||||||
"userId", userId,
|
"userId", userId,
|
||||||
"content", content,
|
"content", sanitized,
|
||||||
"timestamp", System.currentTimeMillis()
|
"timestamp", System.currentTimeMillis()
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user