25 KiB
用户上传产品小册子解析与留存实施计划
文档版本:v1.0
编制日期:2026-07-30
适用范围:海报制作中的产品资料选择、产品小册子上传、解析、确认、留存和复用
建议优先级:P0
预计工作量:MVP 约 6 人日,建议预留 20% 风险缓冲
一、结论
本需求不应直接把普通用户上传的小册子写入现有公共 PptProduct 产品库。
推荐新增“我的产品资料库”:
- 用户可以继续选择后台已审核产品;
- 找不到产品时,可以上传产品小册子 PDF;
- 系统持久化保存原 PDF,并异步解析产品名称、产品卖点、保障亮点、币种、投保规则和风险提示;
- 用户必须核对并确认解析结果,确认后才能生成文案和海报;
- 已确认的小册子进入上传者的“我的产品资料”,以后可直接复用;
- 用户资料默认私有,不对其他用户可见;
- 只有显式提交并经管理员审核后,才允许转入公共产品库。
这一路径能复用现有 PDF 安全校验、小册子解析器、Celery、存储根目录和海报生成逻辑,同时避免未经审核的数据污染公共产品库。
二、当前实现与问题定位
2.1 已有能力
| 能力 | 当前实现 | 可复用结论 |
|---|---|---|
| 公共产品和小册子 | PptProduct.manual_file_url、manual_parsed_rules、manual_parse_status |
可作为公共产品库继续使用 |
| 管理员上传小册子 | PptAdminService.upload_manual() |
可复用文件校验和存储思路 |
| 小册子解析 | poster/manual_parser.py::parse_manual_pdf() |
直接复用产品规则提取 Schema |
| 异步任务 | parse_product_manual_task + Celery |
新增用户资料解析任务,不另建队列 |
| 客户计划书上传 | /poster/case-upload、PosterCaseUpload |
继续保留,不能与产品小册子混用 |
| 海报文案和生成 | PosterService.generate_copy()、generate_poster() |
增加统一产品来源解析后复用 |
| PDF 安全校验 | prepare_pdf_upload() |
直接复用大小、魔数、页数、密码处理 |
| 持久化目录 | get_storage_root() |
用户小册子必须写入共享持久化卷 |
2.2 当前阻断点
/poster/products只返回manual_parse_status=reviewed的公共产品。- 海报页面必须先选公共
productId,找不到产品时无法继续。 - 当前
/poster/case-upload上传的是客户计划书,并且强制绑定公共productId。 generate_copy()和generate_poster()都直接查询PptProduct,不认识用户上传资料。- 普通用户没有自己的产品资料实体、列表、详情、删除和复用能力。
- 当前小册子解析只支持管理员维护的公共产品,没有用户所有权校验。
- 前端
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_uploads 和 poster_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 |
是 | |
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
}
兼容逻辑:
- 有
productSource时使用新逻辑; - 只有
productId时映射为library_product; ProductSourceResolver完成所有权、状态、确认状态和公司状态检查;generate_copy()、generate_poster()不再直接各自拼装产品规则;- 创建
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为 1~8 项;- 每项卖点包括
title、summary,尽量保留source_page; currency_options为字符串数组;coverage_highlights为字符串数组;risk_warnings为字符串数组;investment_rules为对象;- 所有字符串限制长度,禁止把整份 PDF 原文写入结构化字段。
用户替换原 PDF 或重新解析后,必须清空旧 confirmed_rules 和 confirmed_at,强制重新确认。
6.3 Prompt 安全
- PDF 内容一律视为待抽取数据,不视为系统指令;
- 系统 Prompt 明确忽略 PDF 内的指令、链接和要求;
- 结构化输出必须做服务端 Schema 校验;
- 不把 PDF 全文、密码、客户敏感数据或模型密钥写入日志;
- 对超长 PDF 使用页级筛选和最大上下文限制;
- 解析失败应返回结构化错误,不向前端暴露内部堆栈。
七、前端交互
7.1 产品资料面板
将当前“选择产品”抽屉改为两个标签:
系统产品:现有后台已审核产品;我的资料:当前用户已上传并确认的小册子。
固定提供:
- “上传产品小册子”按钮;
- 搜索无结果时显示“没有找到?上传小册子”;
- 我的资料卡片展示产品名、保司、解析状态、更新时间;
- 支持查看原 PDF、编辑确认结果、替换、删除。
7.2 上传对话框
步骤:
- 选择 PDF,可选输入密码;
- 上传并显示真实解析状态;
- 左侧预览 PDF,右侧核对解析字段;
- 点击“确认并使用”;
- 自动加入“我的资料”并选中。
解析可能超过页面停留时间,用户关闭弹窗后任务仍应继续;再次进入“我的资料”可恢复状态。
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:数据、存储和基础 API(1.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.5~2 人日)
- MANUAL-0601 用户提交审核;
- MANUAL-0602 管理端待审列表和 PDF/解析结果对照;
- MANUAL-0603 管理员修改、批准和驳回;
- MANUAL-0604 批准后创建或合并
PptProduct; - MANUAL-0605 记录来源资料、审核人和审核时间;
- MANUAL-0606 同产品重复提交检测。
首期不应把该阶段作为“用户能生成海报”的阻断项,除非合规制度明确要求所有产品必须管理员预审。
十、测试与验收
10.1 P0 验收
- 不选择公共产品也能上传产品小册子;
- 原 PDF 保存在持久化 storage,服务重启后仍可预览;
- 加密 PDF 密码只用于本次请求;
- 解析任务页面关闭后继续执行;
- 用户可以修改并确认解析结果;
- 未确认资料不能生成文案或海报;
- 确认后的资料出现在“我的资料”并可跨项目复用;
- 用户 A 无法枚举、预览、引用、修改或删除用户 B 的资料;
- 公共产品原流程保持可用;
- 旧
productIdAPI 调用保持兼容; - 历史海报使用产品快照,不受资料后续修改影响。
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 使用同一持久化存储;
- 功能支持灰度开关和无损回退。