Files
cmshoppe/docs/tasks/T-566.md
T
chengmaandClaude Opus 4.8 fa27134f7c docs(tasks): add T-566~T-569 cover gallery tasks (archive, gallery, regen, nav)
封面历史对比挑选功能拆四任务:T-566 数据层(重置归档改名、
update_generated_cover 状态语义、候选枚举排序、维持批次软删);
T-567 画廊查看+单选保存+原图弹窗;T-568 单任务重生+并发守卫
(含 fresh task 防跳过);T-569 上下条切换+未保存三态拦截。
含 Codex 评审修正与 title/cover committed 处理不对称的设计说明。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 11:13:21 +08:00

6.9 KiB
Raw Blame History

id, title, phase, deps, status, created
id title phase deps status created
T-566 封面历史归档数据层:重置改名保留旧图 + update_generated_cover + 目录枚举 7
T-564
TODO 2026-07-09

问题 / 背景

运营需要对同一商品多次 AI 生图的结果做对比挑选(哪张更适合商品)。当前重置/重生封面时旧图被直接覆盖或删除(reset_generated(delete_file=True) 走 os.remove),历史生成图无法保留,也没有把 DB 封面指针改到某张历史图的手段。

设计定案(grill 评审收口):

  • 不把多条路径塞进 tasks.new_cover_path(一列多值反模式:所有读方按单路径消费、分隔符无安全选择、DB 与磁盘两个真相源必漂移)。new_cover_path 永远只存当前生效的一张。
  • 目录即数据库:历史候选图只存在文件系统,靠文件名约定 {task_id}_{item_id}_new_<时间戳>.jpg 索引,弹窗打开时现场扫目录枚举。
  • 归档钩子放在重置(运营主动、非时间敏感),不放在生图写入热路径(避免在已扣点的成功路径上引入 Windows 文件锁/TOCTOU 新失败点)。

本任务是数据层前置;GUI 画廊弹窗见 T-567。

方案(改哪个文件、改成什么)

app/db.py — reset_generated 归档改名(替代删除)

  • reset_generated(..., reset_cover=True) 时,若 new_cover_path 指向的文件存在:
    • 改名为 {原名去扩展}_{YYYYMMDDHHMMSS}{ext},如 42_51513423876_new.jpg → 42_51513423876_new_20260709094800.jpg;
    • 撞名兜底:目标名已存在(同秒重置两次)则追加 _2/_3;
    • 顺序:先 os.rename 成功、再清 DB 指针;改名抛 PermissionError(Windows 文件被预览/资源管理器占用)→ 整体中止本次重置,抛 DbError(提示"请先关闭正在查看的封面图片再重置"),不留中间态(先清指针后改名失败会导致下次生成覆盖旧图、归档丢失);
    • 旧文件不存在 → 无事发生,照常清指针(幂等,不报错)。
  • delete_file=True 的旧语义处理:归档取代删除(或保留 delete_file 为真删、新增 archive_previous 参数,默认对封面重置开启——具体参数形状实现时定,验收看行为)。
  • 仅重置标题(reset_cover=False)不触碰任何图片文件(既有行为不回归)。

app/db.py — 新增 update_generated_cover(task_id, new_cover_path)

  • 校验任务存在、目标文件存在,目标路径必须是本地文件路径;供 T-567「保存」调用,把 tasks.new_cover_path 指向运营选中的那张(不复制、不改名文件)。
  • 状态语义必须明确:
    • 写入后设置 stage='generated'、status='pending'、last_error=NULL、updated_at=<now>,表示本地已有可用于③更新蝦皮的新封面;
    • 不增加 generate_attempts / apply_attempts,因为这不是一次新的 AI 调用或线上提交;
    • 不改 committed、applied_at、apply_attempts,保留“曾经提交过线上”的历史事实;
    • 不改 image_task_id / image_task_key,它们只记录/续查 cmhub 异步生图任务,封面指针切换不应误清审计信息;真正重置封面仍由 reset_generated(..., reset_cover=True) 清空。
  • 对已 committed=1 或 stage='applied' 的任务,DB 层允许写入并按上面规则退回 generated/pending,由 GUI 在保存前弹“本地换封面不会回滚蝦皮,重复更新会再次提交线上”确认。
  • 有意的不对称,勿"顺手对齐":update_generated_title(db.py:819)对 committed/applied 任务在 DB 层直接拒绝,而本函数允许(退回 + GUI 警示)。这是设计决定:换封面重挑是运营核心场景、走「重置更新状态」同款警示口径(apply.py:311/334);改标题重提交没有此需求。后续不要参照 title 的守卫给本函数加拒绝逻辑,否则 committed 记录重挑封面功能会被改死。

批次软删除边界:不做归档图片清理

  • 不修改 delete_batch() 的软删除语义,不删除 <image_root>/<batch_id>/ 目录。
  • 理由:T-206 已定“删除批次”为软删除,只隐藏本地批次,不物理删除任务与图片;把本任务改成删目录会造成用户误删历史 AI 图和旧封面备份。
  • 归档图片清理如后续需要,另做独立“清理已软删除批次图片”工具,必须有明确二次确认和删除清单预览。

app/image_paths.py — 新增画廊枚举函数

  • list_task_cover_candidates(image_root, task, account=None):扫 <image_root>/<batch_id>/<slug>/ 下匹配 {task_id}_{item_id}_new*.jpg 的文件,返回绝对路径列表;只包含新封面候选,不含 _old 旧封面。
  • 排序规则必须稳定、可测试:
    • 最新优先;
    • 规范名 {task_id}_{item_id}_new.jpg 视为“当前最新输出槽”,排在同任务候选最前;
    • 归档名 {task_id}_{item_id}_new_YYYYMMDDHHMMSS.jpg 按时间戳倒序;
    • 同秒撞名后缀 _2 / _3 按数字倒序;
    • 无法解析时间戳的匹配文件排在最后,并按文件名升序兜底。
  • GUI 默认选中由 DB 当前 new_cover_path 决定,不依赖排序;排序只影响画廊展示顺序。
  • 只读、不建表、不加列——文件系统是唯一真相源。

验收要点

  • 重置封面且旧图存在 → 旧图改名为带 YYYYMMDDHHMMSS 后缀、原规范名腾出、DB 指针已清;同秒二次重置 → 追加 _2 不覆盖。
  • 改名 PermissionError → 本次重置整体失败、DB 指针未清、原文件原名保留(断言不留中间态)。
  • 旧图不存在 → 重置照常成功(幂等)。
  • 仅重置标题 → 不触碰图片文件。
  • update_generated_cover 写入后 stage=generated/status=pending/last_error=NULL;不增加 attempts,不改 committed/applied_at/apply_attempts/image_task_*;目标文件不存在 → 报错。
  • delete_batch 仍只做软删除,不删除 <batch_id>/ 图片目录;归档图片继续保留。
  • list_task_cover_candidates 返回全部 _new* 候选、不含 _old,排序符合“规范名优先、归档时间倒序、撞名数字倒序、异常文件名兜底”的规则。
  • 验证命令(unittest,不引入 pytest):
    • py -3.10 -m unittest tests.test_db tests.test_image_paths
    • python -m ruff check app tests main.py
    • py -3.10 -m unittest discover -s tests
    • git diff --check

边界(不改什么)

  • 不改 tasks 表 schema(不加列、不建子表);new_cover_path 仍为单路径。
  • 不改生图写入链(_save_jpeg/persist_cover_success/规范名 ..._new.jpg);归档只钩在重置。
  • 不改 ③更新/Excel 回写对 new_cover_path 的消费方式。
  • 不做 GUI(画廊弹窗在 T-567)。
  • 不改 CDP/Shopee 交互、AI 请求链路。

执行记录

(做完在这里写:改了什么文件、跑了什么验证命令及结果、遇到的阻塞、关键决策。)