From 99a3cb883e400684293beb8d8134ba71e6f9f8c8 Mon Sep 17 00:00:00 2001 From: Peanut Date: Sun, 19 Jul 2026 11:52:41 +0800 Subject: [PATCH] =?UTF-8?q?docs=EF=BC=9A=E7=9F=AD=E7=AF=87=E5=B0=8F?= =?UTF-8?q?=E8=AF=B4=E6=9C=8D=E5=8A=A1=E6=8E=A5=E5=8F=A3=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端 SSE 代理 + 前端多轮交互状态机改造 - 外部 short-novel-service 接口替代内部 AI Runtime - 数据持久化改为小说完成后一次性保存 - 短信验证码修复(888888→123456) --- ...19-short-novel-service-migration-design.md | 239 ++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-19-short-novel-service-migration-design.md diff --git a/docs/superpowers/specs/2026-07-19-short-novel-service-migration-design.md b/docs/superpowers/specs/2026-07-19-short-novel-service-migration-design.md new file mode 100644 index 0000000..f538538 --- /dev/null +++ b/docs/superpowers/specs/2026-07-19-short-novel-service-migration-design.md @@ -0,0 +1,239 @@ +--- +author: AI Assistant +created_at: 2026-07-19 +purpose: 小程序心愿实现页面的短篇小说生成接口从内部 AI Runtime 迁移到外部 short-novel-service 的设计方案 +--- + +# 短篇小说服务接口迁移设计 + +## 概述 + +将小程序"心愿实现"页面(ScriptView.vue)的短篇小说生成流程,从当前内部 AI Runtime 同步调用模式,迁移到外部 short-novel-service(`http://49.232.138.53:8010`)的流式 SSE 多轮交互模式。同时开启小程序的手机号短信登录功能(验证码固定 123456)。 + +## 架构决策 + +- **架构模式**:后端 SSE 代理 + 前端完整多轮交互适配 +- **持久化策略**:小说生成完成后一次性保存(中间交互阶段不持久化) +- **认证方式**:外部服务 API Token 保存在后端配置中,前端不接触 + +## 当前架构(AS-IS) + +### 前端流程 + +``` +ScriptView.vue + → submitWish() → runGeneration() + → createScriptWithDialogue(payload) // POST /epicScript/createWithDialogue + → 返回 { scriptId, conversationId, title, plotIntro, plotTurning, plotClimax, plotEnding } + → loadMessages() + → viewState = 'result' +``` + +### 后端流程 + +``` +EpicScriptController.createWithDialogue() + → EpicScriptDialogueServiceImpl.createWithDialogue() + → 创建 EpicScript + Conversation 记录 + → invokeScriptGenerate() → aiRuntimeService.test("script_generate") // 同步 AI 调用 + → parseScriptOutput() → 解析 JSON 输出 + → 创建 3 条 Message(系统欢迎、用户输入、AI 回复) + → 返回 EpicScriptCreateWithDialogueResponse +``` + +### 当前接口特征 + +- 同步调用,一次性返回完整结果 +- AI 输出为 JSON 格式(title、plotIntro、plotTurning、plotClimax、plotEnding) +- 前端无中间交互阶段 + +## 目标架构(TO-BE) + +### 前端新流程 + +``` +ScriptView.vue 状态机: + home → clarifying ⇄ outlining → novel-generating → result +``` + +#### 阶段详情 + +| 阶段 | 触发事件 | UI 行为 | 用户操作 | +|---|---|---|---| +| `home` | 用户输入心愿 | 展示输入框+灵感推荐+语音按钮 | 输入文本/语音,点击发送 | +| `clarifying` | `clarification_card` SSE 事件 | 展示卡片式问题 | 选择选项或输入文本回答 | +| `outlining` | `outline_created` SSE 事件 | 展示大纲(标题/logline/beats/结局) | 确认大纲 或 提出修改意见 | +| `novel-generating` | `novel_start` SSE 事件 | 逐字流式展示小说正文 | 等待生成完成 | +| `result` | `novel_done` SSE 事件 | 展示完成的小说+后续对话 | 继续对话修改/朗读/分享 | + +#### 澄清卡片类型 + +| card_type | UI 渲染 | 交互方式 | +|---|---|---| +| `single_select` | 单选按钮组 | 选择一个选项 | +| `multi_select` | 多选标签组 | 选择多个选项 | +| `text_input` | 文本输入框 | 自由输入 | +| `mixed` | 选项 + 输入框 | 选择或自由输入 | + +### 后端新架构 + +#### 新增 Controller + +```java +@RestController +@RequestMapping("/shortNovel") +public class ShortNovelController { + + // 首次发起(SSE 流式代理) + @PostMapping("/stream") + public SseEmitter stream(@RequestBody ShortNovelStreamRequest request); + + // 后续轮次(SSE 流式代理) + @PostMapping("/followup") + public SseEmitter followup(@RequestBody ShortNovelFollowupRequest request); +} +``` + +#### 请求/响应格式 + +**POST /shortNovel/stream** + +入参: +```json +{ "query": "用户的心愿文本" } +``` + +后端转发到外部服务: +```json +{ + "user_id": "当前登录用户ID", + "message_id": "web_{timestamp}", + "query": "用户的心愿文本" +} +``` + +**POST /shortNovel/followup** + +入参: +```json +{ + "sessionId": "外部服务返回的 session_id", + "action": "answer_clarification | confirm_outline | modify_outline | retry", + "payload": { + "answer": "用户回答(answer_clarification 时)", + "feedback": "修改意见(modify_outline 时)" + } +} +``` + +后端转发到外部服务: +```json +{ + "session_id": "sessionId", + "user_id": "当前登录用户ID", + "message_id": "web_{timestamp}", + "action": "answer_clarification | confirm_outline | modify_outline | retry", + "payload": { ... } +} +``` + +#### SSE 事件格式(透传) + +外部服务返回的 SSE 事件格式: +``` +data: {"type":"status","session_id":"xxx","payload":{"message":"处理中","stage":"clarification"}} + +data: {"type":"clarification_card","session_id":"xxx","payload":{"card":{"card_type":"single_select","question":"...","options":[...]}}} + +data: {"type":"outline_created","session_id":"xxx","payload":{"outline":{"title":"...","beats":[...]}}} + +data: {"type":"novel_start","session_id":"xxx","payload":{}} + +data: {"type":"novel_delta","session_id":"xxx","payload":{"delta":"文本片段"}} + +data: {"type":"novel_done","session_id":"xxx","payload":{"full_text":"完整小说文本"}} + +data: {"type":"error","session_id":"xxx","payload":{"code":"ERROR_CODE","message":"错误描述"}} +``` + +后端 SseEmitter 代理逐行转发这些事件,不做任何修改。 + +#### SSE 代理实现 + +```java +// 使用 RestTemplate 读取外部服务 SSE 流 +// 逐行解析 data: 行,通过 SseEmitter.send() 转发 +// 超时设置 300 秒 +// 错误处理:连接失败、超时、外部服务错误 +``` + +#### 配置 + +```yaml +# application.yml +short-novel: + api-base-url: http://49.232.138.53:8010 + api-token: c67d4a95b0bb92470a24d534302c0d40 + connect-timeout: 10000 + read-timeout: 300000 +``` + +### 数据持久化 + +在收到 `novel_done` 事件后,后端执行一次性保存: + +1. 从 SSE 流中捕获 `novel_done` 事件的 `full_text` +2. 创建 `EpicScript` 记录 +3. 创建 `Conversation` 记录 +4. 创建 3 条 `Message` 记录(系统欢迎、用户初始输入、AI 小说全文) +5. 在最后一个 SSE 事件(`novel_done`)中附加 `scriptId` 和 `conversationId` + +实现方式:在 SSE 代理层拦截 `novel_done` 事件,在转发给前端之前执行保存逻辑,并将 ID 信息注入到事件的 payload 中。 + +### 短信验证码修复 + +#### 改动范围 + +| 文件 | 行号 | 当前值 | 修改为 | +|---|---|---|---| +| `mini-program/src/pages/login/index.vue` | 149 | `'验证码已发送(模拟: 888888)'` | `'验证码已发送(模拟: 123456)'` | + +后端已使用 `DEFAULT_SMS_CODE = "123456"`(AuthServiceImpl.java 第 80 行),无需修改。 + +## 前端新增/修改文件清单 + +| 文件 | 操作 | 说明 | +|---|---|---| +| `services/shortNovel.js` | 新增 | 短篇小说流式 API 服务 | +| `components/ClarificationCard.vue` | 新增 | 澄清卡片组件 | +| `pages/main/ScriptView.vue` | 修改 | 生成流程改为状态机,新增 clarifying/outlining/novel-generating 阶段 | + +## 后端新增/修改文件清单 + +| 文件 | 操作 | 说明 | +|---|---|---| +| `controller/ShortNovelController.java` | 新增 | SSE 代理接口 | +| `service/ShortNovelService.java` | 新增 | SSE 代理转发逻辑 | +| `service/impl/ShortNovelServiceImpl.java` | 新增 | SSE 代理实现 | +| `dto/request/ShortNovelStreamRequest.java` | 新增 | 首次请求 DTO | +| `dto/request/ShortNovelFollowupRequest.java` | 新增 | 后续轮次 DTO | +| `config/ShortNovelConfig.java` | 新增 | 配置类(api-base-url、api-token) | +| `application.yml` | 修改 | 新增 short-novel 配置段 | +| `EpicScriptDialogueServiceImpl.java` | 修改 | 抽取保存逻辑为可复用方法 | + +## 错误处理 + +| 场景 | 处理方式 | +|---|---| +| 外部服务不可达 | SSE 发送 `error` 事件,前端显示"服务暂时不可用" | +| 外部服务超时(>300s) | SseEmitter 超时回调,前端显示"生成超时" | +| 外部服务返回错误事件 | 透传 error 事件给前端 | +| 用户中途退出 | 前端关闭 SSE 连接,后端 SseEmitter 自动清理 | +| 小说生成失败 | 外部服务发送 `error` 事件,前端提供"重试"按钮 | + +## 不在本次范围内 + +- 外部服务的知识库图谱(knowledge-graph)功能 — 暂不集成 +- 外部服务的 prompt 自定义配置 — 使用外部服务默认配置 +- 历史对话的恢复/继续 — 已完成的历史记录按现有方式展示 +- 真实的短信发送服务 — 继续使用固定验证码 123456