Files
ozon-seller-kit/docs/v2.1/background-jobs.md
T

282 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 后台任务中心:设计、实施状态与路线图
> 更新: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` | 单图任务配置与提交入口。 |