25 KiB
PPT / 海报多任务持久化工作台改造计划书
一、项目背景
当前 PPT 和海报生成流程存在以下问题:
- 刷新页面后,当前任务 ID 和步骤丢失。
- 关闭页面后,用户无法继续查看任务。
- PPT 最终生成仍依赖长时间 HTTP 请求。
- 海报虽然异步,但依赖 API 进程内后台线程。
- 用户离开生成页面后,无法直观看到任务进度。
- 历史记录主要面向完成后的下载,不是完整任务中心。
- 一个生成页面只能维护一个当前任务。
- 新建或重新开始会清空当前页面状态。
- 用户无法同时挂多个 PPT、海报任务并自由切换。
- 生成过程中继续编辑时,缺少清晰的版本规则。
本次改造目标是把 PPT 和海报从“一次性生成页面”升级为:
支持多个独立工作区、后台可靠执行、刷新恢复、全局任务常驻、任务选择切换、版本化编辑和结果追踪的生成工作台。
二、目标效果
改造完成后,用户可以:
- 同时创建多个 PPT 和海报任务。
- 在任何页面看到正在运行、排队或待处理的任务。
- 点击全局任务坞中的任务,切换到对应工作区。
- 刷新页面、关闭标签页、重新登录后继续处理。
- 离开生成页面后,任务仍在后台运行。
- 查看当前执行阶段、进度和失败原因。
- 在任务生成期间继续编辑下一版草稿。
- 查看当前成品对应的草稿版本。
- 修改后重新生成,不覆盖旧文件。
- 从任务中心找回被关闭或已完成的任务。
- 复制已有任务,生成不同模板、尺寸或脱敏版本。
三、现状与根因
3.1 PPT 页面状态只存在浏览器内存
当前 PPT 页使用 Vue 局部变量保存:
currentStepsessionId
位置:PptPage.vue
刷新组件后,这些变量恢复默认值。后端 Session 虽然存在,但前端失去了关联 ID。
3.2 海报页面状态同样没有持久恢复
海报页把以下内容保存在局部变量:
- 当前步骤
- 产品 ID
- 上传记录 ID
- 模板 ID
- 文案
- 尺寸
- 脱敏选项
海报预览组件中的任务状态和 recordId 也只存在组件内部:
刷新后页面无法重新连接后台任务。
3.3 PPT 最终生成是同步请求
前端调用生成接口后持续等待:
后端在同一 HTTP 请求里完成校验、模板加载、渲染和文件保存:
这会受到:
- 页面刷新
- 网络断开
- 网关超时
- API Worker 回收
- 容器重启
等因素影响。
3.4 海报使用 API 进程内守护线程
当前海报解析和生成使用:
threading.Thread(..., daemon=True)
位置:
浏览器刷新一般不会直接结束线程,但 API 进程或容器重启会中断任务。
3.5 海报存在潜在文件路径问题
海报输出保存到:
<storage_root>/outputs/posters
下载接口却只允许:
uploads/posters
这可能导致海报已完成,但预览或下载仍提示文件不存在。
3.6 历史记录不是任务中心
PPT 历史主要在导出、下载时写入,无法完整展示:
- 草稿
- 解析中
- 待校验
- 排队中
- 生成中
- 失败待重试
因此需要独立的任务执行记录和统一任务查询。
四、最终架构
用户浏览器
│
├── PPT/海报入口页
├── 工作区详情页
├── 全局任务坞
└── 全部任务中心
│
▼
工作区 API + 任务 API
│
┌───────┴────────┐
│ │
▼ ▼
PPT Session Poster Record
可编辑工作区 可编辑工作区
│ │
└───────┬────────┘
▼
insurance_generation_tasks
执行状态、快照、进度、错误
│
▼
BaoDan Celery
│
┌──────┴──────┐
▼ ▼
PPT解析/生成 海报解析/生成
│
▼
持久化文件存储
系统分为三层:
工作区
保存用户持续编辑的业务内容。
任务执行
保存一次解析或生成的输入快照、状态、进度和结果。
成品文件
按照任务 ID 和版本独立保存,避免覆盖。
五、核心业务规则
5.1 用户可以有多个工作区
例如:
PPT A:张先生储蓄险建议书
PPT B:李女士重疾险建议书
海报 C:家庭保障朋友圈海报
海报 D:讲座邀约海报
这些工作区相互独立。
5.2 不同工作区可以同时执行
不同工作区允许:
- 同时解析
- 同时排队
- 同时生成
- 一边生成一边编辑其他任务
实际并发数量由 Celery Worker 控制。
5.3 同一工作区一次只运行一个生成任务
同一工作区处于 queued 或 running 时,不允许再次提交生成。
用户可以:
- 查看当前运行
- 等待完成
- 复制为新工作区后生成另一版本
这可以避免状态、进度和输出文件互相覆盖。
5.4 生成任务使用不可变输入快照
例如:
PPT 工作区当前是草稿版本 3
用户点击生成
任务保存版本 3 的输入快照
用户继续编辑,工作区变成版本 4
版本 3 的生成结果不受版本 4 修改影响。
完成后显示:
当前成品基于版本 3
当前草稿为版本 4
存在未生成修改
六、数据模型设计
6.1 PPT 工作区
继续使用现有 PptSession,新增:
| 字段 | 用途 |
|---|---|
title |
用户可识别名称 |
workflow_step |
当前业务步骤 |
draft_options_json |
模板、公司、脱敏等草稿设置 |
draft_revision |
当前草稿版本 |
generated_revision |
最新成品版本 |
latest_task_id |
最近一次任务 |
latest_output_path |
最新成品路径 |
archived_at |
归档时间 |
updated_at |
最后修改时间 |
原有解析结果、文件、对话和预览字段继续保留。
6.2 海报工作区
扩展现有 PosterRecord,使其在用户开始海报流程时创建,而不是到最终生成时才创建。
新增:
| 字段 | 用途 |
|---|---|
title |
工作区名称 |
workflow_step |
product/upload/template/preview/result |
draft_status |
active/archived |
draft_revision |
当前草稿版本 |
generated_revision |
最新成品版本 |
latest_task_id |
最近任务 |
updated_at |
最后修改时间 |
archived_at |
归档时间 |
现有产品、模板、文案、尺寸和输出字段继续使用。
6.3 统一任务执行表
新增:
insurance_generation_tasks
建议字段:
| 字段 | 用途 |
|---|---|
id |
UUID |
user_id |
所属用户 |
artifact_type |
ppt/poster |
operation |
parse/generate |
workspace_id |
PPT Session 或 Poster Record ID |
title_snapshot |
提交时任务名称 |
status |
queued/running/done/failed/cancelled |
stage |
当前执行阶段 |
progress |
0~100 |
message |
用户可读进度 |
error_code |
结构化错误代码 |
error_message |
用户可读错误 |
input_revision |
本次生成使用的草稿版本 |
input_snapshot_json |
不可变输入快照 |
output_json |
输出路径、页数、预览等 |
idempotency_key |
防止重复提交 |
celery_task_id |
Celery ID |
attempt_count |
重试次数 |
heartbeat_at |
Worker 心跳 |
started_at |
开始时间 |
finished_at |
完成时间 |
viewed_at |
用户查看时间 |
dock_hidden_at |
从任务坞隐藏时间 |
created_at |
创建时间 |
updated_at |
更新时间 |
统一任务表负责执行状态,工作区负责可编辑数据,避免状态职责混乱。
七、状态设计
7.1 工作区步骤
PPT:
upload
parsing
review
ready
generating
result
海报:
product
upload
review
template
preview
generating
result
7.2 任务执行状态
queued
│
▼
running ─────► done
│
└─────────► failed
MVP 支持取消尚未执行的 queued 任务。
运行中的强制取消不进入 MVP,因为外部模型请求通常无法保证立即终止。
7.3 PPT 阶段
validating
normalizing
loading_template
building_slides
rendering
creating_preview
saving
completed
7.4 海报阶段
preparing_data
building_prompt
requesting_image
processing_image
saving
completed
进度必须跟实际阶段更新,不做虚假的自动增长。
八、后台任务改造
8.1 复用 BaoDan Celery
BaoDan 已有 Celery、Redis和 Worker:
自研任务建议放入:
api/insurance/generation/
├── __init__.py
├── celery_tasks.py
├── task_service.py
├── task_query.py
└── task_state.py
任务包括:
parse_ppt_workspacegenerate_ppt_workspaceparse_poster_casegenerate_poster_workspace
8.2 不修改 BaoDan Celery 基座
通过 insurance 初始化过程显式导入自研 task 模块完成注册。
实施前必须验证:
Worker 启动
→ insurance task 出现在 registered tasks
→ API 提交测试任务
→ Worker 成功领取
→ 数据库状态更新
如果未注册,只调整自研初始化和容器启动配置,不直接修改 BaoDan Celery 源码。
8.3 API 立即返回
生成接口只负责:
- 校验工作区
- 检查并发和幂等
- 创建任务记录
- 保存输入快照
- 发送 Celery 任务
- 返回
202 Accepted
不再等待生成完成。
8.4 Worker 幂等领取
Worker 执行前通过数据库事务把:
queued → running
只有成功更新的 Worker 才能执行。
如果任务已经完成,重复投递直接退出。
8.5 重试策略
自动重试:
- 网络超时
- 模型接口 429
- 模型接口 5xx
- Redis/数据库暂时不可用
- 存储暂时不可用
不自动重试:
- 文件格式错误
- 数据校验失败
- 模板不存在
- 配置缺失
- 用户权限错误
建议重试:
最多 3 次
30 秒 → 90 秒 → 270 秒
8.6 Worker 异常恢复
使用:
- late acknowledgement
- Worker 丢失重新投递
- 数据库幂等
- 心跳更新
- 任务软超时和硬超时
- 临时文件写入
- 成功后原子替换
超时判断改用 heartbeat_at,不再使用工作区 created_at。
九、文件存储设计
9.1 PPT
当前 {sessionId}.pptx 容易在重新生成时覆盖。
改为:
outputs/ppt/{userId}/{workspaceId}/{taskId}/result.pptx
outputs/ppt/{userId}/{workspaceId}/{taskId}/preview.pdf
outputs/ppt/{userId}/{workspaceId}/{taskId}/previews/
9.2 海报
改为:
outputs/posters/{userId}/{workspaceId}/{taskId}/result.png
9.3 安全规则
下载接口必须:
- 根据数据库记录解析文件
- 校验
user_id - 校验文件位于配置的 storage root 下
- 不向前端返回服务器绝对路径
- 不使用写死的
uploads/posters判断
十、API 设计
10.1 PPT 工作区
POST /insurance/ppt/workspaces
GET /insurance/ppt/workspaces/{id}
PATCH /insurance/ppt/workspaces/{id}
POST /insurance/ppt/workspaces/{id}/parse
POST /insurance/ppt/workspaces/{id}/generate
POST /insurance/ppt/workspaces/{id}/retry
POST /insurance/ppt/workspaces/{id}/duplicate
POST /insurance/ppt/workspaces/{id}/archive
现有接口暂时保留并转发到新服务,保证兼容。
10.2 海报工作区
POST /insurance/poster/workspaces
GET /insurance/poster/workspaces/{id}
PATCH /insurance/poster/workspaces/{id}
POST /insurance/poster/workspaces/{id}/generate-copy
POST /insurance/poster/workspaces/{id}/generate
POST /insurance/poster/workspaces/{id}/retry
POST /insurance/poster/workspaces/{id}/duplicate
POST /insurance/poster/workspaces/{id}/archive
10.3 统一任务接口
GET /insurance/generation/tasks
GET /insurance/generation/tasks/active
GET /insurance/generation/tasks/{taskId}
POST /insurance/generation/tasks/{taskId}/retry
POST /insurance/generation/tasks/{taskId}/cancel
POST /insurance/generation/tasks/{taskId}/mark-viewed
POST /insurance/generation/tasks/{taskId}/hide-from-dock
10.4 幂等提交
前端每次点击生成时创建:
Idempotency-Key: UUID
同一次网络重试复用相同 Key。
用户明确重新生成或创建新工作区时生成新 Key。
不能只根据输入内容相同就合并任务。
十一、前端多任务工作台
11.1 路由
新增:
/ppt
/ppt/task/:workspaceId
/poster
/poster/task/:workspaceId
/tasks
/ppt 和 /poster 作为入口页,不再代表唯一当前任务。
11.2 PPT/海报入口页
入口页展示:
- 新建任务
- 进行中
- 草稿
- 失败待处理
- 最近完成
创建新任务不会清空旧任务。
11.3 全局任务坞
任务坞挂载在全局布局:
桌面端:
- 页面底部常驻折叠栏
- 横向显示活跃任务
- 当前任务高亮
- 支持打开、隐藏、查看全部
移动端:
- 显示“任务 N”浮动按钮
- 点击打开底部抽屉
每个任务显示:
- 类型
- 名称
- 状态
- 阶段
- 进度
- 失败标记
- 未读完成标记
- 未生成修改标记
11.4 任务坞展示范围
显示:
- 草稿
- 解析中
- 排队中
- 生成中
- 失败待处理
- 完成未查看
“从任务坞关闭”只隐藏,不删除、不终止。
11.5 全局状态管理
项目当前没有 Pinia。
MVP 新增:
frontend/src/composables/useGenerationTasks.ts
使用模块级响应式状态管理:
- 活跃任务
- 当前任务
- 批量刷新
- 未读完成数量
- 打开任务
- 隐藏任务
后端仍是唯一事实来源。
十二、轮询与状态同步
全局任务坞
有活跃任务:每 5 秒批量查询
没有活跃任务:停止轮询
浏览器后台:每 15 秒
重新可见:立即查询
当前详情页
解析或生成中:每 2 秒查询
完成或失败:停止轮询
批量接口
任务坞只能调用一次:
GET /insurance/generation/tasks/active
不能为每个任务分别建立定时请求。
跨标签页
可使用 BroadcastChannel 同步:
- 任务创建
- 草稿更新
- 任务完成
- 任务隐藏
不支持时使用服务器轮询自然恢复。
十三、草稿保存和编辑版本
13.1 自动保存
文本、模板、尺寸等修改后,800 毫秒防抖保存。
显示:
保存中
已保存
保存失败
13.2 乐观锁
保存时提交:
{
"revision": 4,
"draft": {}
}
服务端版本不一致时返回 409 Conflict。
13.3 生成期间继续编辑
允许编辑,但修改只进入下一草稿版本。
正在执行的任务始终读取提交时的 input_snapshot_json。
13.4 本地缓存
如果保留本地临时草稿,Key 必须包含:
用户 ID + 类型 + 工作区 ID
例如:
draft:user-1:ppt:abc123
本地缓存只用于临时容错,不能代替后端保存。
十四、任务命名与选择
PPT 自动名称
客户名称 + 产品名称 + PPT
无法识别时:
PPT任务 2026-07-28 14:30
海报自动名称
产品名称 + 场景名称
例如:
XX储蓄计划 · 朋友圈海报
支持用户手动重命名。
任务名称只用于识别,不改变文件内容。
十五、并发与配额
推荐默认规则:
| 项目 | 默认限制 |
|---|---|
| 活跃草稿 | 50 个/用户 |
| 排队+运行任务 | 5 个/用户 |
| 同一工作区运行任务 | 1 个 |
| 实际同时运行数量 | 由 Worker 并发控制 |
| 超出处理能力 | 保持 queued |
系统允许用户同时提交多个任务,但不承诺全部立即并行。
同一工作区已有任务时,接口返回:
{
"code": 4091,
"message": "当前工作区已有生成任务",
"data": {
"taskId": "...",
"status": "running"
}
}
前端提供:
- 查看当前运行
- 复制为新任务
十六、任务操作定义
| 操作 | 行为 |
|---|---|
| 打开 | 进入工作区 |
| 新建 | 创建独立工作区 |
| 复制 | 复制草稿为新工作区 |
| 从任务坞关闭 | 只隐藏 |
| 归档 | 移出活跃列表,保留数据和文件 |
| 取消排队 | 仅取消 queued |
| 重试 | 创建新的执行记录 |
| 删除 | 二次确认,运行中禁止 |
| 下载 | 下载指定 task 的成品 |
运行中强制停止放在后续版本。
十七、任务排序
任务坞顺序:
- 当前打开
- 失败待处理
- 运行中
- 排队中
- 解析中
- 有未生成修改的草稿
- 完成未查看
任务中心默认按最后更新时间倒序。
十八、通知
MVP:
- 全局任务坞状态变化
- 完成未读标记
- 在线完成消息
- 浏览器标题提示
后续:
- 站内通知
- 邮件
- 企业微信通知
通知失败不能影响任务完成状态。
十九、存储与生命周期
建议初始规则:
活跃草稿:最多 50 个/用户
排队+运行:最多 5 个/用户
完成文件:保留 90 天
失败临时文件:7 天后清理
清理任务必须:
- 只处理满足保留规则的文件
- 通过数据库记录定位
- 校验 storage root
- 不因任务坞隐藏而删除
- 不删除运行中的文件
二十、安全要求
- 所有工作区和任务查询必须校验
user_id。 - 用户不能通过修改 URL 查看他人任务。
- 下载必须基于数据库授权。
- 前端不得获得绝对文件路径。
- 输入快照可能包含客户数据,日志中不能完整打印。
- 任务错误信息对用户脱敏,完整异常写入服务日志。
- 复制任务时重新校验源工作区所有权。
- 管理员任务监控必须使用独立权限。
二十一、实施阶段
阶段 0:复现与技术验证
预计:1 人日。
任务:
- 复现 PPT/海报各阶段刷新。
- 验证海报路径问题。
- 验证 Celery 自研任务注册。
- 验证 API/Worker 共享存储。
- 记录现有数据库状态和文件结果。
验收:
- 能区分页面丢失、HTTP 中断、Worker 中断和文件访问失败。
阶段 1:数据库与工作区
预计:2~3 人日。
任务:
- 新增数据库迁移。
- 扩展
PptSession。 - 扩展
PosterRecord。 - 新增
insurance_generation_tasks。 - 新增工作区查询、保存、重命名、归档接口。
- 兼容旧记录。
验收:
- 多个工作区可独立创建、查询和保存。
阶段 2:Celery 后台任务
预计:3~4 人日。
任务:
- 新增自研 Celery tasks。
- 迁移 PPT 解析。
- 异步化 PPT 生成。
- 迁移海报解析。
- 迁移海报生成。
- 增加状态、阶段、进度、心跳和重试。
- 替换后台线程。
- 修复海报文件路径。
验收:
- 浏览器关闭后任务继续。
- API 重启不影响已进入 Worker 的任务。
- Worker 异常后可重投或重试。
阶段 3:前端刷新恢复与多工作区
预计:3~4 人日。
任务:
- 新增工作区路由。
- 状态驱动当前步骤。
- 恢复草稿和解析结果。
- 新建任务不清空旧任务。
- 增加任务入口页。
- 增加任务复制。
验收:
- 用户可以同时维护多个 PPT 和海报工作区。
阶段 4:全局任务坞与任务中心
预计:2~3 人日。
任务:
- 新增
GenerationTaskDock。 - 新增全局组合式状态。
- 新增活跃任务批量接口。
- 改造历史页为统一任务中心。
- 增加未读完成和失败提示。
- 完成移动端抽屉适配。
验收:
- 用户在任意业务页面都能看到和选择任务。
阶段 5:版本化编辑
预计:2~3 人日。
任务:
- 草稿自动保存。
- 乐观锁。
- 输入快照。
- 草稿版本和成品版本。
- 未生成修改提示。
- 输出文件按 task 隔离。
验收:
- 生成期间编辑不会污染正在执行的版本。
阶段 6:测试与灰度
预计:2~3 人日。
任务:
- 单元测试。
- Celery 集成测试。
- 多任务端到端测试。
- Worker 中断测试。
- 多标签页测试。
- 权限和文件安全测试。
- 数据迁移和旧历史兼容测试。
- 灰度上线与指标观察。
二十二、测试重点
刷新恢复
在以下节点刷新:
- 上传后
- 解析中
- 数据校验中
- 模板选择后
- 排队中
- 生成中
- 完成后
- 失败后
多任务
同时创建:
3 个 PPT
3 个海报
验证:
- 任务相互独立
- 可自由切换
- 进度不会串任务
- 一个失败不影响其他任务
- 创建新任务不清空旧任务
幂等
- 双击生成
- 网络超时后重试
- Celery 重复投递
- Worker 保存文件后中断
- 同一工作区重复提交
跨页面
- 从 PPT 切到智能问答
- 从海报切到产品推荐
- 关闭详情页
- 退出后重新登录
- 换设备继续
编辑版本
- 生成版本 3 时编辑版本 4
- 版本 3 完成后继续下载
- 用版本 4 重新生成
- 两标签页同时修改
- 冲突时返回 409
权限
- 用户 A 无法读取用户 B 的工作区
- 用户 A 无法下载用户 B 的文件
- 修改 URL 不能越权
- 绝对路径不出现在 API 响应中
二十三、最终验收标准
以下全部通过才算完成:
- 用户可以同时创建多个 PPT 和海报工作区。
- 新建任务不会覆盖旧任务。
- 所有活跃任务在全局任务坞可见。
- 用户可以点击任务进行切换。
- 刷新后任务坞和当前工作区自动恢复。
- 关闭页面后后台继续执行。
- PPT 生成不再依赖长时间 HTTP 请求。
- 海报不再依赖 API 进程内线程。
- 不同工作区可以并行或排队。
- 同一工作区不能同时生成两次。
- 双击和网络重试不会产生重复执行。
- 页面显示真实阶段和进度。
- 生成期间可以编辑下一版。
- 正在执行的输入快照保持不变。
- 不同版本文件不会互相覆盖。
- 一个任务失败不影响其他任务。
- 用户可以从任务中心找回隐藏任务。
- Worker 异常后任务可恢复或明确重试。
- 文件预览和下载稳定。
- 用户之间严格隔离。
- 旧历史记录仍可访问。
- 自研代码保持在
api/insurance/和独立 Vue 前端中。 - 不修改 BaoDan 业务基座的 Celery实现。
二十四、工作量评估
| 模块 | 预计工作量 |
|---|---|
| 技术验证 | 1 人日 |
| 数据模型与迁移 | 2~3 人日 |
| Celery 后台任务 | 3~4 人日 |
| 多工作区前端 | 3~4 人日 |
| 全局任务坞与任务中心 | 2~3 人日 |
| 版本化编辑 | 2~3 人日 |
| 测试与灰度 | 2~3 人日 |
| 合计 | 15~21 人日 |
MVP
预计 9~12 人日,包含:
- 多工作区
- 刷新恢复
- Celery 后台执行
- 全局任务坞
- 任务选择切换
- 基础进度
- 文件隔离
- 幂等和权限
完整版
预计 15~21 人日,增加:
- 自动保存
- 乐观锁
- 版本化编辑
- 复制任务
- 完整任务中心
- 通知
- 生命周期清理
- 故障恢复测试
二十五、明确不进入 MVP 的内容
为控制复杂度,以下暂不进入 MVP:
- WebSocket/SSE
- 运行中强制终止外部模型请求
- 手动调整队列优先级
- 管理员拖动任务排序
- 多个任务之间的数据合并
- 同一工作区同时运行多个生成
- 完整的成品版本对比界面
- 新增第二套队列、认证、数据库或日志基础设施
MVP 使用批量轮询、同工作区单执行和 BaoDan 现有 Celery,已经可以完整解决当前问题。
这份计划现在同时覆盖了“任务不会因刷新丢失”和“多个任务常驻页面、可选择切换”两项目标,可直接作为后续开发基线。当前未修改代码。