# 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 | 已生成图片仍显示失败 | 使用 `` 和 `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 供 `` 预览。 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-01~P1-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. 015~018 部分字段存在; 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 完成后才建议面向全部生产用户开放。