diff --git a/src/main/java/com/gxwebsoft/ai/config/WebSocketServer.java b/src/main/java/com/gxwebsoft/ai/config/WebSocketServer.java index 3293edc..934c7c0 100644 --- a/src/main/java/com/gxwebsoft/ai/config/WebSocketServer.java +++ b/src/main/java/com/gxwebsoft/ai/config/WebSocketServer.java @@ -58,7 +58,9 @@ public class WebSocketServer { logger.info("websocket connected, userId={}", userId); try { - send(userId, "连接成功"); + // 握手回应属于这条连接本身,不能走 {@link #send}:那条路会优先投给 + // 在途的 HTTP 流,可能把「连接成功」送到别的请求上,端上就等不到握手回执。 + session.getBasicRemote().sendText("连接成功"); } catch (IOException e) { logger.warn("websocket 握手回应发送失败, userId={}", userId, e); } @@ -124,17 +126,25 @@ public class WebSocketServer { /** * 向指定用户推送一条文本;用户不在线时静默丢弃。 * - *
优先走 WebSocket;没有截活会话时退到 HTTP 流式通道 - * ({@link ChatStreamRegistry})。生产网关在 {@code /api} 前丢 Upgrade 头, - * WS 握手根本进不来,端上因此一直在用 HTTP 流那条路。 + *
先投在途的 HTTP 流通道(SSE),没有才退到 WebSocket。 + * + *
HTTP 流通道是按请求登记的:登记期间发出的每一帧,都属于那条正在跑的 + * {@code /stream} 请求。反过来按「这个用户有没有 WS 会话」优先就会出事——同一个账号 + * 在问答助手开着(WS 会话在 {@code SESSIONS} 里)时,再到文稿工作台生成,文稿页 + * 那条 SSE 一帧都收不到:正文只能等末帧,收尾的 {@code __END__} 永远不来, + * 生成按钮就卡在加载态、点不动(见 issue 20)。 + * + *
没有在途请求时 {@link ChatStreamRegistry#emit} 返回 {@code false},照旧走 WS。
+ * 生产网关在 {@code /api} 前丢 Upgrade 头、WS 握手进不来,端上用的正是 SSE 那条路。
*/
static void send(String userId, String message) throws IOException {
+ if (ChatStreamRegistry.emit(userId, message)) {
+ return;
+ }
Session target = SESSIONS.get(userId);
if (target != null) {
target.getBasicRemote().sendText(message);
- return;
}
- ChatStreamRegistry.emit(userId, message);
}
diff --git a/src/test/java/com/gxwebsoft/ai/config/ChatStreamRegistryTest.java b/src/test/java/com/gxwebsoft/ai/config/ChatStreamRegistryTest.java
index ad9e17f..9b132c7 100644
--- a/src/test/java/com/gxwebsoft/ai/config/ChatStreamRegistryTest.java
+++ b/src/test/java/com/gxwebsoft/ai/config/ChatStreamRegistryTest.java
@@ -2,7 +2,10 @@ package com.gxwebsoft.ai.config;
import org.junit.jupiter.api.Test;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.Session;
import java.io.IOException;
+import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -97,4 +100,67 @@ class ChatStreamRegistryTest {
assertEquals(Collections.singletonList("{\"answer\":\"fallback\"}"), received);
}
+
+ /**
+ * 回归(issue 20):同一账号开着问答助手(WS 会话在线)时,文稿工作台的
+ * {@code /stream} 请求也必须收到自己的帧。原先按「先看 WS 会话」派发,
+ * 这些帧全被投到 WS 上,SSE 拿不到收尾的 {@code __END__},生成按钮卡在加载态点不动。
+ */
+ @Test
+ void 有在途流式通道时即使WebSocket在线也投给流式通道() throws IOException {
+ String user = newUser();
+ List