baodan/docs/PPT与海报功能问题整改计划.md
wsb1224 e3479f0546 上线阻断问题全部修复
#	问题	修复	文件
1	前端构建失败(引号错误)	size="small type=" → size="small" type="	PosterHistoryPage.vue
2	migrate_014 ORM vs 缺失列	全部改为原始 SQL,不再引用 ORM 模型	migrate_014.py
3	cleanup 字段名错误	output_path → ppt_path	cleanup.py
4	文案生成 case 越权	添加 case.user_id != user_id 校验	poster/service.py
5	存储路径未接通持久化卷	全部改用 get_storage_root()(默认 /app/api/storage/insurance)	config.py, ppt/routes.py, poster/service.py, poster/tasks.py
高风险问题修复
#	问题	修复	文件
6	migrate_019 rollback 撤销成功字段	每个 ALTER 后立即 commit,失败只回滚当前语句	migrate_019.py
7	迁移锁 Windows 不兼容 + 句柄未持久化	全局变量保存锁句柄,支持 Windows msvcrt	api/insurance/db/__init__.py
8	PDF 校验异常时放行	异常返回 False(文件损坏)	security.py
9	健康检查始终返回成功	缺少关键资源时返回 503 + missing 列表	poster/routes.py
10	短密钥掩码泄露原值	≤4 字符返回 ****	ppt_admin_service.py
11	设置无键名白名单	添加 _ALLOWED_SETTING_KEYS 白名单	ppt_admin_service.py
12	容器重启任务永久 stuck	添加 recover_stale_tasks() 启动恢复函数	poster/tasks.py, ppt/parse_worker.py
2026-07-27 13:52:09 +08:00

1308 lines
43 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# PPT 与海报功能问题整改计划
> **文档版本**v1.0
> **编制日期**2026-07-27
> **适用范围**`api/insurance/ppt/`、`api/insurance/poster/`、PPT/海报管理后台、相关数据库迁移与部署配置
> **文档状态**:待评审
> **目标**:修复 PPT 和海报无法稳定生成的问题,打通保司、产品、模板、模型、文件和历史数据链路,并补齐安全与可运维能力
---
## 一、文档目的
当前 PPT 与海报模块已经具备页面、接口、数据库模型、迁移、LLM 调用和本地渲染代码,但各部分没有形成稳定闭环。用户表现为:
- PPT 上传或解析后无法生成;
- 海报没有可选产品或模板;
- 海报生成后无法预览、无法下载;
- 后台已经配置模型,但实际调用不生效;
- 保司、产品、模板和知识库数据之间没有完整关联;
- 不同部署环境、不同启动次数可能得到不同的数据状态。
本文档不是重新设计整个系统,而是基于现有代码做最小必要整改,按以下顺序处理:
1. 修复能够直接导致生成失败、数据泄露的 P0 问题;
2. 恢复 PPT、海报基础生成闭环
3. 将耗时操作接入 BaoDan 现有 Celery、Redis 和数据库;
4. 打通公共保司、产品、模板和知识库数据;
5. 补齐安全、持久化、监控、测试和发布回滚能力。
---
## 二、审计边界与结论说明
### 2.1 已检查范围
| 范围 | 主要文件 |
|------|---------|
| PPT 前端 | `frontend/src/pages/PptPage.vue`、`frontend/src/pages/components/ppt/` |
| 海报前端 | `frontend/src/pages/PosterPage.vue`、`frontend/src/components/poster/` |
| 前端 API | `frontend/src/utils/ppt-api.ts`、`poster-api.ts`、`api.ts` |
| PPT 后端 | `api/insurance/ppt/` |
| 海报后端 | `api/insurance/poster/` |
| 配置管理 | `api/insurance/admin/ppt_admin_routes.py`、`ppt_admin_service.py` |
| 数据模型 | `api/insurance/models/ppt_*`、`poster_*`、`system_setting.py` |
| 数据迁移 | `api/insurance/db/migrate_014.py``migrate_018.py` |
| 部署 | `Dockerfile.dify-custom`、`docker-compose.dify.yml`、`deploy/sql/init.sql` |
### 2.2 已执行验证
| 检查项 | 结果 |
|--------|------|
| Python 代码编译检查 | 通过 |
| Vue 前端生产构建 | 通过 |
| 当前源码环境 `python-pptx` | 未安装 |
| 当前源码环境 `PyMuPDF/fitz` | 未安装 |
| 静态保司配置 | 21 家 |
| 静态产品配置 | 7 个 |
| 静态 PPT 模板 | 7 个 |
| 海报模板种子数据 | 未发现 |
| 海报文案模板种子数据 | 未发现 |
| PPT/海报自动化测试 | 未发现覆盖生成闭环的测试 |
### 2.3 结论口径
- 本文标记为“已确认”的问题,均可由当前代码结构直接证明。
- 外部模型账号余额、生产数据库实际内容、生产容器日志尚未在本次静态审计中验证。
- 实施前仍需采集一次生产数据库结构、迁移历史、配置完整度和失败日志,避免覆盖现有有效数据。
---
## 三、现状调用链
### 3.1 PPT 当前调用链
```text
PptUpload
→ POST /insurance/ppt/upload
→ 保存 PDF + 创建 PptSession
→ POST /insurance/ppt/parse/{sessionId}
→ Web 进程后台线程解析 PDF
→ LLMClient 提取结构化数据
→ PptDataReview 人工核对
→ POST /insurance/ppt/generate/{sessionId}
→ normalizer + validator
→ PptRenderer
→ fast_pptx_renderer.py 子进程
→ 写入 downloads/{userId}/{sessionId}.pptx
→ 鉴权下载
```
主要断点:
- 源码环境缺依赖;
- 数据迁移顺序不稳定;
- `slidesConfig` 结构不一致;
- 前端风格与数据库模板不一致;
- 上传时选择的保司没有流转到生成阶段;
- 校验错误没有真正阻止生成;
- 后台线程不可恢复;
- 输出文件未持久化。
### 3.2 海报当前调用链
```text
选择 reviewed 产品
→ 上传计划书 PDF
→ 上传请求内同步调用 LLM 解析
→ 人工确认客户数据
→ 选择海报模板、文案模板
→ 模板填充或 LLM 生成文案
→ 图片模型生成
→ 失败时 Pillow 降级
→ 写入 uploads/posters
→ 普通 URL 预览和下载
```
主要断点:
- 新环境没有 reviewed 产品、海报模板和文案模板;
- 海报文案设置没有进入调用链;
- 多图片供应商只做了页面配置,没有协议适配;
- 上传解析和图片生成是同步请求,超过前端 60 秒超时;
- 生成文件预览、下载不携带 JWT
- 参考图没有从页面传到后端;
- 图片尺寸映射错误;
- 上传、结果文件未持久化。
---
## 四、问题总表
### 4.1 P0必须先修
| 编号 | 问题 | 影响 | 主要根因 |
|------|------|------|----------|
| PPT-P0-01 | `slidesConfig` 数据结构不一致 | 匹配到模板时渲染器可能直接 `KeyError` | 迁移写对象,渲染器和前端要求数组 |
| DB-P0-01 | 迁移顺序存在循环依赖 | 首次部署可能没有模板或字段 | 014 使用新版模型017 才创建新版字段 |
| DB-P0-02 | 迁移异常不回滚 | 一个字段冲突可导致后续迁移全部失败 | PostgreSQL 事务进入 aborted 后仍继续 |
| SEC-P0-01 | 任意登录用户可读取明文模型密钥 | 模型 API Key 泄露 | settings GET 仅要求 JWT数据库明文保存 |
| SEC-P0-02 | 任意登录用户可查看、导出全体 PPT 历史 | 用户数据、文件路径泄露 | 管理历史接口仅要求 JWT |
| SEC-P0-03 | 海报文案可读取其他用户的 case 数据 | 跨用户客户信息泄露 | `caseUploadId` 未校验所有权 |
| DEP-P0-01 | 源码与 Docker 依赖不一致 | 本地解析、PPT 渲染直接失败 | requirements 未声明全部生成依赖 |
| POSTER-P0-01 | 海报预览和下载不携带 JWT | 已生成图片仍显示失败 | 使用 `<img src>``window.open` |
| POSTER-P0-02 | 新环境没有完整海报基础数据 | 用户无法开始或无法进入下一步 | 无 reviewed 产品、海报/文案模板种子 |
| POSTER-P0-03 | 海报文案模型配置不生效 | 后台配置正确但实际调用错误模型 | 复用了只读取 `ppt_llm_*` 的客户端 |
| POSTER-P0-04 | 同步解析和生成超过请求超时 | 前端报失败,后台可能仍在执行 | 前端 60 秒LLM 180 秒Gunicorn 120 秒 |
| DATA-P0-01 | 生成和上传文件未持久化 | 容器重建后历史文件全部失效 | 文件写在 `/app/api/uploads`、`downloads` |
### 4.2 P1基础可用后立即处理
| 编号 | 问题 | 影响 |
|------|------|------|
| PPT-P1-01 | 前端风格与数据库模板风格不一致 | 多数模板配置永远查询不到 |
| PPT-P1-02 | 上传时选择的保司在后续丢失 | 生成使用错误保司或要求重复选择 |
| PPT-P1-03 | 公司知识库匹配没有进入生成主流程 | 公司介绍和证据数据为空 |
| PPT-P1-04 | 多产品只用第一份数据决定模板和保司 | 跨险种、跨保司组合错误 |
| PPT-P1-05 | validator 错误只记录日志,不阻止生成 | 可输出错误保费、利益数据 |
| POSTER-P1-01 | `poster_image_provider` 未参与协议选择 | 豆包、智谱配置名义可选但实际不可用 |
| POSTER-P1-02 | 图片响应只支持 `b64_json` | 返回 URL 的供应商生成失败 |
| POSTER-P1-03 | 管理员模板参考图未进入图片生成 | 模板参考图配置无效 |
| POSTER-P1-04 | 用户填写参考图未传到下一步 | 页面输入无效 |
| POSTER-P1-05 | 横版、方形尺寸会退回竖版 | 输出尺寸与用户选择不一致 |
| POSTER-P1-06 | 图片 API 错误被无条件降级掩盖 | 配置错误被记录成“成功” |
| SEC-P1-01 | 上传仅检查扩展名或不检查 | 恶意文件、超大 PDF、资源耗尽 |
| SEC-P1-02 | 自定义 Base URL 可访问任意地址 | 存在服务端请求内网风险 |
| DATA-P1-01 | JSON 与数据库形成双数据源 | 环境间保司、产品、模板数据漂移 |
| DATA-P1-02 | 保留天数配置没有清理任务 | 客户数据、PDF、prompt 永久保留 |
| API-P1-01 | 成功响应解包不统一 | 前端大量 `data?.data ?? data`,容易误判 |
| TASK-P1-01 | 后台 daemon thread 不可恢复 | Worker 重启后解析任务永久丢失 |
| TASK-P1-02 | 生成缺少幂等控制 | 重复点击导致重复计费、并发覆盖文件 |
| OBS-P1-01 | health 只返回 `ok` | 依赖、模型、模板、磁盘异常无法发现 |
| TEST-P1-01 | 缺少端到端测试 | 每次改动都可能再次断链 |
### 4.3 P2稳定性与体验优化
| 编号 | 问题 | 影响 |
|------|------|------|
| DATA-P2-01 | 公司模型部分字段没有通过 `to_dict` 返回 | `shortEn` 等品牌信息无法进入渲染器 |
| DATA-P2-02 | 管理后台不能完整维护评级、亮点、知识目录 | 公共保司数据只能部分编辑 |
| TASK-P2-01 | LLM 限流只在单进程内生效 | 多 Worker 时无法控制总调用量 |
| TASK-P2-02 | 配置保存后缓存最长 60 秒才生效 | 管理员误以为设置无效 |
| UX-P2-01 | 降级图、AI 图无明显区分 | 用户无法判断实际生成方式 |
| UX-P2-02 | 刷新页面后无法恢复生成步骤 | 长任务体验不稳定 |
---
## 五、重点问题根因与修复要求
### 5.1 PPT-P0-01`slidesConfig` 结构错误
### 现状
- `migrate_017.py` 写入:
```json
{
"slides": [
{"pageType": "cover"}
]
}
```
- `PptTemplatesAdmin.vue`、`PptTemplate.to_dict()` 和渲染器期望:
```json
[
{"pageType": "cover"}
]
```
- 渲染器 `_slide_meta()` 对对象使用整数下标,可能抛出 `KeyError: 0`
### 修改方案
1.`migrate_017._build_default_slides_config()` 返回值统一为数组。
2. 管理端、模型层、渲染器只接受数组,不再兼容多种永久格式。
3. 新增修复迁移,将历史对象结构转换为数组:
```text
如果值是 {"slides": [...]} → 提取 slides
如果值已经是数组 → 原样保留
如果为空 → 根据 required_page_types_json 生成
如果无法解析 → 记录异常,不静默覆盖
```
4.`PptTemplate.to_dict()` 中增加安全解析,非法 JSON 返回明确配置错误。
5. 渲染前验证 `slidesConfig`
- 必须为数组;
- 每项必须有 `pageType`
- `pageType` 必须在允许集合内;
- 至少包含一页。
### 涉及文件
- `api/insurance/db/migrate_017.py`
- 新增 `api/insurance/db/migrate_019.py`
- `api/insurance/models/ppt_config.py`
- `api/insurance/ppt/scripts/fast_pptx_renderer.py`
- `api/insurance/admin/ppt_admin_service.py`
- `frontend/src/pages/admin/PptTemplatesAdmin.vue`
### 验收
- 历史对象结构能够自动转为数组;
- 所有启用模板均能生成至少一页 PPT
- 非法结构在管理后台明确显示“模板配置无效”;
- 不再出现 `KeyError: 0`
---
### 5.2 DB-P0-01/P0-02迁移顺序与事务
### 现状
1. `deploy/sql/init.sql` 的模板表缺少 `slides_config_json`
2. `migrate_014` 使用包含该字段的新版 ORM 模型。
3. `migrate_017` 才添加字段。
4. `migrate_015._add_column()` 捕获错误后不回滚。
5. `run_migrations()` 捕获迁移异常后不统一回滚。
6. 每个 Web Worker 启动时都可能并发执行迁移。
### 修改原则
- 已经在生产执行过的迁移文件不作为唯一修复手段;
- 修正旧迁移以保证新环境可部署;
- 另加一个幂等修复迁移处理已有环境;
- 单个迁移必须原子提交;
- 失败必须回滚并停止,不允许“带病继续”;
- 迁移只能由一个进程执行。
### 修改方案
1. 更新 `deploy/sql/init.sql`,使新建表结构与当前模型一致。
2. 调整 `migrate_014`
- 使用 SQLAlchemy Inspector 检查表和字段;
- 不在字段不存在时通过完整 ORM 模型查询;
- 种子导入改为显式 SQL 或确保最新列已存在。
3. 修复 `migrate_015`
- 添加字段前先检查,不通过异常判断“已存在”;
- 捕获异常时 rollback
- 任一必要字段失败时抛出异常。
4. 修复迁移调度器:
- 每个迁移开始前 rollback 残留事务;
- 迁移失败后 rollback
- 不记录失败迁移;
- 必要迁移失败时停止应用启动。
5. PostgreSQL 使用 advisory lock避免多个 Gunicorn Worker 同时迁移。
6. 新增 `migrate_019.py`
- 补齐缺失字段;
- 修复 `slides_config_json`
- 校验 PPT、海报表
- 补必要索引;
- 输出修复数量。
7. 提供只读迁移预检命令,显示:
- 当前 schema
- 已执行迁移;
- 缺失字段;
- 异常配置数量。
### 涉及文件
- `deploy/sql/init.sql`
- `api/insurance/db/__init__.py`
- `api/insurance/db/migrate_014.py`
- `api/insurance/db/migrate_015.py`
- `api/insurance/db/migrate_017.py`
- `api/insurance/db/migrate_018.py`
- 新增 `api/insurance/db/migrate_019.py`
- `scripts/setup/patch_app.py`
### 验收
- 空数据库首次启动一次即可得到完整表、保司、产品和模板;
- 同一迁移重复执行三次结果一致;
- 两个进程并发启动时只有一个执行迁移;
- 故意制造字段冲突时能够 rollback并阻止应用误报启动成功
- SQLite 若继续声明支持017/018 必须通过 SQLite 测试;否则文档明确只支持 PostgreSQL。
---
### 5.3 SEC-P0-01模型密钥保护
### 现状
- `GET /insurance/admin/ppt/settings` 只要求登录;
- 返回所有 `system_settings` 原始值;
- API Key 以明文保存;
- 前端加载后会显示完整值,并在保存时再次回传。
### 修改方案
1. settings GET、available-models 和历史管理接口增加明确权限。
2. 设置项建立允许列表,禁止任意 key 写入。
3. 密钥类字段加密存储:
- 复用项目已有密钥/加密能力;
- 数据库只保存密文;
- 日志不得输出明文。
4. GET 只返回:
```json
{
"poster_image_api_key_configured": true,
"poster_image_api_key_masked": "sk-****abcd"
}
```
5. PUT 语义:
- 字段缺失:保持不变;
- 空字符串:不覆盖旧值;
- 明确 `clear=true`:删除密钥;
- 新密钥:加密后保存。
6. 增加密钥修改审计日志。
7. 对现有明文数据执行一次加密迁移。
### 涉及文件
- `api/insurance/admin/ppt_admin_routes.py`
- `api/insurance/admin/ppt_admin_service.py`
- `api/insurance/models/system_setting.py`
- `frontend/src/pages/admin/PptSettingsAdmin.vue`
- `frontend/src/utils/ppt-admin-api.ts`
- 新增密钥加解密工具或复用现有工具
### 验收
- 普通用户请求 settings 返回 403
- 管理员看不到完整旧密钥;
- 数据库中不存在可直接使用的明文 API Key
- 日志、错误响应和前端状态中不包含完整密钥。
---
### 5.4 SEC-P0-02/P0-03历史权限与跨用户数据
### 修改方案
1. 管理历史列表、导出、删除统一要求 `audit_view``config_manage`
2. 应用 `get_data_scope()`
- 超级管理员:全部;
- 部门主管:本部门;
- 普通用户:仅本人。
3. 海报 `generate_copy()` 必须校验:
- case 存在;
- `case.user_id == current_user`
- case 已确认;
- case.product_id 与请求 productId 一致。
4. 海报 `generate_poster()` 同样校验 case、产品和模板。
5. 所有按 ID 查询的用户数据接口建立统一所有权检查。
6. 越权访问统一返回 404避免暴露 ID 是否存在。
### 涉及文件
- `api/insurance/admin/ppt_admin_routes.py`
- `api/insurance/admin/ppt_admin_service.py`
- `api/insurance/poster/service.py`
- `api/insurance/poster/routes.py`
- `api/insurance/middleware/auth_middleware.py`
### 验收
- 用户 A 无法读取、生成或下载用户 B 的 case、文案和海报
- 普通用户无法导出全体历史;
- 权限测试覆盖本人、同部门、跨部门和管理员四种身份。
---
### 5.5 DEP-P0-01依赖统一
### 必须声明
| 依赖 | 用途 |
|------|------|
| `python-pptx` | PPTX 生成 |
| `PyMuPDF` | PDF 文本提取 |
| `PyPDF2`/`pypdf`/`pdfplumber` | PDF 提取回退 |
| `Pillow` | 海报降级图 |
| `openai` | OpenAI 兼容图片接口 |
| `httpx` | LLM HTTP 调用 |
### 修改方案
1.`api/insurance/requirements.txt` 声明全部直接依赖。
2. Dockerfile 安装该 requirements不再维护另一份手写依赖列表。
3. 增加启动依赖自检。
4. 快速启动、完整部署文档使用同一安装命令。
5. 固定兼容版本区间,避免基础镜像升级导致 SDK 行为变化。
### 涉及文件
- `api/insurance/requirements.txt`
- `Dockerfile.dify-custom`
- `docs/快速启动指南.md`
- `docs/部署文档_完整版.md`
### 验收
- 新 Python 环境安装 requirements 后可导入全部生成依赖;
- Docker 构建阶段执行导入检查;
- 使用样例 DeckContract 能生成有效 PPTX
- 无图片 API Key 时能生成明确标记的 fallback PNG。
---
### 5.6 POSTER-P0-01鉴权预览和下载
### 修改方案
1. 海报 API 增加 Blob 下载方法,与 PPT 下载方式一致。
2. 前端通过 Axios 请求 Blob创建临时 Object URL 供 `<el-image>` 预览。
3. 下载按钮复用同一 Blob不再 `window.open` 受保护 URL。
4. 组件卸载或重新生成时释放 Object URL。
5. PPT 的 `window.open` 降级路径一并移除。
6. 历史页使用相同认证图片加载组件。
### 涉及文件
- `frontend/src/utils/poster-api.ts`
- `frontend/src/components/poster/PosterStepPreview.vue`
- `frontend/src/pages/PosterHistoryPage.vue`
- `frontend/src/pages/components/ppt/PptResult.vue`
### 验收
- 登录模式和访客模式均能预览、下载本人海报;
- 浏览器网络请求携带 Authorization
- 用户之间不能互相下载;
- 多次预览不持续占用 Blob 内存。
---
### 5.7 POSTER-P0-02基础数据初始化
### 修改方案
1. 至少准备:
- 1 个 reviewed 测试产品;
- 2 个海报模板;
- 2 个文案模板。
2. 正式产品仍必须走“小册子解析 → 人工审核 → reviewed”。
3. 开发/验收种子与生产业务数据区分:
- 开发环境自动导入;
- 生产环境提供显式初始化命令;
- 不自动覆盖管理员修改。
4. 页面空状态明确区分:
- 没有产品;
- 产品未审核;
- 没有模板;
- 没有文案模板。
5. 管理后台增加配置完整度提示。
### 涉及文件
- 新增 `api/insurance/poster/config/` 或数据库种子文件
- 新增修复迁移/初始化脚本
- `frontend/src/components/poster/PosterStepProduct.vue`
- `frontend/src/components/poster/PosterStepTemplate.vue`
- 管理后台相关页面
### 验收
- 空数据库初始化后可以完整走通一条海报流程;
- 生产环境不会把未审核产品自动标为 reviewed
- 缺配置时页面给出管理员可执行的处理提示。
---
### 5.8 POSTER-P0-03/P1-01模型配置和供应商适配
### 目标
保留一套通用调用接口,但按用途读取不同配置:
```text
ppt_extract → ppt_llm_*
poster_copy → poster_llm_*
poster_image → poster_image_*
```
### 修改方案
1. `LLMClient` 支持传入用途或配置命名空间,不让海报文案继续读取 PPT 配置。
2. 不配置模型时不再返回 mock `{}`;正式接口应返回“模型未配置”错误。
3. 结构化输出执行真实 Schema/Pydantic 校验。
4. 图片供应商按协议拆分最小适配器:
- OpenAI
- 豆包;
- 智谱;
- 自定义 OpenAI-compatible。
5. 同时支持:
- `b64_json`
- 图片 URL 下载;
- 各供应商允许的尺寸;
- 参考图是否受支持。
6. 保存配置前提供测试连接。
7. 图片生成记录增加:
- provider
- model
- generation_mode
- error_code
- latency
- fallback_reason。
8. AI 失败时是否允许 fallback 由明确配置决定。
### 涉及文件
- `api/insurance/ppt/llm_client.py`
- `api/insurance/poster/copy_generator.py`
- `api/insurance/poster/manual_parser.py`
- `api/insurance/poster/image_generator.py`
- `api/insurance/poster/service.py`
- `api/insurance/models/poster_record.py`
- `frontend/src/pages/admin/PptSettingsAdmin.vue`
### 验收
- 海报文案实际使用后台配置的 `poster_llm_*`
- 每个声明支持的图片供应商至少有一个契约测试;
- 错误 Key、错误模型、欠费、超时返回不同错误码
- 空对象或缺少标题、正文、行动号召时不能进入下一步。
---
### 5.9 POSTER-P0-04/TASK-P1-01异步任务
### 目标状态机
```text
created
→ queued
→ parsing / generating
→ review_required / done
→ failed
```
### 修改方案
1. 复用 BaoDan Celery不新增独立任务系统。
2. 上传接口只完成:
- 文件校验;
- 保存;
- 创建记录;
- 投递任务;
- 返回任务 ID。
3. Celery 负责:
- PDF 解析;
- 产品小册子解析;
- 海报图片生成;
- 必要时 PPT 渲染。
4. Redis 只用于任务锁和进度,不作为最终状态唯一来源。
5. 数据库保存最终状态、错误和结果路径。
6. 增加幂等键:
- 同一 session 同一版本只生成一次;
- 重复点击返回现有任务;
- 明确“重新生成”时创建新版本。
7. Redis 锁使用随机 owner token释放时比较 owner。
8. 前端按 2 秒轮询,连续错误采用退避。
9. 页面刷新后根据 session/record ID 恢复进度。
10. Celery 重试仅覆盖网络、限流等可重试错误;数据错误不重试。
### 接口调整
| 接口 | 调整 |
|------|------|
| `POST /poster/case-upload` | 立即返回 case ID 和 `queued` |
| `GET /poster/case-upload/{id}` | 返回解析进度、状态、错误 |
| `POST /poster/generate` | 返回 generation record/task ID |
| `GET /poster/records/{id}` | 返回生成进度、模式、文件状态 |
| `POST /ppt/generate/{sessionId}` | 建议返回任务状态,保留兼容期 |
### 验收
- 创建任务接口 3 秒内返回;
- 5 分钟解析任务不会导致浏览器请求超时;
- Worker 重启后任务可重试或明确失败;
- 重复点击不会重复扣费;
- 用户可以刷新页面并恢复进度。
---
### 5.10 DATA-P0-01文件持久化
### 统一目录
```text
/app/api/storage/insurance/
├── uploads/
│ ├── ppt/
│ ├── poster-cases/
│ └── manuals/
├── outputs/
│ ├── ppt/
│ └── posters/
└── work/
```
### 修改方案
1. 新增 `INSURANCE_STORAGE_ROOT` 配置,默认指向 BaoDan 持久化 storage。
2. 数据库保存相对对象键,不保存依赖当前工作目录的绝对路径。
3. 下载时通过统一 StorageService 解析路径。
4. 使用 `os.path.commonpath` 校验安全目录,不使用字符串 `startswith`
5. Docker API、Worker 共享同一 storage volume。
6. 工作目录可清理,结果目录按保留策略清理。
7. 对旧路径提供一次迁移/兼容读取。
### 涉及文件
- `api/insurance/config.py`
- `api/insurance/ppt/routes.py`
- `api/insurance/ppt/renderer.py`
- `api/insurance/poster/service.py`
- `api/insurance/poster/routes.py`
- `docker-compose.dify.yml`
- Worker volume 配置
### 验收
- API 容器重建后历史文件仍能下载;
- Worker 生成的文件 API 能读取;
- 非 storage 路径无法通过下载接口访问;
- 清理任务不会误删仍在保留期的文件。
---
### 5.11 PPT-P1-01P1-04模板、保司、产品和知识库
### 统一运行时数据源
运行时以数据库为唯一权威数据源JSON 仅作为版本化种子,不在请求中直接读取。
### 生成上下文
```json
{
"customer": {},
"plans": [
{
"planType": "savings",
"company": {},
"product": {},
"extractedData": {},
"evidence": []
}
],
"template": {},
"generation": {}
}
```
### 修改方案
1. 上传时的 `companyId` 保留到每个 extraction。
2. 解析后用产品名称、别名匹配产品和保司。
3. 匹配优先级:
- 用户强制选择;
- 产品目录精确匹配;
- 产品别名;
- 保司别名;
- 无法确定时人工确认。
4. 低置信度匹配不得自动进入正式生成。
5.`company-kb/match` 接入解析完成流程。
6. 调用 BaoDan 知识库获取公司介绍证据,结果保存:
- 文档 ID
- 文件名;
- 片段;
- 版本/更新时间。
7. 多产品按每份 plan 保存独立保司,不再使用一个全局 companyId。
8. 前端不硬编码风格列表,按险种和产品读取可用模板。
9. 模板选择校验:
- status
- planType
- applicableCompanyIds
- applicableProductIds。
10. 公司 `to_dict()` 补齐品牌字段,管理后台允许维护知识目录、评级和亮点。
11. 增加显式种子 upsert 命令,默认只新增,不覆盖人工修改。
### 涉及文件
- `api/insurance/ppt/routes.py`
- `api/insurance/ppt/parse_worker.py` 或对应 Celery task
- `api/insurance/ppt/knowledge.py`
- `api/insurance/ppt/renderer.py`
- `api/insurance/models/ppt_config.py`
- `api/insurance/admin/ppt_admin_service.py`
- `frontend/src/pages/components/ppt/PptUpload.vue`
- `frontend/src/pages/components/ppt/PptDataReview.vue`
- `frontend/src/pages/components/ppt/PptGenerate.vue`
### 验收
- 同一产品在 PPT 和海报中使用相同保司名称、Logo 和卖点;
- 上传阶段选择的保司自动带入生成阶段;
- 两家保司的组合方案分别显示各自公司信息;
- 公司介绍可以追溯到知识库证据;
- 前端只展示当前方案真正可用的模板。
---
### 5.12 PPT-P1-05正式数据校验
### 修改方案
1. 统一校验级别:
- `error`:禁止生成;
- `warn`:用户确认后允许;
- `info`:展示但不阻塞。
2. 生成接口必须再次服务端校验,不能依赖前端。
3. 用户确认 warning 时保存:
- 确认人;
- 时间;
- 校验快照;
- 数据版本。
4. PPT 历史记录保存正式输入快照和校验摘要。
5. 关键保险数字禁止由 LLM 补造。
### 验收
- 构造缺少保费、缴费年期或利益表的数据时生成返回 4xx
- warning 未确认不能生成;
- 已生成文件可追溯到输入数据和校验版本。
---
### 5.13 SEC-P1-01/P1-02上传与外部请求安全
### 文件上传规则
| 项目 | 建议规则 |
|------|----------|
| 文件类型 | 仅 PDF |
| 文件头 | 必须为合法 PDF |
| 单文件大小 | 配置化,默认不超过 30MB |
| 页数 | 配置化,默认不超过 200 页 |
| 单会话文件数 | 默认不超过 3 |
| 加密 PDF | 明确拒绝并提示 |
| 文件名 | 不使用原始文件名作为物理路径 |
| 解析超时 | 任务级限制 |
### 自定义 Base URL 规则
- 默认只允许 HTTPS
- 拒绝 localhost、环回、私网、链路本地和云元数据地址
- DNS 解析后校验最终 IP
- 禁止跟随到内网的重定向;
- 可配置供应商域名白名单;
- 连接测试设置短超时和响应大小限制。
### 验收
- 伪造扩展名、超大文件、加密 PDF、超页数 PDF 被拒绝;
- `127.0.0.1`、`169.254.169.254`、私网地址不能作为自定义 Base URL
- 文件拒绝不创建脏记录。
---
### 5.14 DATA-P1-02隐私、保留和清理
### 敏感数据
- 上传的计划书 PDF
- 客户姓名、年龄、性别;
- 保额、保费、利益演示;
- 解析结果和人工确认数据;
- AI 原始文案;
- 生成 prompt
- 模型调用日志。
### 修改方案
1. 明确保留策略的适用对象,而不只配置历史记录天数。
2. 增加每日 Celery 清理任务。
3. 删除顺序:
- 标记待删除;
- 删除物理文件;
- 清理或匿名化数据库数据;
- 写审计记录。
4. prompt 日志默认不保存完整客户数据,保存脱敏摘要或 hash。
5. 发送外部模型前记录供应商、用途和数据范围。
6. 管理后台提供按用户/会话删除能力。
7. 明确失败任务和临时工作目录的短保留期。
### 验收
- 将保留期设置为测试值后,过期文件和记录能自动清理;
- 清理任务可重复执行;
- 日志中搜索不到完整保额、保费和 API Key
- 删除后历史接口不再返回失效下载链接。
---
### 5.15 API-P1-01接口响应统一
### 统一格式
```json
{
"code": 0,
"message": "success",
"data": {}
}
```
### 修改方案
1. PPT、海报路由统一使用 `success()`、`error()`。
2. Service 返回业务数据或抛出业务异常,不返回第二层完整响应包装。
3. 前端 Axios 拦截器只解包一次。
4. 删除:
```typescript
res?.data?.data ?? res?.data
```
5. 为任务类接口统一字段:
- `taskId`
- `status`
- `progress`
- `message`
- `errorCode`
- `retryable`
6. 在 API 文档补齐请求、响应和错误码。
### 验收
- 所有 PPT/海报成功响应只有一层 `data`
- 4xx/5xx 错误码与页面提示一致;
- TypeScript 为接口响应提供明确类型,不再使用主要流程 `any`
---
## 六、文件级修改清单
| 文件/目录 | 修改内容 | 优先级 |
|-----------|----------|--------|
| `api/insurance/db/__init__.py` | 迁移锁、失败回滚、失败停止、原子记录 | P0 |
| `api/insurance/db/migrate_014.py` | 修复模板种子导入顺序 | P0 |
| `api/insurance/db/migrate_015.py` | Inspector 检查字段,移除异常驱动流程 | P0 |
| `api/insurance/db/migrate_017.py` | `slidesConfig` 改为数组 | P0 |
| `api/insurance/db/migrate_018.py` | 数据库方言兼容 | P1 |
| `api/insurance/db/migrate_019.py` | 修复已有 schema 和模板数据 | P0 |
| `deploy/sql/init.sql` | 同步最新表结构 | P0 |
| `api/insurance/requirements.txt` | 补齐 PPT、PDF、海报依赖 | P0 |
| `Dockerfile.dify-custom` | 安装统一 requirements 并自检 | P0 |
| `api/insurance/ppt/routes.py` | 模板校验、正式数据校验、异步生成、持久化路径 | P0/P1 |
| `api/insurance/ppt/parse_worker.py` | 迁移到 Celery保留兼容入口后删除线程 | P1 |
| `api/insurance/ppt/renderer.py` | 配置预检、输出校验、持久化路径 | P0 |
| `api/insurance/ppt/scripts/fast_pptx_renderer.py` | 严格验证 slidesConfig失败不误报成功 | P0 |
| `api/insurance/ppt/llm_client.py` | 配置命名空间、真实 Schema 校验、禁用 mock 成功 | P0 |
| `api/insurance/ppt/knowledge.py` | 接入真实知识库证据 | P1 |
| `api/insurance/poster/service.py` | 所有权、状态校验、异步任务、统一响应 | P0 |
| `api/insurance/poster/image_generator.py` | 供应商适配、尺寸、响应格式、fallback 标记 | P0/P1 |
| `api/insurance/poster/copy_generator.py` | 使用 poster LLM 配置并校验输出 | P0 |
| `api/insurance/poster/routes.py` | 统一响应、鉴权下载、安全路径 | P0 |
| `api/insurance/admin/ppt_admin_routes.py` | 设置、历史权限收紧 | P0 |
| `api/insurance/admin/ppt_admin_service.py` | 密钥掩码、允许列表、配置验证 | P0 |
| `api/insurance/models/system_setting.py` | 密钥字段处理或关联加密服务 | P0 |
| `api/insurance/models/poster_record.py` | 任务、供应商、模式、错误字段 | P1 |
| `api/insurance/models/ppt_config.py` | 完整公司字段、安全 JSON 解析 | P1 |
| `frontend/src/utils/api.ts` | 任务请求策略、统一响应 | P1 |
| `frontend/src/utils/poster-api.ts` | 鉴权 Blob、状态接口类型 | P0 |
| `frontend/src/components/poster/*` | 恢复任务、参考图、错误和 fallback 状态 | P0/P1 |
| `frontend/src/pages/components/ppt/*` | 动态模板、保司流转、任务恢复 | P1 |
| `frontend/src/pages/admin/PptSettingsAdmin.vue` | 密钥掩码、连接测试、配置完整度 | P0 |
| `docker-compose.dify.yml` | API/Worker 共享持久化 storage | P0 |
---
## 七、分阶段实施计划
### Phase 0生产现状快照与回归基线
**预计工作量**0.5 人日
### 任务
- [ ] 备份数据库;
- [ ] 导出 `db_migration_history`
- [ ] 导出相关表字段;
- [ ] 统计保司、产品、PPT 模板、海报模板、文案模板数量;
- [ ] 检查 `slides_config_json` 的实际结构;
- [ ] 收集最近 PPT、海报失败日志
- [ ] 准备脱敏 PDF 样例和预期结果。
### 产出
- 生产数据快照;
- 迁移前检查报告;
- 可重复的失败样例;
- 回归测试基线。
### 完成标准
- 能明确当前失败发生在上传、解析、模板选择、渲染还是下载阶段;
- 所有修复均可用同一组样例复测。
---
### Phase 1紧急 P0 修复
**预计工作量**2 人日
### 任务
- [ ] 修复 `slidesConfig` 结构;
- [ ] 修复迁移顺序、事务回滚和迁移锁;
- [ ] 增加修复迁移 019
- [ ] 收紧 settings、历史权限
- [ ] 修复海报 case 所有权;
- [ ] 补齐依赖;
- [ ] 修复海报鉴权预览和下载;
- [ ] 将文件根目录改到持久化 storage。
### 完成标准
- 不配置外部图片模型时fallback 海报可生成、预览和下载;
- 储蓄险样例能够生成可打开的 PPTX
- 普通用户不能读取模型密钥和全体历史;
- 容器重启后已生成文件仍存在。
---
### Phase 2模型和海报基础闭环
**预计工作量**2 人日
### 任务
- [ ] 海报文案读取 `poster_llm_*`
- [ ] 图片供应商按协议适配;
- [ ] 真实结构化输出校验;
- [ ] 初始化海报模板和文案模板;
- [ ] 修复参考图、尺寸;
- [ ] 区分 AI 成功、fallback 成功和失败;
- [ ] 增加模型连接测试。
### 完成标准
- 模板文案和 AI 文案均可用;
- 至少一个正式图片供应商通过真实测试;
- 错误模型配置不会被误报为成功;
- 生成记录包含真实 provider、model 和 generation_mode。
---
### Phase 3异步任务和幂等
**预计工作量**2 人日
### 任务
- [ ] 将海报 case 解析接入 Celery
- [ ] 将海报图片生成接入 Celery
- [ ] 将 PPT 解析从后台线程迁移到 Celery
- [ ] 评估 PPT 渲染是否同步保留或异步化;
- [ ] 增加状态、进度、错误和重试字段;
- [ ] 增加幂等键;
- [ ] 前端支持轮询和刷新恢复。
### 完成标准
- 任务创建请求 3 秒内返回;
- Worker 重启后状态可解释;
- 重复点击不重复生成、计费;
- 任务失败可区分可重试和不可重试。
---
### Phase 4公共保司、产品、模板与知识库
**预计工作量**2 人日
### 任务
- [ ] 建立统一 GenerationContext
- [ ] 上传保司信息贯穿解析、审核和生成;
- [ ] 自动匹配产品和保司;
- [ ] 低置信度人工确认;
- [ ] 接入 BaoDan 知识库证据;
- [ ] 多产品独立携带公司;
- [ ] 前端动态读取可用模板;
- [ ] 统一 JSON 种子和数据库策略。
### 完成标准
- PPT、海报共享同一保司、产品数据
- 跨保司方案公司信息不串用;
- 公司介绍有证据来源;
- 模板适用范围生效。
---
### Phase 5安全、隐私与运维
**预计工作量**1.5 人日
### 任务
- [ ] 文件上传限制;
- [ ] 自定义 Base URL 安全校验;
- [ ] API Key 加密和掩码;
- [ ] 数据保留和清理任务;
- [ ] prompt 和日志脱敏;
- [ ] 增强 readiness
- [ ] 结构化日志和关键指标。
### 完成标准
- 安全测试通过;
- 过期文件可自动清理;
- health 能识别依赖、配置和存储异常;
- 日志不包含完整密钥和客户敏感数字。
---
### Phase 6自动化测试、灰度和上线
**预计工作量**2 人日
### 任务
- [ ] 单元测试;
- [ ] API 集成测试;
- [ ] Celery 任务测试;
- [ ] 前端端到端测试;
- [ ] 数据迁移演练;
- [ ] 容器重建测试;
- [ ] 灰度开关;
- [ ] 上线和回滚演练;
- [ ] 更新 API、部署、测试和变更日志文档。
### 完成标准
- P0/P1 验收用例全部通过;
- 旧数据修复结果有统计;
- 灰度期间无跨用户访问、密钥泄露和文件丢失;
- 回滚方案演练成功。
---
## 八、测试计划
### 8.1 单元测试
| 编号 | 测试内容 |
|------|----------|
| UT-PPT-01 | 对象形式 slidesConfig 转换为数组 |
| UT-PPT-02 | 非法 slidesConfig 被拒绝 |
| UT-PPT-03 | 风格、险种、保司、产品模板匹配 |
| UT-PPT-04 | validator error 阻止生成 |
| UT-PPT-05 | 多产品分别携带公司 |
| UT-POSTER-01 | 文案模板变量替换 |
| UT-POSTER-02 | 海报文案缺字段被拒绝 |
| UT-POSTER-03 | 各图片供应商响应解析 |
| UT-POSTER-04 | 横版、方形、竖版尺寸映射 |
| UT-SEC-01 | 安全文件路径判断 |
| UT-SEC-02 | Base URL 私网拦截 |
| UT-DB-01 | 迁移重复执行幂等 |
### 8.2 API 集成测试
| 编号 | 场景 | 预期 |
|------|------|------|
| API-PPT-01 | 上传合法 PDF | 返回 sessionId |
| API-PPT-02 | 上传伪 PDF | 4xx |
| API-PPT-03 | 解析任务提交 | 3 秒内返回 queued |
| API-PPT-04 | 无效数据生成 | 4xx包含校验问题 |
| API-PPT-05 | 合法数据生成 | 返回任务,最终 done |
| API-PPT-06 | 用户 B 下载用户 A 文件 | 404 |
| API-POSTER-01 | 无 reviewed 产品 | 返回明确配置状态 |
| API-POSTER-02 | 访问他人 caseUploadId | 404 |
| API-POSTER-03 | 模型未配置 | 明确错误,不返回空文案 |
| API-POSTER-04 | 图片 API 失败且允许 fallback | `fallback_success` |
| API-POSTER-05 | 图片 API 失败且禁止 fallback | `failed` |
| API-SEC-01 | 普通用户读取 settings | 403 |
| API-SEC-02 | 普通用户导出全体历史 | 403 |
### 8.3 端到端测试
1. 储蓄险 PDF → 解析 → 人工核对 → PPT → 下载;
2. 重疾险 PDF → business 模板 → PPT → 下载;
3. IUL PDF → 数据校验 → PPT → 下载;
4. Reviewed 产品 → 客户计划书 → 模板文案 → 海报 → 预览 → 下载;
5. Reviewed 产品 → AI 文案 → AI 海报;
6. 图片供应商失败 → fallback 海报;
7. 两家保司组合 → 自动匹配 → 对比 PPT
8. 页面刷新 → 恢复任务;
9. API/Worker 容器重建 → 历史文件仍可下载;
10. 普通用户越权访问 → 全部拒绝。
### 8.4 数据库迁移测试
至少准备四种数据库状态:
1. 完全空库;
2. 只执行到 014
3. 015018 部分字段存在;
4. `slides_config_json` 同时包含对象、数组、空值和非法 JSON。
每种状态验证:
- 迁移一次成功;
- 再执行两次无副作用;
- 迁移失败可回滚;
- 数据数量和模板配置符合预期。
---
## 九、监控与健康检查
### 9.1 健康检查分层
| 检查 | 内容 |
|------|------|
| `/health/live` | 进程存活 |
| `/health/ready` | DB、Redis、storage 可写、必要表字段 |
| `/ppt/health` | PPT 依赖、模板数量、渲染器可执行 |
| `/poster/health` | reviewed 产品、模板、文案模板、图片模型配置 |
### 9.2 关键指标
- PPT 上传成功率;
- PDF 解析成功率和平均耗时;
- PPT 渲染成功率和页数;
- 海报 AI 成功率;
- 海报 fallback 比例;
- 模型超时、限流、鉴权错误;
- 任务排队长度;
- 存储剩余空间;
- 历史文件缺失数量;
- 模板配置错误数量。
### 9.3 日志字段
```text
requestId
taskId
sessionId / recordId
userId必要时脱敏
provider
model
status
durationMs
errorCode
retryable
fallbackReason
```
---
## 十、发布与回滚
### 10.1 上线前
- [ ] 数据库备份;
- [ ] storage 目录备份;
- [ ] 导出迁移历史;
- [ ] 运行迁移预检;
- [ ] 在生产副本演练迁移 019
- [ ] 验证旧 PPT、海报历史读取
- [ ] 准备功能开关:
- `PPT_GENERATION_ENABLED`
- `POSTER_GENERATION_ENABLED`
- `POSTER_AI_IMAGE_ENABLED`
- `POSTER_FALLBACK_ENABLED`
### 10.2 灰度顺序
1. 只开放管理员和测试账号;
2. 开放 PPT 基础生成;
3. 开放海报模板/fallback 模式;
4. 开放 AI 文案;
5. 开放 AI 图片;
6. 开放全部用户。
### 10.3 回滚原则
- 代码回滚不自动回滚已成功的数据修复;
- 新字段保持向后兼容;
- migration 019 不删除旧字段和旧文件;
- 功能异常优先关闭开关,不立即执行破坏性数据库回滚;
- 文件路径迁移期间保留旧路径兼容读取;
- 回滚后仍需保证历史文件可下载。
### 10.4 回滚触发条件
- PPT/海报生成失败率持续超过基线;
- 出现跨用户数据访问;
- 模型密钥暴露;
- 迁移导致表结构异常;
- 生成任务大量重复计费;
- storage 文件无法读取;
- 数据校验失效并输出错误保险数字。
---
## 十一、文档同步清单
实施代码时必须同步更新:
| 文档 | 更新内容 |
|------|----------|
| `保险智能客服系统_API接口文档.md` | 异步任务、状态、错误码、鉴权 |
| `保险智能客服系统_需求文档.md` | PPT/海报业务规则和异常场景 |
| `保险智能客服系统_测试用例.md` | 本文测试用例 |
| `部署文档_完整版.md` | 依赖、storage、Celery、配置 |
| `快速启动指南.md` | 统一依赖安装和验证 |
| `PPT与海报功能开发任务清单.md` | 修复任务状态 |
| `CHANGELOG.md` | 安全、迁移、生成链路修复 |
---
## 十二、实施约束
1. 自研代码仍全部位于 `api/insurance/`
2. 不修改 BaoDan 核心模块来承载业务逻辑。
3. 复用 BaoDan 数据库、Redis、Celery、日志和认证。
4. 不在一次提交中同时重构无关模块。
5. 每项代码修改必须关联本文问题编号。
6. 先写失败测试,再修复对应问题。
7. 不以 fallback 掩盖配置错误。
8. 不以“前端已经校验”为理由跳过服务端校验。
9. 不提交 `.env`、API Key、客户 PDF 或未脱敏样例。
10. 迁移必须在生产数据副本上演练后上线。
---
## 十三、完成定义
只有同时满足以下条件PPT 与海报模块才可标记为完成:
- [ ] 空数据库首次部署一次成功;
- [ ] 迁移重复执行幂等;
- [ ] PPT 三个险种样例均可生成和下载;
- [ ] 海报模板、AI、fallback 三种路径状态明确;
- [ ] 生成文件在容器重建后仍存在;
- [ ] PPT、海报使用同一公共保司和产品数据
- [ ] 模板配置实际进入渲染器;
- [ ] 普通用户无法读取密钥、全体历史和他人 case
- [ ] 所有耗时任务可恢复、可追踪、可幂等;
- [ ] 关键数据错误会阻止正式生成;
- [ ] health 能发现依赖、配置、数据库和存储问题;
- [ ] P0/P1 自动化测试全部通过;
- [ ] 发布和回滚演练完成;
- [ ] API、部署、测试、变更日志文档同步完成。
---
## 十四、预计工作量
| 阶段 | 预计人日 |
|------|:--------:|
| Phase 0现状快照 | 0.5 |
| Phase 1紧急 P0 | 2 |
| Phase 2模型和海报闭环 | 2 |
| Phase 3异步任务 | 2 |
| Phase 4公共数据打通 | 2 |
| Phase 5安全与运维 | 1.5 |
| Phase 6测试与上线 | 2 |
| **合计** | **约 12 人日** |
说明:
- 以上为单人连续开发的技术工作量估算;
- 不包含外部模型账号申请、知识库资料整理、业务方核对产品规则的等待时间;
- Phase 1 完成后可恢复基础可用;
- Phase 4 完成后,公共保司、产品、模板和知识库才算真正连通;
- Phase 6 完成后才建议面向全部生产用户开放。