fix(upload): complete 接口幂等去重 + HEVC 转码回写占位 asset 禁止兜底新建 (#1714) #1715

Merged
auto-approve-bot merged 1 commits from feature/upload-complete-idempotency-hevc-orphan-1714 into develop 2026-09-05 16:07:45 +08:00
Owner

背景

Issue #1714(诊断见项目文件 asset_duplicate_upload_rootcause_20260905.md):同一文件被反复走完整上传链路(prepare→OSS直传→complete),13 个素材建 69 条记录;HEVC .MOV 转码后占位 PROCESSING 永久卡住。

本 PR 为后端部分(前端入队去重/超时处理由前端工程师负责)。

P0:complete 接口幂等

  • 三级去重_find_duplicate_asset):
    1. client_upload_id(客户端幂等 token,同一次上传重试保持一致)
    2. file_hash(内容哈希)
    3. 兜底:同库 + 同文件名且 30 分钟内仍处 uploading/processing 的活动记录——旧客户端不传 hash/token 时,complete 超时重试不再重复建占位记录和 ingest job
  • 幂等检查前置到 OSS file_exists 之前:第一次 complete 已建占位后,重试即使 OSS 对象已过期/不存在也返回已存在记录(duplicated=true),不再 404、不重复建库
  • multipart 上传同样支持 client_upload_id,重复提交直接返回、不再传 OSS
  • 新仓储方法全部 getattr 鸭子类型调用,旧仓储/旧 mock 零破坏

P1:HEVC 转码孤儿 PROCESSING 修复

  • ingest job 透传 complete 阶段创建的占位 asset_idingest_jobs.asset_id 新列)
  • worker 关联占位优先级:job.asset_id原始 storage_key → 当前 key;不再用转码改写后的 *_h264 新 key 回查占位(旧逻辑必然落空 → 兜底新建 READY,原 PROCESSING 占位永久卡住,每个 HEVC 视频产生两条记录)
  • 命中占位则原地更新 READY,storage_key 同步改写为 *_h264(播放/下载走转码产物),原始 key 记入 metadata.original_storage_key/hevc_transcoded 可溯源
  • 无效媒体 / ingest 异常路径同样回写占位为 ERROR,不再新建第二条;异常路径从 _h264 key 还原原始 key 多路径查找
  • 真正无占位(旧版本 API / 历史 job 重跑)才兜底新建,且日志降级为 warning
  • job_repo.update 同步 storage_key(转码改写后 DB 一致)

迁移

alembic 066_upload_idempotencyassets.client_upload_id(+索引)、ingest_jobs.asset_id(NOT NULL server_default '',+索引)

测试

新增 14 个单测:

  • test_upload_complete_idempotency_1714.py(8 个):同 token/hash 重复 complete 只建 1 条 asset+1 个 job、无 hash/token 近期同名兜底、READY 历史不兜底、窗口过期不兜底、旧仓储鸭子类型降级、OSS 已缺失仍幂等返回、multipart 重复提交不重复传 OSS
  • test_ingest_hevc_orphan_1714.py(6 个):转码成功占位原地 READY 且不产生重复 READY、job.asset_id 关联、无占位兜底新建兼容、非 HEVC 占位更新、无效媒体占位标 ERROR 不新建、异常路径占位标 ERROR

全量 14281 passed(仅 5 个已知 h2.config TTS 环境失败,与本改动无关);black/isort/ruff 通过;scripts/ci/validate_mypy.sh 通过;迁移 upgrade/downgrade DDL 本地验证通过。

前端对接说明

  • complete 请求新增可选字段 client_upload_id(建议用入队时生成的稳定文件指纹/上传 id,重试必须复用同一个)和 file_size;prepare 已有的 file_hash 建议在 complete 也带上
  • 重复 complete 响应:duplicated=true + 已有 asset_id,前端按成功处理即可,无需重传
## 背景 Issue #1714(诊断见项目文件 `asset_duplicate_upload_rootcause_20260905.md`):同一文件被反复走完整上传链路(prepare→OSS直传→complete),13 个素材建 69 条记录;HEVC .MOV 转码后占位 PROCESSING 永久卡住。 本 PR 为**后端部分**(前端入队去重/超时处理由前端工程师负责)。 ## P0:complete 接口幂等 - **三级去重**(`_find_duplicate_asset`): 1. `client_upload_id`(客户端幂等 token,同一次上传重试保持一致) 2. `file_hash`(内容哈希) 3. **兜底**:同库 + 同文件名且 30 分钟内仍处 uploading/processing 的活动记录——旧客户端不传 hash/token 时,complete 超时重试不再重复建占位记录和 ingest job - **幂等检查前置到 OSS file_exists 之前**:第一次 complete 已建占位后,重试即使 OSS 对象已过期/不存在也返回已存在记录(duplicated=true),不再 404、不重复建库 - multipart 上传同样支持 `client_upload_id`,重复提交直接返回、不再传 OSS - 新仓储方法全部 `getattr` 鸭子类型调用,旧仓储/旧 mock 零破坏 ## P1:HEVC 转码孤儿 PROCESSING 修复 - ingest job 透传 complete 阶段创建的**占位 asset_id**(`ingest_jobs.asset_id` 新列) - worker 关联占位优先级:`job.asset_id` → **原始 storage_key** → 当前 key;不再用转码改写后的 `*_h264` 新 key 回查占位(旧逻辑必然落空 → 兜底新建 READY,原 PROCESSING 占位永久卡住,每个 HEVC 视频产生两条记录) - 命中占位则**原地更新 READY**,storage_key 同步改写为 `*_h264`(播放/下载走转码产物),原始 key 记入 `metadata.original_storage_key/hevc_transcoded` 可溯源 - 无效媒体 / ingest 异常路径同样回写占位为 ERROR,不再新建第二条;异常路径从 `_h264` key 还原原始 key 多路径查找 - 真正无占位(旧版本 API / 历史 job 重跑)才兜底新建,且日志降级为 warning - `job_repo.update` 同步 storage_key(转码改写后 DB 一致) ## 迁移 alembic `066_upload_idempotency`:`assets.client_upload_id`(+索引)、`ingest_jobs.asset_id`(NOT NULL server_default '',+索引) ## 测试 新增 14 个单测: - `test_upload_complete_idempotency_1714.py`(8 个):同 token/hash 重复 complete 只建 1 条 asset+1 个 job、无 hash/token 近期同名兜底、READY 历史不兜底、窗口过期不兜底、旧仓储鸭子类型降级、OSS 已缺失仍幂等返回、multipart 重复提交不重复传 OSS - `test_ingest_hevc_orphan_1714.py`(6 个):转码成功占位原地 READY 且不产生重复 READY、job.asset_id 关联、无占位兜底新建兼容、非 HEVC 占位更新、无效媒体占位标 ERROR 不新建、异常路径占位标 ERROR 全量 **14281 passed**(仅 5 个已知 h2.config TTS 环境失败,与本改动无关);black/isort/ruff 通过;`scripts/ci/validate_mypy.sh` 通过;迁移 upgrade/downgrade DDL 本地验证通过。 ## 前端对接说明 - complete 请求新增可选字段 `client_upload_id`(建议用入队时生成的稳定文件指纹/上传 id,**重试必须复用同一个**)和 `file_size`;prepare 已有的 `file_hash` 建议在 complete 也带上 - 重复 complete 响应:`duplicated=true` + 已有 `asset_id`,前端按成功处理即可,无需重传
xiaoxia added 1 commit 2026-09-05 16:00:26 +08:00
fix(upload): complete接口幂等去重 + HEVC转码回写占位asset禁止兜底新建 (#1714)
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 31s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 32s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m30s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 1m36s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m51s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m16s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m56s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 5m18s
AI Code Review / AI Code Review (pull_request) Successful in 6m15s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 6m36s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 4m18s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 7s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 23s
31cef633ee
P0 complete 幂等:
- complete/上传去重三级:client_upload_id(客户端幂等token) → file_hash →
  同库同名近期(30min)活动占位兜底;旧客户端不传hash/token时,complete
  超时重试不再重复建 PROCESSING 记录和 ingest job
- 幂等检查前置到 OSS file_exists 之前:重复 complete 即使 OSS 对象已不存在
  也返回已存在记录(duplicated=true),不404、不重复建库
- multipart 上传同 token 重复提交直接返回,不再传 OSS
- 新仓储方法全部 getattr 鸭子类型,旧仓储/mock 零破坏

P1 HEVC 转码孤儿:
- ingest job 透传 complete 阶段创建的占位 asset_id;worker 关联优先级
  job.asset_id → 原始 storage_key → 当前 key(旧 key),不再用转码后的
  *_h264 新 key 回查占位(旧逻辑查不到兜底新建 READY,原占位永久 processing)
- 命中占位则原地更新 READY,storage_key 同步改写为 *_h264(播放走转码产物),
  原始 key 记入 metadata(original_storage_key/hevc_transcoded)
- 无效媒体/ingest 异常路径同样回写占位为 ERROR,不再新建第二条
- 异常路径从 _h264 key 还原原始 key 多路径查找
- job_repo.update 同步 storage_key(转码改写后 DB 一致)

迁移:alembic 066(assets.client_upload_id + ingest_jobs.asset_id,含索引)

测试:新增 14 个单测(重复 complete 幂等只建一条/不重复派 job、token/hash/
兜底/窗口/旧仓储降级/OSS缺失、转码占位回写不产生重复 READY、asset_id 关联、
无占位兜底兼容、非HEVC、无效媒体/异常占位标 ERROR);全量 14281 passed

🚀 预览环境已部署

项目 详情
PR号 #1715
预览链接 https://pr-1715.preview.xiaoxiajianji.com
API环境 staging

💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。

🔄 每次提交新代码后预览环境会自动更新。

🗑️ PR 关闭或合并后,预览环境会自动清理。

🚀 **预览环境已部署** | 项目 | 详情 | |------|------| | PR号 | #1715 | | 预览链接 | [https://pr-1715.preview.xiaoxiajianji.com](https://pr-1715.preview.xiaoxiajianji.com) | | API环境 | staging | > 💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。 > > 🔄 每次提交新代码后预览环境会自动更新。 > > 🗑️ PR 关闭或合并后,预览环境会自动清理。
auto-approve-bot merged commit 0a7ae8db4f into develop 2026-09-05 16:07:45 +08:00
auto-approve-bot deleted branch feature/upload-complete-idempotency-hevc-orphan-1714 2026-09-05 16:07:46 +08:00

🗑️ 预览环境已清理

PR #1715 已关闭或合并,对应的预览环境已被清理。

如有需要,可以重新打开 PR 来重新生成预览环境。

🗑️ **预览环境已清理** PR #1715 已关闭或合并,对应的预览环境已被清理。 > 如有需要,可以重新打开 PR 来重新生成预览环境。
Sign in to join this conversation.