fix(ai): WebSocket 推送静默化,并补重试与静默推送用例(issue 11 / 12)
- WebSocketServer.sendMessage 里两行 System.out.println 删除。该方法被**每个 SSE 事件**调用一次(大纲单次生成实测 3062 个事件,即一次请求刷 6000+ 行),而且绕过 日志框架——既不受 logging.level 控制(生产把 com.gxwebsoft 压到 ERROR 也拦不住), 也不进日志文件,真故障时需要的几行会被顶掉。日志改到低频的 onOpen / onClose; 同时把 onOpen 里静默吞掉的握手回应异常改成 WARN(那是「连上了却收不到内容」的 第一现场)。 - 新增用例:AiCreativeRetryTest、AiCreativeRetryIntegrationTest(issue 11, 后者用本地 HTTP server 扮演 Dify,断言瞬时故障确实重跑一次、凭证失效只请求一次)、 WebSocketServerSilentPushTest(issue 12,捕获 System.out 断言推送路径零输出)。
This commit is contained in:
@@ -1,88 +1,106 @@
|
||||
package com.gxwebsoft.ai.config;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.websocket.OnClose;
|
||||
import javax.websocket.OnOpen;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.server.PathParam;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
@ServerEndpoint(value = "/chat/{userId}")
|
||||
@Component
|
||||
public class WebSocketServer {
|
||||
|
||||
/**
|
||||
* concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
|
||||
*/
|
||||
private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
|
||||
*/
|
||||
private Session session;
|
||||
/**
|
||||
* 接收userId
|
||||
*/
|
||||
private String userId = "";
|
||||
|
||||
/**
|
||||
* 连接建立成功调用的方法
|
||||
*/
|
||||
@OnOpen
|
||||
public void onOpen(Session session, @PathParam("userId") String userId) {
|
||||
this.session = session;
|
||||
this.userId = userId;
|
||||
if (webSocketMap.containsKey(userId)) {
|
||||
webSocketMap.remove(userId);
|
||||
webSocketMap.put(userId, this);
|
||||
//加入set中
|
||||
} else {
|
||||
webSocketMap.put(userId, this);
|
||||
}
|
||||
|
||||
try {
|
||||
sendMessage(userId, "连接成功");
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接关闭调用的方法
|
||||
*/
|
||||
@OnClose
|
||||
public void onClose() {
|
||||
if (webSocketMap.containsKey(userId)) {
|
||||
webSocketMap.remove(userId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 实现服务器主动推送
|
||||
*/
|
||||
public void sendMessage(String userId, String message) throws IOException {
|
||||
System.out.println("userId:" + userId);
|
||||
if (webSocketMap.containsKey(userId)) {
|
||||
Session session1 = webSocketMap.get(userId).session;
|
||||
System.out.println("session1:" + session1);
|
||||
if (session1 != null) session1.getBasicRemote().sendText(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 实现服务器主动推送
|
||||
*/
|
||||
public void sendAllMessage(String message) throws IOException {
|
||||
ConcurrentHashMap.KeySetView<String, WebSocketServer> userIds = webSocketMap.keySet();
|
||||
for (String userId : userIds) {
|
||||
WebSocketServer webSocketServer = webSocketMap.get(userId);
|
||||
webSocketServer.session.getBasicRemote().sendText(message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
package com.gxwebsoft.ai.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.websocket.OnClose;
|
||||
import javax.websocket.OnOpen;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.server.PathParam;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
@ServerEndpoint(value = "/chat/{userId}")
|
||||
@Component
|
||||
public class WebSocketServer {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(WebSocketServer.class);
|
||||
|
||||
/**
|
||||
* concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
|
||||
*/
|
||||
private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
|
||||
*/
|
||||
private Session session;
|
||||
/**
|
||||
* 接收userId
|
||||
*/
|
||||
private String userId = "";
|
||||
|
||||
/**
|
||||
* 连接建立成功调用的方法
|
||||
*
|
||||
* <p>这里才是值得打日志的位置:一次连接一条,低频且信息量大
|
||||
* (排查「大纲/创意生成时端上收不到流式内容」时,第一件事就是确认端到底连没连上)。
|
||||
* 推送路径 {@link #sendMessage(String, String)} 相反,单次请求会被调用上千次,必须静默。
|
||||
*/
|
||||
@OnOpen
|
||||
public void onOpen(Session session, @PathParam("userId") String userId) {
|
||||
this.session = session;
|
||||
this.userId = userId;
|
||||
if (webSocketMap.containsKey(userId)) {
|
||||
webSocketMap.remove(userId);
|
||||
webSocketMap.put(userId, this);
|
||||
//加入set中
|
||||
} else {
|
||||
webSocketMap.put(userId, this);
|
||||
}
|
||||
logger.info("websocket connected, userId={}", userId);
|
||||
|
||||
try {
|
||||
sendMessage(userId, "连接成功");
|
||||
} catch (IOException e) {
|
||||
logger.warn("websocket 握手回应发送失败, userId={}", userId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接关闭调用的方法
|
||||
*/
|
||||
@OnClose
|
||||
public void onClose() {
|
||||
if (webSocketMap.containsKey(userId)) {
|
||||
webSocketMap.remove(userId);
|
||||
logger.info("websocket closed, userId={}", userId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 实现服务器主动推送
|
||||
*
|
||||
* <p>两条 AI 流式链路(创意助手、活动策划大纲)**每个 SSE 事件**都会走到这里:
|
||||
* 实测一次大纲生成 3062 个事件,即单次请求就会调 3000+ 次。
|
||||
* 因此这里**一行日志都不能打**——原先的两行 {@code System.out.println}
|
||||
* ({@code userId} 与 {@code session1})在控制台刷出上万行「userId:833」,
|
||||
* 既刷屏、又绕过日志级别、还会淹没真正的告警。
|
||||
*
|
||||
* <p>连接与断开另见 {@link #onOpen(Session, String)} / {@link #onClose()},那里是低频的。
|
||||
*/
|
||||
public void sendMessage(String userId, String message) throws IOException {
|
||||
if (webSocketMap.containsKey(userId)) {
|
||||
Session session1 = webSocketMap.get(userId).session;
|
||||
if (session1 != null) {
|
||||
session1.getBasicRemote().sendText(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 实现服务器主动推送
|
||||
*/
|
||||
public void sendAllMessage(String message) throws IOException {
|
||||
ConcurrentHashMap.KeySetView<String, WebSocketServer> userIds = webSocketMap.keySet();
|
||||
for (String userId : userIds) {
|
||||
WebSocketServer webSocketServer = webSocketMap.get(userId);
|
||||
webSocketServer.session.getBasicRemote().sendText(message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user