baodan/docs/用户上传产品小册子解析与留存实施计划.md

25 KiB
Raw Blame History

用户上传产品小册子解析与留存实施计划

文档版本v1.0
编制日期2026-07-30
适用范围:海报制作中的产品资料选择、产品小册子上传、解析、确认、留存和复用
建议优先级P0
预计工作量MVP 约 6 人日,建议预留 20% 风险缓冲


一、结论

本需求不应直接把普通用户上传的小册子写入现有公共 PptProduct 产品库。

推荐新增“我的产品资料库”:

  1. 用户可以继续选择后台已审核产品;
  2. 找不到产品时,可以上传产品小册子 PDF
  3. 系统持久化保存原 PDF并异步解析产品名称、产品卖点、保障亮点、币种、投保规则和风险提示
  4. 用户必须核对并确认解析结果,确认后才能生成文案和海报;
  5. 已确认的小册子进入上传者的“我的产品资料”,以后可直接复用;
  6. 用户资料默认私有,不对其他用户可见;
  7. 只有显式提交并经管理员审核后,才允许转入公共产品库。

这一路径能复用现有 PDF 安全校验、小册子解析器、Celery、存储根目录和海报生成逻辑同时避免未经审核的数据污染公共产品库。


二、当前实现与问题定位

2.1 已有能力

能力 当前实现 可复用结论
公共产品和小册子 PptProduct.manual_file_urlmanual_parsed_rulesmanual_parse_status 可作为公共产品库继续使用
管理员上传小册子 PptAdminService.upload_manual() 可复用文件校验和存储思路
小册子解析 poster/manual_parser.py::parse_manual_pdf() 直接复用产品规则提取 Schema
异步任务 parse_product_manual_task + Celery 新增用户资料解析任务,不另建队列
客户计划书上传 /poster/case-uploadPosterCaseUpload 继续保留,不能与产品小册子混用
海报文案和生成 PosterService.generate_copy()generate_poster() 增加统一产品来源解析后复用
PDF 安全校验 prepare_pdf_upload() 直接复用大小、魔数、页数、密码处理
持久化目录 get_storage_root() 用户小册子必须写入共享持久化卷

2.2 当前阻断点

  1. /poster/products 只返回 manual_parse_status=reviewed 的公共产品。
  2. 海报页面必须先选公共 productId,找不到产品时无法继续。
  3. 当前 /poster/case-upload 上传的是客户计划书,并且强制绑定公共 productId
  4. generate_copy()generate_poster() 都直接查询 PptProduct,不认识用户上传资料。
  5. 普通用户没有自己的产品资料实体、列表、详情、删除和复用能力。
  6. 当前小册子解析只支持管理员维护的公共产品,没有用户所有权校验。
  7. 前端 localStorage 只保存当前草稿,不等于服务端永久留存。

2.3 两类 PDF 必须分开

文件类型 主要内容 解析结果 生命周期
产品小册子 产品规则、卖点、币种、保障、风险提示 product_rules 可跨多个海报项目复用
客户计划书 年龄、性别、保额、保费、缴费年期、利益表 customer_data 与本次客户和海报项目绑定

前端文案也应统一使用“产品小册子”和“客户计划书”,避免两者都显示为“计划书”。


三、方案选择

3.1 方案对比

方案 优点 风险 结论
直接创建全局 PptProduct 改动表面较少 私有数据可能被其他用户看到;未经审核资料混入公共库;现有所有查询都要补权限 不采用
PptProduct 增加 owner/visibility 仍使用一个产品表 需要审计全项目中所有 PptProduct 查询,漏一处就可能越权 不建议作为首期
新建用户产品资料表 权限边界清晰;不影响公共产品;便于留存、复用和软删除 海报生成需要支持两种来源 推荐
仅保存为当前海报项目素材 与未来工作区模型一致 无法自然实现跨项目复用的“我的资料库” 可作为项目快照,不作为主实体

3.2 推荐架构

产品资料来源
├── 公共产品PptProduct管理员审核
└── 我的资料UserProductMaterial用户上传并确认
          │
          ├── 原始 PDF持久化 storage
          ├── 解析结果parsed_rules
          ├── 用户确认结果confirmed_rules
          └── 可选提交审核:进入公共产品库

统一 ProductSourceResolver
          │
          ├── 校验来源存在、状态和所有权
          ├── 输出统一 ProductContext
          └── 提供给文案生成、海报生成和项目恢复

统一的 ProductContext 至少包含:

{
  "sourceType": "library_product",
  "sourceId": "aia_example",
  "productName": "产品名称",
  "companyId": "aia",
  "companyName": "保司名称",
  "planType": "savings",
  "rules": {},
  "confirmedAt": "2026-07-30T10:00:00"
}

sourceType 首期只支持:

  • library_product:后台公共产品;
  • user_material:当前用户上传的产品小册子。

四、数据模型与存储

4.1 新表 insurance_user_product_materials

建议新增 api/insurance/models/user_product_material.py

字段 类型 说明
id BIGINT 主键
owner_user_id VARCHAR(64) 上传者,必须建立索引
tenant_id VARCHAR(64) 租户隔离;无法可靠取得时首期使用 default
company_id VARCHAR(50) 可为空,确认时允许选择
company_name VARCHAR(100) PDF 解析或用户确认的保司名称
product_name VARCHAR(150) 解析或用户确认的产品名称
plan_type VARCHAR(20) savings/ci/iul/other
original_name VARCHAR(200) 原文件名
file_key VARCHAR(500) 相对 storage 根目录的对象键
file_size BIGINT 文件字节数
page_count INTEGER PDF 页数
sha256 VARCHAR(64) 用户内去重和审计
parse_status VARCHAR(20) pending/queued/parsing/parsed/failed
parse_message VARCHAR(500) 进度提示
parse_error TEXT 解析错误,返回前需要脱敏
parse_task_id VARCHAR(200) Celery 任务 ID
parsed_rules TEXT 系统原始解析 JSON
confirmed_rules TEXT 用户核对后的最终 JSON
confirmed_by VARCHAR(64) 确认人
confirmed_at TIMESTAMP 确认时间
review_status VARCHAR(20) private/submitted/approved/rejected
status SMALLINT 1=可用0=停用
created_at / updated_at TIMESTAMP 时间字段
deleted_at TIMESTAMP 软删除

索引和约束:

  • INDEX(owner_user_id, deleted_at, updated_at)
  • INDEX(owner_user_id, sha256)
  • INDEX(parse_status)
  • 不做跨用户重复文件提示,避免侧信道泄露;
  • confirmed_rules 为空的资料不得用于生成。

4.2 现有表兼容扩展

poster_case_uploadsposter_records 增加:

字段 用途
product_source_type library_product / user_material
product_source_id 统一使用字符串保存来源 ID
product_snapshot_json 提交生成时固化产品名称、保司、规则和确认时间

兼容规则:

  • 旧记录没有 product_source_type 时,按 library_product + product_id 读取;
  • 新代码继续接受旧请求中的 productId
  • 新页面优先发送 productSource
  • 已生成项目始终使用 product_snapshot_json,后台资料后续变化不能静默改变历史海报。

4.3 文件存储

推荐对象键:

uploads/product-manuals/users/{safe_user_id}/{material_id}/{uuid}.pdf

要求:

  • 只在数据库保存相对 file_key,读取时由统一存储服务拼接;
  • API 和 Worker 必须挂载同一个 INSURANCE_STORAGE_ROOT 持久化卷;
  • 下载接口必须鉴权并校验文件位于产品小册子目录内;
  • PDF 密码仅用于本次解密,不写数据库、不写日志;
  • 替换文件时生成新对象,不覆盖旧文件;新解析成功后再切换引用;
  • 删除采用软删除;被历史海报快照引用的文件不得立即物理删除。

建议默认保留策略:

  • 活跃资料:保留至用户主动删除;
  • 用户删除:进入 30 天回收期;
  • 已被海报使用:至少保留到相关海报项目过期;
  • 最终期限需要由业务、法务和存储成本负责人确认。

五、后端接口

5.1 用户产品资料接口

方法 URL 用途
GET /insurance/poster/product-materials 查询“我的产品资料”
POST /insurance/poster/product-materials 上传 PDF创建记录并自动投递解析
GET /insurance/poster/product-materials/{id} 查询详情和解析状态
PUT /insurance/poster/product-materials/{id}/confirm 保存修正结果并确认
POST /insurance/poster/product-materials/{id}/retry 解析失败后重试
GET /insurance/poster/product-materials/{id}/file 鉴权预览原 PDF
DELETE /insurance/poster/product-materials/{id} 软删除本人资料
POST /insurance/poster/product-materials/{id}/submit-review 可选:提交进入公共库审核

上传使用 multipart/form-data

字段 必填 说明
file PDF
password PDF 打开密码,不保存
companyId 用户已知时可传
planType 用户已知时可传

5.2 产品来源接口

保留现有 GET /poster/products,新增:

GET /insurance/poster/product-sources

返回:

{
  "code": 0,
  "message": "success",
  "data": {
    "libraryProducts": [],
    "myMaterials": []
  }
}

“我的资料”只返回:

  • 当前用户所有;
  • 未删除且启用;
  • parse_status=parsed 或已确认;
  • 生成时仍必须再次检查 confirmed_rules

5.3 生成接口兼容

新请求:

{
  "productSource": {
    "type": "user_material",
    "id": "123"
  },
  "caseUploadId": 456,
  "templateId": 1
}

兼容逻辑:

  1. productSource 时使用新逻辑;
  2. 只有 productId 时映射为 library_product
  3. ProductSourceResolver 完成所有权、状态、确认状态和公司状态检查;
  4. generate_copy()generate_poster() 不再直接各自拼装产品规则;
  5. 创建 PosterRecord 时保存产品快照。

建议错误码:

错误码 含义
4101 小册子文件不合法
4102 小册子需要密码或密码错误
4103 小册子解析中
4104 小册子解析失败
4105 小册子尚未确认
4106 产品资料不存在或无权访问
4107 产品资料已删除或停用

六、解析与确认流程

上传 PDF
  → prepare_pdf_upload 安全校验和解密
  → 计算 sha256
  → 持久化原文件
  → 创建 material状态 queued
  → Celery 解析
  → parse_manual_pdf
  → 保存 parsed_rules状态 parsed
  → 用户查看原 PDF + 修改结构化字段
  → 服务端 Schema 校验
  → 保存 confirmed_rules、确认人和确认时间
  → 可用于海报并进入“我的资料”

6.1 解析复用

直接复用:

  • insurance.utils.security.prepare_pdf_upload
  • insurance.poster.manual_parser.parse_manual_pdf
  • insurance.generation.celery_tasks 的 Celery 基础设施;
  • insurance.config.get_storage_root
  • BaoDan 数据库、Redis、日志和认证。

新增 parse_user_product_material_task(material_id),状态流转:

pending → queued → parsing → parsed → confirmed
                          └→ failed

数据库字段仍使用 parsed + confirmed_at 表示最终确认,不必为了展示再增加 confirmed 状态值。

6.2 确认 Schema

首期至少要求:

  • product_name 非空;
  • features 为 18 项;
  • 每项卖点包括 titlesummary,尽量保留 source_page
  • currency_options 为字符串数组;
  • coverage_highlights 为字符串数组;
  • risk_warnings 为字符串数组;
  • investment_rules 为对象;
  • 所有字符串限制长度,禁止把整份 PDF 原文写入结构化字段。

用户替换原 PDF 或重新解析后,必须清空旧 confirmed_rulesconfirmed_at,强制重新确认。

6.3 Prompt 安全

  • PDF 内容一律视为待抽取数据,不视为系统指令;
  • 系统 Prompt 明确忽略 PDF 内的指令、链接和要求;
  • 结构化输出必须做服务端 Schema 校验;
  • 不把 PDF 全文、密码、客户敏感数据或模型密钥写入日志;
  • 对超长 PDF 使用页级筛选和最大上下文限制;
  • 解析失败应返回结构化错误,不向前端暴露内部堆栈。

七、前端交互

7.1 产品资料面板

将当前“选择产品”抽屉改为两个标签:

  1. 系统产品:现有后台已审核产品;
  2. 我的资料:当前用户已上传并确认的小册子。

固定提供:

  • “上传产品小册子”按钮;
  • 搜索无结果时显示“没有找到?上传小册子”;
  • 我的资料卡片展示产品名、保司、解析状态、更新时间;
  • 支持查看原 PDF、编辑确认结果、替换、删除。

7.2 上传对话框

步骤:

  1. 选择 PDF可选输入密码
  2. 上传并显示真实解析状态;
  3. 左侧预览 PDF右侧核对解析字段
  4. 点击“确认并使用”;
  5. 自动加入“我的资料”并选中。

解析可能超过页面停留时间,用户关闭弹窗后任务仍应继续;再次进入“我的资料”可恢复状态。

7.3 草稿状态调整

PosterDraft 增加:

productSourceType: 'library_product' | 'user_material' | ''
productSourceId: string
productSourceConfirmedAt: string

兼容保留 productId,但新流程不再仅以 productId 判断是否完成产品选择。

当用户更换产品来源时:

  • 清空已生成文案;
  • 将合规确认置为未确认;
  • 如果客户计划书与旧产品绑定,提示并要求重新确认;
  • 不直接删除已上传客户计划书。

7.4 文案调整

当前“计划书与数据”建议改为“客户计划书与数据”,上传提示明确写“客户计划书 PDF”。

产品资料区域明确写“产品小册子”,避免用户把同一个文件传错入口。


八、代码改动清单

8.1 后端

文件 改动
api/insurance/db/migrate_026.py(以开发分支启动时的下一个可用编号为准) 新表、兼容字段、索引和幂等迁移
api/insurance/models/user_product_material.py 用户产品资料模型
api/insurance/models/__init__.py 注册模型
api/insurance/poster/product_material_service.py 上传、查询、确认、删除和所有权校验
api/insurance/poster/product_source_resolver.py 统一公共产品与用户资料
api/insurance/poster/routes.py 新增产品资料和产品来源接口
api/insurance/poster/service.py 文案/海报生成改用统一 resolver
api/insurance/generation/celery_tasks.py 新增用户小册子解析任务
api/insurance/models/poster_case_upload.py 保存产品来源引用
api/insurance/models/poster_record.py 保存产品来源和产品快照
api/insurance/utils/security.py 仅在现有能力不足时补充限制,不重写上传校验

当前仓库的下一个迁移编号是 026但《海报生成工作台完整重构修复计划》也预留了该编号。若两项工作进入同一发布版本应合并为一个迁移若分支独立交付后合并的分支必须顺延编号。海报工作台 V2 的 poster_project_assets 已落地时,在生成项目时把用户资料注册为:

asset_type=product_manual
source_type=library
source_ref_id={user_material_id}

8.2 前端

文件 改动
frontend/src/utils/poster-api.ts 产品资料上传、查询、确认、删除、重试接口
frontend/src/composables/usePosterWorkspace.ts 增加产品来源状态和兼容恢复
frontend/src/components/poster/workspace/PosterProductPanel.vue 系统产品/我的资料双标签
frontend/src/components/poster/workspace/UserProductMaterialDialog.vue 新增上传、轮询、核对弹窗
frontend/src/components/poster/workspace/PosterSourcePanel.vue 文案改为客户计划书,解除“必须先选公共 productId”假设
frontend/src/pages/PosterPage.vue 生成请求发送 productSource

8.3 测试

建议新增:

tests/poster_product_material_model_test.py
tests/poster_product_material_api_test.py
tests/poster_product_material_parse_test.py
tests/poster_product_source_resolver_test.py
tests/poster_product_material_permission_test.py
tests/poster_product_material_storage_test.py
tests/poster_user_manual_e2e_test.py

九、可执行排期

Phase 0需求冻结和样例准备0.5 人日)

  • MANUAL-0001 确认“用户资料默认私有”;
  • MANUAL-0002 确认是否允许用户确认后立即生成;
  • MANUAL-0003 准备 3 份脱敏样例:文本 PDF、扫描 PDF、加密 PDF
  • MANUAL-0004 固化解析字段 Schema 和验收期望;
  • MANUAL-0005 确认保留期、单文件大小和页数上限。

完成标准:三份样例和预期 JSON 入库到测试夹具,关键产品决策有书面结论。

Phase 1数据、存储和基础 API1.5 人日)

  • MANUAL-0101 编写幂等迁移;
  • MANUAL-0102 新增模型和 to_dict()
  • MANUAL-0103 实现用户内 SHA-256 去重;
  • MANUAL-0104 实现上传、列表、详情、预览和软删除;
  • MANUAL-0105 实现文件路径和所有权校验;
  • MANUAL-0106 增加迁移、存储和权限单元测试。

完成标准:重启 API/Worker 后文件仍存在,用户 A 无法查看、下载或删除用户 B 的资料。

Phase 2异步解析和人工确认1.5 人日)

  • MANUAL-0201 新增用户资料 Celery 任务;
  • MANUAL-0202 复用 parse_manual_pdf()
  • MANUAL-0203 增加 Prompt 注入防护和输出 Schema 校验;
  • MANUAL-0204 实现轮询、失败原因和重试;
  • MANUAL-0205 实现确认接口和确认失效规则;
  • MANUAL-0206 覆盖加密、损坏、空文本、超限和扫描件场景。

完成标准:上传后异步完成解析;失败可重试;未确认资料不能进入生成。

Phase 3海报链路接入1.0 人日)

  • MANUAL-0301 实现 ProductSourceResolver
  • MANUAL-0302 改造 generate_copy()
  • MANUAL-0303 改造 generate_poster()
  • MANUAL-0304 为计划书和海报记录保存产品来源与快照;
  • MANUAL-0305 验证旧 productId 请求保持兼容;
  • MANUAL-0306 增加跨用户引用和历史快照测试。

完成标准:公共产品和本人资料均可生成;用户资料更新不改变已生成海报的产品快照。

Phase 4前端交互1.0 人日)

  • MANUAL-0401 产品选择抽屉增加双标签;
  • MANUAL-0402 新增上传、解析进度、预览和核对弹窗;
  • MANUAL-0403 支持从“我的资料”复用、替换和删除;
  • MANUAL-0404 修正产品小册子/客户计划书文案;
  • MANUAL-0405 产品来源变化时清理失效草稿状态;
  • MANUAL-0406 PC 和移动端基本回归。

完成标准:用户无需管理员帮助即可完成“上传小册子 → 解析 → 确认 → 生成海报 → 下次复用”。

Phase 5测试、灰度和上线0.5 人日)

  • MANUAL-0501 执行后端权限、存储和兼容回归;
  • MANUAL-0502 执行前端端到端流程;
  • MANUAL-0503 增加 POSTER_USER_MANUAL_UPLOAD_ENABLED 功能开关;
  • MANUAL-0504 管理员和 5% 用户灰度;
  • MANUAL-0505 观察解析成功率、耗时、重试率和存储增长;
  • MANUAL-0506 更新 API、测试、部署和任务清单文档。

完成标准P0/P1 用例通过;关闭功能开关可立即回到旧产品选择流程;旧海报可继续查看和下载。

可选 Phase 6提交公共库审核1.52 人日)

  • MANUAL-0601 用户提交审核;
  • MANUAL-0602 管理端待审列表和 PDF/解析结果对照;
  • MANUAL-0603 管理员修改、批准和驳回;
  • MANUAL-0604 批准后创建或合并 PptProduct
  • MANUAL-0605 记录来源资料、审核人和审核时间;
  • MANUAL-0606 同产品重复提交检测。

首期不应把该阶段作为“用户能生成海报”的阻断项,除非合规制度明确要求所有产品必须管理员预审。


十、测试与验收

10.1 P0 验收

  • 不选择公共产品也能上传产品小册子;
  • 原 PDF 保存在持久化 storage服务重启后仍可预览
  • 加密 PDF 密码只用于本次请求;
  • 解析任务页面关闭后继续执行;
  • 用户可以修改并确认解析结果;
  • 未确认资料不能生成文案或海报;
  • 确认后的资料出现在“我的资料”并可跨项目复用;
  • 用户 A 无法枚举、预览、引用、修改或删除用户 B 的资料;
  • 公共产品原流程保持可用;
  • productId API 调用保持兼容;
  • 历史海报使用产品快照,不受资料后续修改影响。

10.2 P1 验收

  • 同一用户重复上传相同 PDF 不重复调用模型;
  • 替换 PDF 后旧确认自动失效;
  • 解析失败可查看可理解原因并重试;
  • 危险 PDF、伪造扩展名、超大小和超页数被拒绝
  • PDF 中的 Prompt 注入文本不会改变解析任务;
  • 日志中没有 PDF 密码、完整正文和模型密钥;
  • 删除资料不会破坏历史海报;
  • 解析成功率、P95 耗时和存储增长可监控。

10.3 建议性能指标

指标 建议目标
上传接口 P95 小于 3 秒,不含客户端网络上传时间
解析任务成功率 大于 95%,扫描件单独统计
文本 PDF 解析 P95 小于 120 秒
重复上传模型调用 0 次
跨用户访问成功数 0
服务重启后文件丢失 0

十一、风险与处理

风险 影响 应对
用户资料被误当公共产品 数据泄露、合规风险 独立表、默认私有、所有权校验
PDF 解析结果不准确 错误产品卖点进入文案 人工确认、来源页、未确认阻断
扫描 PDF 无文本 解析失败率高 首期明确提示P1 接入 OCR
重复上传导致模型成本增加 成本浪费 用户内 SHA-256 去重
文件只存容器临时盘 重启后丢失 强制共享持久化卷和 readiness 检查
资料修改影响历史海报 审计不可追溯 生成时保存产品快照
用户删除资料导致历史断链 旧项目无法恢复 软删除、引用保护、延迟物理清理
在现有 PptProduct 上补权限遗漏 跨用户暴露 不复用全局产品表保存私有资料
与海报工作台 V2 迁移冲突 重复表和迁移编号 用户资料作为长期资料库,项目素材只保存引用和快照

十二、上线决策项

开发开始前必须确认:

编号 决策 推荐默认值
D-01 用户资料是否默认私有
D-02 用户确认后能否立即生成 能,仅限本人使用
D-03 是否自动进入公共产品库 否,必须显式提交并管理员审核
D-04 扫描 PDF 是否首期支持 OCR 否,先提示用户上传可检索 PDF
D-05 文件默认保留期 活跃资料保留至用户删除,删除后 30 天回收
D-06 单文件大小和页数 沿用 prepare_pdf_upload() 当前配置
D-07 是否允许无保司资料 允许上传,确认前必须补齐或选择“其他”
D-08 删除后历史海报是否保留文件 是,保留至项目生命周期结束

十三、完成定义

只有以下条件全部满足,本需求才可标记完成:

  • 用户能在海报产品选择区上传系统中不存在的小册子;
  • 小册子原文件、解析结果、确认结果和确认信息均保存在服务端;
  • 上传资料默认只对本人可见;
  • 用户确认后可以立即用于文案和海报生成;
  • 下次新建海报时可以从“我的资料”直接选择;
  • 公共产品和旧接口不受影响;
  • 生成记录保存产品快照;
  • 文件、权限、解析、兼容和端到端自动化测试通过;
  • 生产环境 API 与 Worker 使用同一持久化存储;
  • 功能支持灰度开关和无损回退。