docs: 心愿实现小说保存与复现完整性修复设计
This commit is contained in:
@@ -0,0 +1,531 @@
|
||||
---
|
||||
author: Claude
|
||||
created_at: 2026-07-26
|
||||
purpose: 修复小程序心愿实现(小说生成)页面数据保存不完整、历史列表看不到最新小说、详情页无法完整复现全流程的方案设计
|
||||
---
|
||||
|
||||
# 心愿实现小说保存与复现完整性修复设计
|
||||
|
||||
## 1. 概述
|
||||
|
||||
修复小程序"心愿实现"(ScriptView → 短篇小说生成)页面存在的多个严重问题:
|
||||
- 生成的小说没有完整、正常地保存到数据库
|
||||
- 历史列表页(ScriptLibraryView)看不到最新生成的小说
|
||||
- 详情页无法完整复现用户心愿、澄清选择、大纲、AI 生成内容、对话修改历史
|
||||
- 当前保存逻辑只在 `novel_done` 事件触发一次性保存,中间步骤全部丢失
|
||||
|
||||
## 2. 现状分析
|
||||
|
||||
### 2.1 现有数据保存链路(AS-IS)
|
||||
|
||||
```
|
||||
[小程序 ScriptView.vue]
|
||||
用户输入心愿 → POST /shortNovel/stream (SSE)
|
||||
↓
|
||||
[后端 ShortNovelServiceImpl]
|
||||
代理外部 short-novel-service → 拦截 novel_done 事件
|
||||
↓
|
||||
调用 saveNovelResult() 一次性保存 6 条数据
|
||||
↓
|
||||
[数据库] 1 EpicScript + 1 Conversation + 3 Message
|
||||
```
|
||||
|
||||
### 2.2 已识别的根因
|
||||
|
||||
| # | 严重度 | 根因 | 影响 |
|
||||
|---|------|------|------|
|
||||
| 1 | 🔴 高 | `saveNovelResult` 缺少 `@Transactional` 注解 | 6 次写操作无原子性,中途失败留下脏数据 |
|
||||
| 2 | 🔴 高 | DDL 与 Entity 不一致:`t_epic_script` 缺 `conversation_id`/`current_version_message_id` 列;`t_message` 缺 `script_id` 列 | INSERT/UPDATE 失败,异常被吞,导致数据丢失 |
|
||||
| 3 | 🔴 高 | `originalQuery` 为空时 `saveNovelResult` 直接返回不保存 | followup 阶段生成的小说永远不会落库 |
|
||||
| 4 | 🔴 高 | 中间交互(澄清卡片、大纲确认)没有持久化为消息 | 详情页无法复现全流程 |
|
||||
| 5 | 🟡 中 | `saveNovelResult` 写死 `length="short"` / `style="career"` | 列表按 length/style 过滤时看不到 |
|
||||
| 6 | 🟡 中 | 列表查询 `getListByCurrentUser` 触发隐性写操作(`ensureConversationForScript`) | 读路径产生写操作,违反分层原则 |
|
||||
| 7 | 🟡 中 | `create_time` 排序依赖 `MetaObjectHandler`,若失效则 NULL 排序异常 | 新记录可能排到末尾 |
|
||||
| 8 | 🟡 中 | 前端 `novel_done` 后 `fetchScripts()` 刷新不可靠(未 await) | 列表可能不包含最新记录 |
|
||||
|
||||
## 3. 目标架构(TO-BE)
|
||||
|
||||
### 3.1 核心策略
|
||||
|
||||
**实时分阶段保存 + 最终补全:**
|
||||
- 每轮交互(澄清回答、大纲确认)完成后立即保存为消息
|
||||
- `novel_done` 时补全最终的小说正文消息 + 更新 script 元数据
|
||||
- 每个保存操作独立事务,互不影响
|
||||
|
||||
### 3.2 完整消息流程
|
||||
|
||||
| message_order | sender | type | 触发时机 | content |
|
||||
|---|---|---|---|---|
|
||||
| 1 | system | system | INIT | 欢迎消息 |
|
||||
| 2 | user | chat | INIT | 用户心愿原文(firstQuery) |
|
||||
| 3 | assistant | clarification | CLARIFICATION | AI 澄清卡片(metadata=card JSON) |
|
||||
| 4 | user | chat | CLARIFICATION_ANSWER | 用户对澄清的回答 |
|
||||
| 5 | assistant | outline | OUTLINE | AI 大纲(metadata=outline JSON) |
|
||||
| 6 | user | chat | OUTLINE_CONFIRM | 用户确认/修改 |
|
||||
| 7 | assistant | script | NOVEL_DONE | 完整小说正文(currentVersionMessageId) |
|
||||
| 8+ | user/assistant | chat | 后续对话 | 修改历史 |
|
||||
|
||||
## 4. 数据模型修复
|
||||
|
||||
### 4.1 DDL 补全(SQL migration)
|
||||
|
||||
```sql
|
||||
-- 补 t_epic_script 缺失列
|
||||
ALTER TABLE t_epic_script
|
||||
ADD COLUMN conversation_id BIGINT NULL COMMENT '关联对话ID',
|
||||
ADD COLUMN current_version_message_id BIGINT NULL COMMENT '当前版本消息ID';
|
||||
|
||||
-- 补 t_message 缺失列
|
||||
ALTER TABLE t_message
|
||||
ADD COLUMN script_id BIGINT NULL COMMENT '关联剧本ID(冗余反查)';
|
||||
|
||||
-- 补 t_conversation 缺失列(如果需要)
|
||||
-- ALTER TABLE t_conversation ADD COLUMN current_version_message_id BIGINT NULL;
|
||||
```
|
||||
|
||||
### 4.2 Entity 校验
|
||||
|
||||
确认 `EpicScript.java`、`Message.java`、`Conversation.java` 的字段与 DDL 一致:
|
||||
- `EpicScript.conversationId` → `t_epic_script.conversation_id`
|
||||
- `EpicScript.currentVersionMessageId` → `t_epic_script.current_version_message_id`
|
||||
- `Message.scriptId` → `t_message.script_id`
|
||||
|
||||
### 4.3 Message.type 枚举扩展
|
||||
|
||||
新增 message type 值:
|
||||
- `clarification` — AI 澄清卡片
|
||||
- `outline` — AI 大纲
|
||||
- `script` — 完整小说正文(已有)
|
||||
- `chat` — 普通对话(已有)
|
||||
- `system` — 系统消息(已有)
|
||||
|
||||
## 5. 后端修复
|
||||
|
||||
### 5.1 新增 `NovelSessionService`
|
||||
|
||||
负责在 Redis 中缓存 `sessionId → sessionMeta` 的映射:
|
||||
- `originalQuery`(用户首次输入的心愿原文)
|
||||
- `style`、`length`
|
||||
- `conversationId`、`scriptId`、`userId`
|
||||
|
||||
**目的:** 保证 followup 流程中即使前端没传 `originalQuery`,也能从 session 恢复。
|
||||
|
||||
### 5.2 重构 `saveNovelResult` → 分阶段保存
|
||||
|
||||
#### 5.2.1 新方法签名
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class NovelStageService {
|
||||
|
||||
/**
|
||||
* 分阶段保存小说生成过程
|
||||
* @param userId 当前用户ID
|
||||
* @param sessionId 外部 short-novel-service 返回的 session_id
|
||||
* @param stage 当前阶段
|
||||
* @param stageData 阶段数据
|
||||
*/
|
||||
public StageSaveResult saveStage(Long userId, String sessionId, NovelStage stage, Map<String, Object> stageData);
|
||||
}
|
||||
|
||||
public enum NovelStage {
|
||||
INIT, // 首次发起,初始化 script + conversation + 首条 user 消息
|
||||
CLARIFICATION, // AI 返回澄清卡片
|
||||
CLARIFICATION_ANSWER, // 用户回答澄清
|
||||
OUTLINE, // AI 返回大纲
|
||||
OUTLINE_CONFIRM, // 用户确认/修改大纲
|
||||
NOVEL_DONE // AI 返回完整小说正文(最终补全)
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.2.2 每个 stage 的保存动作
|
||||
|
||||
**INIT:**
|
||||
- 创建 `EpicScript`(id=雪花ID, userId, title="生成中...", theme=originalQuery, style, length, plotJson={})
|
||||
- 创建 `Conversation`(id=雪花ID, userId, userType="registered", scriptId, title, type="script", status="active")
|
||||
- 创建 Message 1(system 欢迎)
|
||||
- 创建 Message 2(user 心愿原文)
|
||||
- 更新 EpicScript.conversationId
|
||||
- 缓存 sessionMeta 到 Redis
|
||||
- 返回 `{scriptId, conversationId}`
|
||||
|
||||
**CLARIFICATION:**
|
||||
- 创建 Message 3(assistant, type=clarification, content="(见 metadata)", metadata=card JSON)
|
||||
- 返回 `{messageId: 3}`
|
||||
|
||||
**CLARIFICATION_ANSWER:**
|
||||
- 创建 Message 4(user, type=chat, content=answer)
|
||||
- 返回 `{messageId: 4}`
|
||||
|
||||
**OUTLINE:**
|
||||
- 创建 Message 5(assistant, type=outline, content="(见 metadata)", metadata=outline JSON)
|
||||
- 更新 EpicScript.plotJson.outline = outline 数据
|
||||
- 返回 `{messageId: 5}`
|
||||
|
||||
**OUTLINE_CONFIRM:**
|
||||
- 创建 Message 6(user, type=chat, content=feedback/confirmation)
|
||||
- 返回 `{messageId: 6}`
|
||||
|
||||
**NOVEL_DONE(最终补全):**
|
||||
- 创建 Message 7(assistant, type=script, content=fullText, versionNumber=1, metadata=完整正文+章节 JSON)
|
||||
- 更新 EpicScript.title = outline.title 或生成默认标题
|
||||
- 更新 EpicScript.currentVersionMessageId = message 7 的 id
|
||||
- 更新 Conversation.currentMessageId = message 7 的 id
|
||||
- 返回 `{messageId: 7, scriptId, conversationId}`
|
||||
|
||||
#### 5.2.3 事务保证
|
||||
|
||||
每个 stage 独立事务(`@Transactional(rollbackFor = Exception.class)`):
|
||||
- 单个 stage 失败不影响其他 stage
|
||||
- 已保存的中间数据不会丢失
|
||||
- 失败的 stage 可通过 retry 重试
|
||||
|
||||
### 5.3 修复 `originalQuery` fallback
|
||||
|
||||
```java
|
||||
// ShortNovelServiceImpl.followup() 中
|
||||
String originalQuery = request.getOriginalQuery();
|
||||
if (StringUtils.isBlank(originalQuery)) {
|
||||
originalQuery = novelSessionService.getOriginalQuery(sessionId);
|
||||
}
|
||||
if (StringUtils.isBlank(originalQuery)) {
|
||||
throw new BizException("originalQuery 不能为空且无法从 session 恢复");
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 修复 length/style 硬编码
|
||||
|
||||
- `ShortNovelStreamRequest` 增加 `style`、`length` 字段
|
||||
- `ShortNovelFollowupRequest` 增加 `style`、`length` 字段
|
||||
- 前端在首次发起时传 style/length,后端透传给外部服务
|
||||
- `saveNovelResult` 不再写死 `length="short"` / `style="career"`
|
||||
|
||||
### 5.5 列表查询修复
|
||||
|
||||
#### 5.5.1 `getListByCurrentUser` 修复
|
||||
|
||||
```java
|
||||
public List<EpicScript> getListByCurrentUser(EpicScriptListRequest request) {
|
||||
Long currentUserId = UserContextHolder.getCurrentUserId();
|
||||
LambdaQueryWrapper<EpicScript> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(EpicScript::getUserId, currentUserId)
|
||||
.eq(EpicScript::getIsDeleted, 0);
|
||||
|
||||
// 只在显式传入时才过滤
|
||||
if (StringUtils.isNotBlank(request.getStyle())) {
|
||||
wrapper.eq(EpicScript::getStyle, request.getStyle());
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getLength())) {
|
||||
wrapper.eq(EpicScript::getLength, request.getLength());
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getKeyword())) {
|
||||
wrapper.and(w -> w.like(EpicScript::getTitle, request.getKeyword())
|
||||
.or().like(EpicScript::getTheme, request.getKeyword()));
|
||||
}
|
||||
|
||||
// create_time 倒序,NULL 排最后
|
||||
wrapper.orderByDesc(EpicScript::getCreateTime);
|
||||
wrapper.last("NULLS LAST"); // 或在 Mapper 中使用 coalesce
|
||||
|
||||
return epicScriptMapper.selectList(wrapper);
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.5.2 移除隐性写操作
|
||||
|
||||
```java
|
||||
// 移除 ensureConversationForScript 中的写操作
|
||||
// 仅查询 script,不在列表查询时补建 conversation
|
||||
```
|
||||
|
||||
#### 5.5.3 校验 `MetaObjectHandler`
|
||||
|
||||
确认 `MybatisMetaObjectHandler` 正确填充 `createTime`/`updateTime`:
|
||||
- 若 handler 缺失或失效,需要补全实现
|
||||
- 字段上加 `@TableField(fill = FieldFill.INSERT)`
|
||||
|
||||
## 6. 前端修复
|
||||
|
||||
### 6.1 状态管理增强
|
||||
|
||||
`stores/app.js` 新增字段:
|
||||
- `currentSessionMeta: { sessionId, scriptId, conversationId, originalQuery, style, length }`
|
||||
- 提供 setter `setSessionMeta(meta)` 和 getter `getSessionMeta()`
|
||||
|
||||
### 6.2 ScriptView.vue 修复
|
||||
|
||||
#### 6.2.1 原始参数持久化
|
||||
|
||||
```javascript
|
||||
const runGeneration = async ({ prompt, source }) => {
|
||||
// 首次发起前,保存原始参数
|
||||
store.setSessionMeta({
|
||||
originalQuery: prompt,
|
||||
style: userStyle.value,
|
||||
length: userLength.value,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
|
||||
// 启动 SSE 流
|
||||
await startNovelStream({ query: prompt, style: ..., length: ..., onEvent: handleShortNovelEvent })
|
||||
}
|
||||
```
|
||||
|
||||
#### 6.2.2 可靠刷新列表
|
||||
|
||||
```javascript
|
||||
const handleNovelDone = async (payload) => {
|
||||
scriptId.value = payload.scriptId
|
||||
conversationId.value = payload.conversationId
|
||||
|
||||
// await 确保刷新完成
|
||||
try {
|
||||
await store.fetchScripts()
|
||||
} catch (e) {
|
||||
console.error('剧本列表刷新失败:', e)
|
||||
// 显示重试按钮
|
||||
}
|
||||
|
||||
isGenerating.value = false
|
||||
}
|
||||
```
|
||||
|
||||
#### 6.2.3 followup 时 originalQuery 兜底
|
||||
|
||||
```javascript
|
||||
const submitClarification = async (answer) => {
|
||||
const meta = store.getSessionMeta()
|
||||
if (!meta?.originalQuery) {
|
||||
uni.showToast({ title: '原始心愿已丢失,请重新开始', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
await followupStream({
|
||||
sessionId: meta.sessionId,
|
||||
action: 'answer_clarification',
|
||||
payload: { answer },
|
||||
originalQuery: meta.originalQuery,
|
||||
style: meta.style,
|
||||
length: meta.length,
|
||||
onEvent: handleShortNovelEvent
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### 6.2.4 错误处理增强
|
||||
|
||||
```javascript
|
||||
const handleNovelError = (error) => {
|
||||
isGenerating.value = false
|
||||
// 显示明确错误提示
|
||||
uni.showModal({
|
||||
title: '生成失败',
|
||||
content: error.message || '请稍后重试',
|
||||
showCancel: false,
|
||||
confirmText: '知道了'
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 ScriptLibraryView.vue 修复
|
||||
|
||||
#### 6.3.1 默认不传 length/style 过滤
|
||||
|
||||
```javascript
|
||||
onMounted(async () => {
|
||||
await store.fetchScripts() // 默认查询全部
|
||||
})
|
||||
```
|
||||
|
||||
#### 6.3.2 列表项展示优化
|
||||
|
||||
```vue
|
||||
<view class="script-card">
|
||||
<view class="script-title">{{ script.title }}</view>
|
||||
<view class="script-meta">
|
||||
<text class="tag-style">{{ script.style }}</text>
|
||||
<text class="tag-length">{{ script.length }}</text>
|
||||
<text class="word-count">{{ script.wordCount }}字</text>
|
||||
</view>
|
||||
<view class="script-time">{{ formatDate(script.createTime) }}</view>
|
||||
</view>
|
||||
```
|
||||
|
||||
### 6.4 ScriptDetailView.vue 增强
|
||||
|
||||
进入详情页加载所有消息,按 message_order 顺序完整复现:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view class="script-detail">
|
||||
<!-- 1. 用户心愿原文 -->
|
||||
<view v-if="userWishMessage" class="message-item user">
|
||||
<view class="message-label">💭 用户心愿</view>
|
||||
<view class="message-content">{{ userWishMessage.content }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 2. AI 澄清卡片 -->
|
||||
<view v-if="clarificationMessage" class="message-item assistant">
|
||||
<view class="message-label">🤔 AI 澄清</view>
|
||||
<ClarificationCard :card="clarificationMessage.metadata" readonly />
|
||||
</view>
|
||||
|
||||
<!-- 3. 用户对澄清的回答 -->
|
||||
<view v-if="clarificationAnswerMessage" class="message-item user">
|
||||
<view class="message-label">💬 澄清回答</view>
|
||||
<view class="message-content">{{ clarificationAnswerMessage.content }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 4. AI 大纲 -->
|
||||
<view v-if="outlineMessage" class="message-item assistant">
|
||||
<view class="message-label">📋 大纲</view>
|
||||
<OutlineView :outline="outlineMessage.metadata" />
|
||||
</view>
|
||||
|
||||
<!-- 5. 用户确认/修改 -->
|
||||
<view v-if="outlineConfirmMessage" class="message-item user">
|
||||
<view class="message-label">✏️ 大纲确认</view>
|
||||
<view class="message-content">{{ outlineConfirmMessage.content }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 6. 完整小说正文 -->
|
||||
<view v-if="scriptMessage" class="message-item assistant novel">
|
||||
<view class="message-label">📖 小说正文</view>
|
||||
<Markdown :content="scriptMessage.content" />
|
||||
</view>
|
||||
|
||||
<!-- 7. 后续对话修改历史 -->
|
||||
<view v-for="msg in chatHistoryMessages" :key="msg.id" class="message-item">
|
||||
...
|
||||
</view>
|
||||
|
||||
<!-- 继续修改按钮 -->
|
||||
<button @click="continueModify">继续修改</button>
|
||||
</view>
|
||||
</template>
|
||||
```
|
||||
|
||||
```javascript
|
||||
const loadFullScript = async (scriptId) => {
|
||||
const script = store.getScriptById(scriptId) || (await store.fetchScripts(), store.getScriptById(scriptId))
|
||||
if (!script?.conversationId) return
|
||||
|
||||
const res = await listMessagesByConversation({
|
||||
conversationId: script.conversationId,
|
||||
includeVersions: false
|
||||
})
|
||||
const messages = (res.data || []).sort((a, b) => a.messageOrder - b.messageOrder)
|
||||
|
||||
userWishMessage.value = messages.find(m => m.messageOrder === 2)
|
||||
clarificationMessage.value = messages.find(m => m.messageOrder === 3)
|
||||
clarificationAnswerMessage.value = messages.find(m => m.messageOrder === 4)
|
||||
outlineMessage.value = messages.find(m => m.messageOrder === 5)
|
||||
outlineConfirmMessage.value = messages.find(m => m.messageOrder === 6)
|
||||
scriptMessage.value = messages.find(m => m.messageOrder === 7)
|
||||
chatHistoryMessages.value = messages.filter(m => m.messageOrder > 7)
|
||||
}
|
||||
```
|
||||
|
||||
## 7. 修改文件清单
|
||||
|
||||
### 后端新增文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `service/NovelStageService.java` | 分阶段保存服务 |
|
||||
| `service/impl/NovelStageServiceImpl.java` | 分阶段保存实现 |
|
||||
| `service/NovelSessionService.java` | session 元数据缓存 |
|
||||
| `service/impl/NovelSessionServiceImpl.java` | Redis 缓存实现 |
|
||||
| `enums/NovelStage.java` | 阶段枚举 |
|
||||
|
||||
### 后端修改文件
|
||||
|
||||
| 文件 | 修改内容 |
|
||||
|------|---------|
|
||||
| `entity/EpicScript.java` | 确认 conversationId/currentVersionMessageId 字段映射 |
|
||||
| `entity/Message.java` | 确认 scriptId 字段映射 + 扩展 type 枚举 |
|
||||
| `service/impl/EpicScriptDialogueServiceImpl.java` | 重构 saveNovelResult 为分阶段保存 |
|
||||
| `service/impl/ShortNovelServiceImpl.java` | 拦截所有 stage 事件,调用 saveStage |
|
||||
| `service/impl/EpicScriptServiceImpl.java` | 修复 getListByCurrentUser 过滤逻辑 + 移除隐性写 |
|
||||
| `dto/request/ShortNovelStreamRequest.java` | 增加 style/length 字段 |
|
||||
| `dto/request/ShortNovelFollowupRequest.java` | 增加 style/length 字段 |
|
||||
| `sql/migration/2026-07-26-novel-save-completion.sql` | DDL 补列 |
|
||||
|
||||
### 前端修改文件
|
||||
|
||||
| 文件 | 修改内容 |
|
||||
|------|---------|
|
||||
| `stores/app.js` | 增加 currentSessionMeta 字段 + setter/getter |
|
||||
| `pages/main/ScriptView.vue` | 增强 originalQuery 持久化、可靠刷新、错误处理 |
|
||||
| `pages/main/ScriptLibraryView.vue` | 默认不传过滤参数 + 列表项展示优化 |
|
||||
| `pages/main/ScriptDetailView.vue` | 完整复现全流程消息 |
|
||||
|
||||
## 8. 验收标准(强制)
|
||||
|
||||
### 8.1 验收前置条件
|
||||
|
||||
- ✅ 后端代码 `mvn clean install -DskipTests` 编译通过
|
||||
- ✅ 后端 `python deploy-remote.py backend` 部署成功,服务器无报错
|
||||
- ✅ 小程序 H5 模式启动成功(`http://localhost:5180`)
|
||||
|
||||
### 8.2 端到端验收流程(H5 浏览器)
|
||||
|
||||
#### 场景 1:完整流程生成小说
|
||||
|
||||
1. 打开 `http://localhost:5180`
|
||||
2. 进入"心愿实现"页面
|
||||
3. 输入心愿文本(如"我想成为一个优秀的产品经理")
|
||||
4. 点击"生成"按钮
|
||||
5. **验证:** AI 弹出澄清卡片,UI 显示"已保存"状态
|
||||
6. 选择/输入澄清回答
|
||||
7. **验证:** 后端数据库 t_message 新增 message_order=4 的 user 消息
|
||||
8. AI 生成大纲
|
||||
9. **验证:** 后端数据库 t_message 新增 message_order=5 的 assistant 消息 + EpicScript.plotJson.outline 更新
|
||||
10. 确认大纲
|
||||
11. **验证:** 后端数据库 t_message 新增 message_order=6 的 user 消息
|
||||
12. AI 流式生成小说正文
|
||||
13. **验证:** 完成后 t_message 新增 message_order=7 的 assistant 消息(type=script)
|
||||
14. **验证:** EpicScript.currentVersionMessageId 已回写到 message 7 的 id
|
||||
15. 进入"剧本"(library)列表
|
||||
16. **验证:** 列表显示刚刚生成的小说
|
||||
17. 点击该小说进入详情
|
||||
18. **验证:** 详情页按顺序显示:用户心愿 → 澄清卡片 → 澄清回答 → 大纲 → 大纲确认 → 完整小说正文
|
||||
19. **验证:** 浏览器 Console 无错误
|
||||
20. **验证:** Network 面板所有 API 返回 200
|
||||
|
||||
#### 场景 2:中途刷新页面(断点恢复)
|
||||
|
||||
1. 走到澄清回答后,刷新页面
|
||||
2. **验证:** 重新进入"心愿实现"页面,状态恢复(不要求完全恢复,但要能查到已保存的消息)
|
||||
|
||||
#### 场景 3:多次生成小说
|
||||
|
||||
1. 连续生成 3 个不同心愿的小说
|
||||
2. **验证:** 列表按时间倒序显示 3 条记录
|
||||
3. **验证:** 每条记录详情页完整复现
|
||||
|
||||
#### 场景 4:对话修改流程
|
||||
|
||||
1. 在剧本 chat 视图输入修改意见
|
||||
2. **验证:** 后端保存消息
|
||||
3. **验证:** 列表和详情同步更新
|
||||
|
||||
### 8.3 通过标准
|
||||
|
||||
- ✅ 所有场景 1-4 通过
|
||||
- ✅ 浏览器 Console 无任何新增错误
|
||||
- ✅ Network 面板所有 API 返回 200 或业务预期状态码
|
||||
- ✅ 服务器端日志无 ERROR
|
||||
- ✅ H5 页面完整验收通过才可标记任务完成
|
||||
|
||||
### 8.4 不通过处理
|
||||
|
||||
任何场景不通过都必须立即停止,修复后重新部署、重新验收,禁止"部分完成"标记。
|
||||
|
||||
## 9. 不在本次范围内
|
||||
|
||||
- 外部 short-novel-service 接口调整
|
||||
- AI 生成质量优化
|
||||
- 收藏、分享等附加功能
|
||||
- 真实的语音朗读、语音识别集成
|
||||
- 多端(小程序非 H5 模式)适配(H5 已能覆盖小程序核心功能)
|
||||
Reference in New Issue
Block a user