feat: 优化 SSE 转发逻辑,使用严格行读取和显式 flush
This commit is contained in:
@@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@@ -100,7 +101,7 @@ public class ShortNovelServiceImpl implements ShortNovelService {
|
||||
upstreamBody.put("action", request.getAction());
|
||||
upstreamBody.put("payload", request.getPayload());
|
||||
|
||||
return forwardSse("/api/novels/daily/conversation/stream", upstreamBody, currentUserId, null);
|
||||
return forwardSse("/api/novels/daily/conversation/stream", upstreamBody, currentUserId, request.getOriginalQuery());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,12 +140,18 @@ public class ShortNovelServiceImpl implements ShortNovelService {
|
||||
|
||||
// source() 返回的 BufferedSource 逐行从网络 socket 读取,不会缓冲整个响应
|
||||
BufferedSource source = responseBody.source();
|
||||
log.info("[ShortNovel SSE] 开始读取上游响应: path={}, userId={}", path, currentUserId);
|
||||
StringBuilder dataBuffer = new StringBuilder();
|
||||
|
||||
while (!source.exhausted()) {
|
||||
String line = source.readUtf8Line();
|
||||
if (line == null) break;
|
||||
|
||||
String line;
|
||||
try {
|
||||
line = source.readUtf8LineStrict();
|
||||
} catch (EOFException e) {
|
||||
break;
|
||||
}
|
||||
log.debug("[ShortNovel SSE] 读取到行: length={}, timestamp={}",
|
||||
line.length(), System.currentTimeMillis());
|
||||
if (line.startsWith("data:")) {
|
||||
dataBuffer.append(line.substring(5).trim());
|
||||
} else if (line.isEmpty() && dataBuffer.length() > 0) {
|
||||
@@ -158,17 +165,23 @@ public class ShortNovelServiceImpl implements ShortNovelService {
|
||||
try {
|
||||
JSONObject event = JSON.parseObject(dataStr);
|
||||
String type = event.getString("type");
|
||||
long eventTimestamp = System.currentTimeMillis();
|
||||
log.info("[ShortNovel SSE] 处理事件: type={}, timestamp={}", type, eventTimestamp);
|
||||
|
||||
// 拦截 novel_done 事件,保存到数据库
|
||||
if ("novel_done".equals(type)) {
|
||||
JSONObject payload = event.getJSONObject("payload");
|
||||
if (payload != null && originalQuery != null) {
|
||||
log.info("[ShortNovel SSE] novel_done 事件: originalQuery={}, payload={}",
|
||||
originalQuery, payload != null);
|
||||
if (payload != null && originalQuery != null && !originalQuery.trim().isEmpty()) {
|
||||
String fullText = payload.getString("full_text");
|
||||
if (fullText != null) {
|
||||
Map<String, Object> metadata = new HashMap<>();
|
||||
if (payload.get("title") != null) {
|
||||
metadata.put("title", payload.get("title"));
|
||||
}
|
||||
log.info("[ShortNovel SSE] 开始保存小说: userId={}, queryLength={}, textLength={}",
|
||||
currentUserId, originalQuery.length(), fullText.length());
|
||||
Map<String, String> saveResult = epicScriptDialogueServiceImpl.saveNovelResult(
|
||||
currentUserId,
|
||||
originalQuery,
|
||||
@@ -179,18 +192,26 @@ public class ShortNovelServiceImpl implements ShortNovelService {
|
||||
payload.put("scriptId", saveResult.get("scriptId"));
|
||||
payload.put("conversationId", saveResult.get("conversationId"));
|
||||
payload.put("currentVersionMessageId", saveResult.get("currentVersionMessageId"));
|
||||
log.info("[ShortNovel SSE] 小说保存成功: scriptId={}", saveResult.get("scriptId"));
|
||||
} else {
|
||||
log.warn("[ShortNovel SSE] novel_done 事件缺少 full_text");
|
||||
}
|
||||
} else {
|
||||
log.warn("[ShortNovel SSE] novel_done 事件跳过保存: originalQuery={}, payload={}",
|
||||
originalQuery, payload);
|
||||
}
|
||||
}
|
||||
|
||||
// 逐事件转发给前端(OkHttp 每读到一个完整 SSE 事件就立即转发)
|
||||
emitter.send(SseEmitter.event().name(type).data(event.toJSONString()));
|
||||
emitter.send(SseEmitter.event().comment("")); // 触发 flush
|
||||
} catch (Exception parseEx) {
|
||||
log.warn("SSE 事件解析失败: {}", parseEx.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("[ShortNovel SSE] 完成读取上游响应");
|
||||
emitter.complete();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
Reference in New Issue
Block a user