# 后台任务中心:设计、实施状态与路线图 > 更新: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` | 单图任务配置与提交入口。 |