docs: 详情页澄清选项卡显示不全修复设计

This commit is contained in:
2026-07-26 22:34:52 +08:00
parent 502586b64a
commit 4e79e2150b
@@ -0,0 +1,75 @@
---
author: AI Assistant
created_at: 2026-07-26
purpose: 修复详情页只显示 2 个澄清选项卡(应为 N 个)的后端累积丢失 bug
---
# 详情页澄清选项卡显示不全修复设计
## 问题
生成小说时,生成页显示 N 个澄清选项卡(clarification cards),但进入详情页后只显示前 2 个。必须保证详情页选项卡数量与生成页完全一致。
## 根因分析
通过数据库查询(t_message 按 conversation_id 分组统计)确认:3 个有 clarification 的 conversation 全部是 **clarification_question=2 + clarification_answer=3**。即用户回答了 3 次(3 个 answer),但后端只保存了 2 个 question。**丢失的是第一个 clarification_cardQ1**。
DB 中 conversation `339751895901937665` 的 messageOrder 分布:
| order | type | content |
|---|---|---|
| 1 | clarification_answer | 同事/上司 |
| 2 | clarification_question | 是什么具体事件让你决定不再讨好? |
| 3 | clarification_answer | 功劳被抢 |
| 4 | clarification_question | 你希望主角如何反击? |
| 5 | clarification_answer | 暗中布局 |
| 6 | outline | ... |
**根因链**
1. **上游 SSE 事件结构**`session_id` 字段在事件**顶层**(与 `type` 同级),不在 `payload` 内。前端从顶层取(`ScriptView.vue:1800` `const { type, session_id, payload } = event`)且工作正常。
2. **后端 status 事件处理**`ShortNovelServiceImpl.forwardSse` 行 312-314 从 `payload.getString("session_id")` 取,**取不到**。
3. **首次 stream 的 currentSessionId[0] 初始为 null**`forwardSse` 行 253 `final String[] currentSessionId = {knownSessionId}`,首次 stream 调用时 `knownSessionId=null`
4. **首次 stream 的 Q1 不累积**:首次 stream 里第一个 `clarification_card` 事件到达时 `currentSessionId[0]` 仍为 null,行 346 的 `if (currentSessionId[0] != null)` 为 falseQ1 不被 `accumulateStage` 累积。
5. **followup 流正常**followup 调用 `forwardSse``knownSessionId = request.getSessionId()`(前端传的,非 null),后续 Q2/Q3 都正确累积。
6. **drainStages 取到不完整的 stagesHistory**novel_done 时 drainStages 取到 `[A1, Q2, A2, Q3, A3, outline]`(缺 Q1),saveNovelResult 创建 2 question + 3 answer。
7. **详情页只能渲染 2 个 card**:从 DB 读到的 clarification_question 只有 2 条。
## 修复方案
**修改文件**`server/src/main/java/com/emotion/service/impl/ShortNovelServiceImpl.java`
**修改点**`forwardSse` 方法的事件循环(行 305 附近),在解析 event 后、status 处理之前,**从事件顶层取 `session_id` 同步 `currentSessionId[0]`**(与前端 `ScriptView.vue:1805-1806` 取 sessionId 的方式对齐)。
**新增代码**(放在 `String type = event.getString("type");` 之后、status 事件 `if ("status".equals(type))` 之前):
```java
// 从事件顶层取 session_id 同步 sessionId
// 上游事件结构与前端对齐:session_id 与 type 同级,不在 payload 内
// 修复:首次 stream 里 status 处理从 payload 取不到 session_id,导致 currentSessionId 保持 null
// 第一个 clarification_card 因 sessionId 为 null 不累积,详情页少显示第一个选项卡
String topSessionId = event.getString("session_id");
if (topSessionId != null && !topSessionId.isEmpty()) {
currentSessionId[0] = topSessionId;
}
```
## 影响范围
-`forwardSse` 方法新增 5 行代码
- 零现有逻辑改动(status 处理保留原样作为次选,顶层取 sessionId 优先覆盖)
- 零数据库变更
- 零前端改动
## 预期效果
- **新剧本**:首次 stream 的 Q1 不再丢失,详情页显示全部 N 个选项卡(与生成页完全一致)
- **老剧本**3 个已有 conversation):保持现状,仍显示 2 个 card。DB 中 stagesHistory 缺失的 Q1 已永久丢失,无法真实恢复。
## 验收标准
1. 在 H5 手动完成一次新的小说生成(包含至少 2 轮澄清)
2. 进入该剧本的详情页
3. 验证选项卡数量与生成页完全一致(生成 N 个,详情显示 N 个)
4. Console 无新增错误
5. 本地 `mvn clean install` 编译通过