# Book 43 卡死问题 & 5 层兜底方案 > 2026-07-04 记录 > 触发问题:`https://book.rrbrr.com/#/pages/book-generator/detail?id=43` 显示"生成中"实际无任何进度,3 天未推进。 ## 现象 - 用户访问 book 43 详情页:进度永远 5%,状态"大纲完成",无法继续 - 前端轮询 + stalledTip 兜底都触发过,但用户没看到任何"请重新生成"按钮 - 后端日志:book 43 自 7/2 09:51 之后无任何 LangGraph/TTS 活动 ## 根因(深度排查后) 1. **interactive 流程未闭环**:用户用 interactive 模式改完大纲(`POST /interactive/outline`),但前端没引导也没暴露"开始生成内容"按钮,导致 `POST /interactive/generate` 从未被调用,book 永远停在 `genStage='outline_ready'`(不在前端合法枚举里) 2. **LangGraph rich_outline 节点 720s 超时**:book-recovery-scanner 触发 LangGraph 全量重跑时,rich_outline 节点 12 分钟不够(25 章大书),再次失败 3. **章节状态不一致**:book 字段已 `content_completed`,但 BookChapter 仍是 `outline_completed`,`computeBookGenStage` 推断前端还是"大纲阶段" 4. **核心业务测试超时设太紧**:poll.ts 默认 180s,生产 LangGraph 跑 1000 字实测 3 分 48 秒,CORE-3 误报失败 ## 改动总览(5 层兜底 + 1 个 100% 兜底) ### 1. `nodes/rich-outline.node.ts` — rich_outline 100% 兜底 catch 块包内层 try-catch,跑三级 fallback: ``` [RichOutline] 富信息大纲生成失败 → 进入 100% 成功兜底 ├─ fallback 1: 复用已有 outlineJson(之前生成过、没丢就用) ├─ fallback 2: 调 generateOutline 简版 LLM(prompt 小、稳) └─ fallback 3: buildSkeletonOutline() 本地骨架大纲(0 LLM 调用,100% 必成功) │ └─ 无论如何:outlineJson + 章节记录必落库 ``` **强压力测试通过**:mock 让所有 LLM 调用必失败 10 次,rich_outline 仍能兜底成功(outlineJson 长度 3791 字节,10 章骨架正常入库)。 ### 2. `book-recovery-scanner.ts` — book 级别自愈扫描器 - 每 60s 扫一次 - 触发条件(任一): - `outline_ready` 超 30min 未动 - `content_generating` 超 60min 无章节推进 - **书标记已完成但仍有章节漏内容**(新增场景 3) - **不再触发 LangGraph 全量重跑**,改用并发单节补全(`fillMissingChapters`,3 worker pool) - 每轮最多 3 本书,避免 LLM 配额爆 - 失败回写 `failed`,不出现永远卡住的状态 ### 3. `audio-scanner.ts` — 章节级状态不一致修复 新增 step 3c:每 30s 扫所有 `genStage=outline_completed` 但 `content/audioUrl` 实际已就绪的章节,自动推进到正确状态。 ### 4. `langgraph-controller.ts` — fill-missing 接口 `POST /api/book-generator/langgraph/books/:id/fill-missing` 前端按钮可手动触发;book-recovery-scanner 也用同一函数。找不到缺失章节返回 `missingCount=0`。 ### 5. `detail.vue` — 前端兜底 - `BookGenStage` 枚举补 `outline_generating / outline_ready / outline_failed / generating_content` - `outline_ready` 状态显示橙色 "📋 大纲已生成" + 🚀 按钮调 `/interactive/generate` - `stalledTip` 块加 "⚡ 补全缺失章节" 按钮 - **始终可用的兜底块**:任何时候只要检测到章节内容缺失(`hasMissingChapters`)就显示蓝色 "🩹 检测到 N 个章节未生成内容" + 调 `/fill-missing` - `outlineReadyPolling` 30s 降频轮询,检测到后端自愈推进自动切回正常轮询 ### 6. `poll.ts` — 测试超时放宽 180s → 300s(生产 LangGraph 跑 1000 字实测 3 分 48 秒 + 1 分钟余量)。 ## 验证证据 | 场景 | 实测 | |---|---| | book 43 真实状态 | `audio_completed` + 100% + 72/25 章 + 0 缺失 | | fill-missing 接口 | `{"code":0,"message":"书籍已完整","missingCount":0}` | | rich_outline 强压力测试 | LLM mock 必失败 → 兜底3 成功,outlineJson 必落库 | | 新建书 1000 字 LangGraph 全链路 | 3 分 48 秒从创建到 `content_completed` + 100% | ## 端到端演示命令 ```bash # 1. 登录拿 token curl -X POST https://bookapi.rrbrr.com/api/auth/login \ -H "Content-Type: application/json" \ -d '{"phone":"13xxx","code":"123456"}' # 2. 创建书触发 fast-path curl -X POST https://bookapi.rrbrr.com/api/book-generator/langgraph/books \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"title":"测试","description":"...","bookScale":"1000","autoGenerateContent":true,"autoGenerateAudio":true}' # 3. 手动补全缺失章节(任何时候) curl -X POST https://bookapi.rrbrr.com/api/book-generator/langgraph/books//fill-missing \ -H "Authorization: Bearer " ``` ## 教训 1. **后端兜底不能依赖 LangGraph**:LangGraph 有超时(rich_outline 720s),超时后默认 catch 块只标 failed,会触发循环。**自愈路径必须用单节补全,不要重跑全量**。 2. **前端必须识别所有后端状态值**:`genStage` 枚举要包含 `outline_ready` 等 intermediate 值,否则用户看到的永远是"中间状态"。 3. **章节状态是真相,book 字段只是 hint**:`computeBookGenStage` 用 chapter 推断时,要确保 BookChapter 状态被同步推进;否则写一个 `audio-scanner` 兜底修复。 4. **测试超时反映真实环境**:180s 在生产不够,要按 99 分位数 + 1 分钟余量设。 5. **关键接口必须给前端兜底入口**:fill-missing 接口 + 按钮,让用户在自动兜底失败时能手动触发,避免"卡死无人能救"。 ## 监控建议(待做) - 定时检查"任何书 genStage 在中间态超过 2 小时"主动报警(钉钉/邮件) - book-recovery-scanner 每次扫描输出统计摘要 - 给 rich_outline 节点的 fallback 触发次数加 metric