feat: 优化图片资源删除管理,生图任务列表管理

This commit is contained in:
2026-08-30 14:50:33 +08:00
parent 4ee667142b
commit 957289f717
46 changed files with 4180 additions and 524 deletions
+281
View File
@@ -0,0 +1,281 @@
# 后台任务中心:设计、实施状态与路线图
> 更新:2026-08-30
> 适用范围:套图生成、单张 AI 生图 / AI 编辑、批量添加水印,以及未来的耗时导出和批处理。
> 阅读顺序:接手相关开发前,先读本文,再读 [`image-asset-lifecycle.md`](./image-asset-lifecycle.md) 与 [`api.md`](./api.md)。
## 1. 结论与边界
“单图弹窗等待”、“套图生成”和“批量水印”本质都是耗时、可失败、需要恢复的后台操作,不应分别实现异步逻辑。项目统一采用:
```text
Web API
└─ 在数据库创建父任务 + 子项,立即返回任务 ID
独立 Worker
└─ 领取一个子项 → 调模型 / 合成水印 / 保存图片 → 回写进度与结果
试算页任务入口(当前)/ 任务中心(后续)
└─ 查询状态、恢复进度、取消、重试、打开结果
```
当前是本地单机、SQLite 架构,**不引入 Redis、Celery 或分布式队列**。数据库驱动的独立 Worker 已能覆盖断开弹窗、刷新页面、Web 服务重启、模型长耗时和失败重试,复杂度与收益最平衡。未来扩展到多机器或多实例时,再迁移 PostgreSQL 并评估 Redis / Celery / RQ 等队列。
本文中的“任务中心”指统一的数据模型、调度和用户可见的任务体验;不意味着每一种任务各有一套后台实现。
---
## 2. 产品体验
### 2.1 目标体验
单张“AI 生图 / AI 编辑”弹窗只负责配置,不负责等待:
1. 用户填写要求、选择模型,点击“提交任务”。
2. 页面立即提示“已加入任务队列”,弹窗关闭;用户可继续编辑商品或离开当前页面。
3. 页面提供“处理中(N)”入口,任务完成后图片自动出现在“生图记录”。
4. 套图与批量水印同样进入任务中心,可查看进度、失败原因、取消、重试和结果。
单张 AI 生图和 AI 编辑保留不同入口、提示词与来源图,但后端都属于 `image_edit` 单图生成任务;区别只在任务快照和标题。
### 2.2 统一状态
| 用户看到的状态 | 父任务 / 子项状态 | 含义 |
|---|---|---|
| 排队中 | `queued` | 等待 Worker 领取。 |
| 处理中 | `running` | 正在生成、下载或合成水印。 |
| 等待模型结果 | 子项 `waiting_provider` | 已提交给第三方模型,Worker 后续轮询同一个模型任务。 |
| 已完成 | `done` | 所有子项成功。 |
| 部分完成 | `partial` | 至少一个成功,且有失败或取消项。 |
| 已失败 | `failed` | 没有成功项,失败原因可查看与重试。 |
| 已取消 | `cancelled` | 用户取消;后续结果不会回写商品。 |
### 2.3 当前已呈现的界面
- 单图弹窗提交成功后关闭,生成进度由“生成结果”区域继续跟踪。
- 套图在“生成结果”区域展示排队、进行、完成、取消、失败重试。
- 水印设置浮层展示本次批量水印进度、取消、全部失败项重试和单图重试。
- 试算页右下角“录入商品”上方有固定“任务 / 处理中(N)”入口;点击打开当前商品的右侧任务抽屉,统一显示套图、单图与水印任务。
- 两类任务均会在刷新试算页后恢复跟踪。
**尚未完成的是应用级全局“任务中心”**:当前抽屉只展示当前商品,且生成结果区和水印设置浮层仍保留上下文状态提示。见第 9 节第三阶段。
---
## 3. 统一数据模型
### 3.1 已实现表
#### `background_jobs`(父任务)
一条用户可感知的后台操作,关键字段包括:
| 字段 | 用途 |
|---|---|
| `id`、`product_id`、`kind` | 任务身份、归属商品和任务类型。商品删除后 `product_id` 置空,避免任务反向复活商品。 |
| `status`、`priority`、`cancel_requested` | 调度与取消控制。 |
| `provider`、`model`、`platform`、`lang`、`ratio`、`style_set` | 生图任务的可恢复执行上下文。 |
| `request_payload` | 不可变请求快照:商品上下文、参考图、提示词、风格、水印配置等。 |
| `total`、`completed`、`failed`、`error` | 整体进度和概要错误。 |
| `created_at`、`started_at`、`completed_at`、`updated_at` | 任务历史、排序和诊断。 |
#### `background_job_items`(子项)
可独立领取、失败和重试的最小单元:
| 场景 | 父任务 | 子项 |
|---|---|---|
| 套图生成 | 一次套图请求 | 一张计划生成图 |
| 单张 AI 生图 / 编辑 | 一次编辑请求 | 唯一的一张图 |
| 批量添加水印 | 一次批量操作 | 一张商品素材 |
子项保存 `payload`、状态、尝试次数、租约、结果 URL、生成回写素材 ID、错误、下次执行时间,以及第三方 `provider_task_id` / `provider_task_kind`。
### 3.2 任务类型
| 类型 | 当前状态 | 说明 |
|---|---|---|
| `image_suite` | 已实施 | 套图按“每张图一个子项”执行。 |
| `image_edit` | 已实施 | 单张 AI 生图 / AI 编辑。 |
| `watermark_batch` | 已实施 | 已选素材逐张生成当前水印版。 |
| `export_zip` / 批量下载 | 待开发 | 可复用同一父/子任务模型处理长时间 ZIP。 |
| 孤儿文件清理 | 待开发 | 后台扫描并回收存储异常残留。 |
### 3.3 可选诊断表(未实施)
未来新增 `background_job_events`,记录“提交模型”“获取 provider task ID”“轮询失败”“租约恢复”“用户取消”“人工重试”等事件。它不参与调度,作用是排障、审计和任务中心时间线;当前诊断信息主要存于子项错误文本和服务日志。
---
## 4. 执行、恢复与一致性
### 4.1 Worker 执行流程(已实施)
`start.command` 会额外启动 `python server/worker.py`。Worker 通过数据库租约领取一个符合条件的子项:
```text
queued / waiting_provider
→ Worker 写入 running + 租约
→ 执行一次推进
├─ 模型未完成:持久化 provider_task_id → waiting_provider → 延迟下次轮询
├─ 成功:保存图片 / 回写素材 → done
├─ 可诊断错误:failed
└─ 已取消或商品不存在:cancelled
→ 从所有子项重新计算父任务进度与终态
```
Worker 异常退出时,过期租约会重新回到队列;Web 服务重启不影响已提交的数据库任务。
### 4.2 第三方模型恢复(已实施范围)
- **Pcore、RightAPI、通义万相**:提交后先持久化 `provider_task_id`,Worker 重启后继续轮询同一模型任务,不重复扣费。
- **豆包、同步千问等同步接口**:供应商不提供可恢复的外部任务 ID。若进程恰好在请求期间中断,只能重试,无法保证供应商侧幂等。
- **仍待完善的极小窗口**:若“已向供应商提交”与“成功写入 `provider_task_id`”之间进程崩溃,当前无法完全避免重复提交。第三阶段应引入供应商幂等键;不支持幂等键时,把子项标为“待人工确认”,不盲目重复扣费。
### 4.3 图片生命周期配合(已实施)
批量水印任务创建时会冻结:
- `asset_id`;
- 当时的 `current_storage_key` / 当前图片 URL;
- 水印配置快照。
Worker 始终基于素材 `original` 无水印版本合成新的 `current` 水印版。执行时若素材已删除或 current 版本已经变化,子项失败而不会用旧任务覆盖新图片;单项重试会重新冻结该素材的最新 current 版本。数据库成功切换到新 current 后,才尝试删除旧水印对象。
详细的原图、当前版、删除回收规则见 [`image-asset-lifecycle.md`](./image-asset-lifecycle.md)。
### 4.4 删除与取消(已实施)
删除商品前会取消该商品所有未完成任务。Worker 在落盘和回写素材前再次检查取消标志与商品是否存在;若商品或素材已删除,只丢弃/清理本次临时结果,绝不“复活”商品或素材记录。
---
## 5. 调度与优先级
### 当前实现
当前为一个独立 Worker 进程,按子项粒度领取,排序为优先级优先、再按创建时间:
| 类型 | 当前优先级 | 含义 |
|---|---:|---|
| `image_edit` | `10` | 用户临时单图编辑优先。 |
| `image_suite` | `0` | 常规套图。 |
| `watermark_batch` | `-10` | 低优先级,不应优先于模型生成。 |
这已避免“十张套图作为整体长期占住任务”的问题:套图也是逐张领取,单图编辑可插队。
### 后续目标(未实施)
AI 生成与水印不应长期共用单线程吞吐:
1. AI 按供应商设置串行或小并发及速率限制;单图编辑优先。
2. 批量水印使用独立逻辑队列 / Worker 池,建议并发约 2,不阻塞模型任务。
3. 调度增加公平性:连续处理 2–3 个高优先级单图后,给套图一个执行机会,避免套图饿死。
在 SQLite 单机阶段,这可先通过两个启动参数不同的 Worker 和类型过滤实现;迁移 PostgreSQL 后再做跨进程可靠并发领取。
---
## 6. API 与前端契约
### 当前接口
| 接口 | 返回 / 用途 |
|---|---|
| `POST /api/suite/generate` | 返回 `{ suite_id }`,创建 `image_suite`。 |
| `POST /api/suite/image-edit` | 返回 `{ suite_id }`,创建 `image_edit`。 |
| `POST /api/assets/watermark` | 返回 `{ job_id, total }`,创建 `watermark_batch`。 |
| `GET /api/suites/{id}` | 通用任务详情、父任务进度、逐项状态和错误。 |
| `GET /api/products/{id}/suites?active_only=true` | 恢复活动的套图 / 单图生成任务。 |
| `GET /api/products/{id}/watermark-jobs?active_only=true` | 恢复活动的水印任务。 |
| `POST /api/suites/{id}/cancel` | 取消任务中尚未完成的子项。 |
| `POST /api/suites/{id}/retry` | 重试全部失败子项。 |
| `POST /api/suites/{id}/items/{item_id}/retry` | 仅重试指定失败子项。 |
`GET /api/suites/{id}` 是历史命名;它已经承载水印任务。全局任务中心落地时应新增语义更通用的 `GET /api/background-jobs/{id}`(保留旧接口兼容)。
### 当前前端轮询
前端使用递归 `setTimeout`,在上一轮请求完成后才安排下一轮;网络失败采用指数退避并使用 `AbortController` 取消页面卸载或任务切换后的请求。浏览器会保存最近任务 ID,刷新后还会从服务端查询商品活动任务,因此不依赖单次页面生命周期。
---
## 7. 已完成阶段
| 阶段 | 内容 | 状态 |
|---|---|---|
| 第一阶段:AI 任务可靠性 | 任务表与迁移;独立 Worker;套图与单图编辑迁入持久化任务;provider task ID 持久化;取消、失败重试、刷新恢复。 | ✅ 已完成 |
| 第二阶段:批量水印复用 | `watermark_batch` 逐素材子项;后台合成和版本切换;整体进度、取消、全部失败重试、单项重试、刷新恢复。 | ✅ 已完成 |
| 数据安全配合 | 商品删除取消任务;写入前二次检查;水印任务冻结素材 current 版本,拒绝旧任务覆盖新状态。 | ✅ 已完成 |
| 第三阶段:前端任务中心(第一步) | 当前商品固定任务入口、右侧任务抽屉、任务进度、取消、失败重试、跳转生成结果。 | ✅ 已完成 |
已验证:Python 编译、前端 TypeScript/生产构建;模拟存储下的“创建水印任务 → Worker 完成 → 素材 current 回写”以及“单个失败项按最新版本重试”。真实模型与七牛仍应按第 10 节进行人工回归。
---
## 8. 当前限制
- 任务中心 UI 尚未独立成页面或全局抽屉,任务入口仍分散在生成结果和水印设置中。
- `background_job_events` 尚未建立,复杂问题主要依赖日志与错误摘要。
- 当前一个 Worker 统一领取 AI 与水印任务,优先级有效,但没有独立并发池和公平调度。
- 移除水印仍是短时同步接口;大量素材场景可迁入 `watermark_batch` 同一框架。
- SQLite 适合本机单用户,不适合多机器或高并发抢占。
- 同步供应商无法做到提交中的完全幂等恢复;异步供应商的“提交后未保存 provider ID”窗口尚未用幂等键闭合。
- 历史任务和失败临时文件尚未设置保留期与定期清理策略。
---
## 9. 待开发阶段
### 第三阶段:前端任务中心后续完善
1. **已完成**:在试算页右下角“录入商品”上方提供“任务 / 处理中(N)”入口,打开当前商品任务抽屉。
2. **已完成**:抽屉合并套图、单图编辑、水印任务,展示逐图失败、取消、重试和“查看结果”。
3. 待开发:按类型、状态筛选,展示更细的 `waiting_provider` 时间线与预计耗时。
4. 待开发:新增通用列表接口,例如 `GET /api/background-jobs?product_id=&status=&kind=`;旧 `/suites/*` 继续兼容。
5. 待开发:单图弹窗提交后提供“查看任务”快捷入口,并逐步移除弹窗自身的轮询职责。
### 第四阶段:可靠性、调度与诊断
1. 新增 `background_job_events` 与结构化日志,记录供应商、模型、耗时、错误类别和重试。
2. 引入供应商幂等键或“待人工确认”状态,关闭提交模型后的重复扣费窗口。
3. 拆分 AI 与水印的逻辑 Worker 池,配置并发、供应商限流和公平调度。
4. 增加任务历史保留期、失败临时文件清理和七牛孤儿对象扫描。
5. 把移除水印、耗时 ZIP 导出、批量下载逐步迁入任务中心。
### 第五阶段:多实例基础设施
前提是产品确实需要多用户、多机器或更高吞吐:
1. SQLite 迁移 PostgreSQL。
2. 采用 PostgreSQL 行级锁 / `SKIP LOCKED` 强化子项领取。
3. 再评估 Redis + Celery、RQ 或专业队列;不要在本地单机阶段提前引入。
---
## 10. 回归与运维清单
每次修改任务系统后,至少验证:
1. `./start.command` 会同时启动 Web、前端和 Worker;手动启动 server 时必须另开 `python server/worker.py`。
2. 单图编辑提交后可关闭弹窗;刷新试算页后仍能看到任务和最终图片。
3. 套图多张任务中插入单图编辑,单图应优先领取,套图不丢进度。
4. 批量水印中删除商品或单个素材,Worker 不得回写/复活资源。
5. 改水印任务执行期间再次修改同一素材,旧任务必须失败或跳过,不能覆盖新 current。
6. Pcore / 通义等异步模型在重启 Worker 后继续轮询原 `provider_task_id`,不再次提交。
7. 失败项的全部重试与单项重试都能恢复状态;单项水印重试使用最新素材版本。
8. 真实存储环境检查旧水印对象仅在新 current 数据库提交后删除;失败时不会丢失原图。
## 11. 代码入口
| 位置 | 责任 |
|---|---|
| `server/models/job.py` | 父任务、子项 ORM 模型。 |
| `server/migrations/versions/b5d2c9a3e1f7_add_background_jobs.py` | 任务表迁移。 |
| `server/services/jobs.py` | 创建、领取、租约恢复、状态聚合、取消与重试。 |
| `server/services/job_worker.py` | 执行生成和批量水印子项。 |
| `server/worker.py`、`start.command` | 独立 Worker 命令与启动编排。 |
| `server/api/suite.py` | 套图、单图编辑和通用任务查询接口。 |
| `server/api/materials.py` | 批量水印创建和水印任务列表。 |
| `studio/src/pages/trial/TrialSuitePanel.tsx` | 试算页任务恢复、轮询、取消与重试入口。 |
| `studio/src/pages/trial/AiImageGenModal.tsx` | 单图任务配置与提交入口。 |