feat(#1714): complete 请求携带 file_size,修复同名兜底误杀新视频
AI Code Review / AI Code Review (pull_request) Successful in 1m30s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m41s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m45s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 2m47s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 6s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 14s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker 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 / 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 / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (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 / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 9s
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s

completeDirectUpload 请求体新增 file_size,prepareDirectUploadHandle
的 complete 闭包透传 data.file.size(队列/非队列两条链路同时生效)。

后端 complete_direct_upload 的 _find_duplicate_asset 同名兜底分支
需要用 file_size 做大小严格校验:此前前端不传(后端收到 0),
导致 30 分钟内同名视频(如 iPhone IMG_2285.MOV)即使内容/大小全新
也被同名兜底误判重复跳过。

单测:completeDirectUpload 透传 file_size;uploadAssetDirect 老流程
断言 complete body 携带 file_size。
This commit is contained in:
xiaoxia
2026-09-06 12:38:14 +08:00
parent 70dde8cbfb
commit 2c9db4430c
2 changed files with 20 additions and 0 deletions
+4
View File
@@ -32,6 +32,8 @@ export const completeDirectUpload = async (data: {
file_hash?: string
/** 前端上传幂等 token(与 prepare 一致),同一次上传重发 complete 不重复建记录 */
client_upload_id?: string
/** 文件字节数;后端同名兜底去重需用它做大小校验,缺失(=0)时同名记录一律不判重 */
file_size?: number
}): Promise<DirectUploadCompleteResult> => {
// complete 内含 OSS 存在性检查 + 建库 + 派单,放宽到 60s;
// 超时不代表失败(记录可能已建成),调用方禁止超时后盲目重传整个文件
@@ -156,6 +158,8 @@ export const prepareDirectUploadHandle = async (data: {
storage_key: prepared.storage_key,
file_hash: data.fileHash,
client_upload_id: data.clientUploadId,
// 透传文件字节数:后端同名兜底去重依赖大小校验,缺省会导致同名新视频被误判重复
file_size: data.file.size,
}),
}
}
+16
View File
@@ -242,6 +242,20 @@ describe("assets API", () => {
await expect(completeDirectUpload({ name: "test-item" })).resolves.not.toThrow()
})
it("请求体携带 file_size(后端同名兜底去重的大小校验依赖它)", async () => {
await completeDirectUpload({
project_id: "p-1",
library_id: "l-1",
storage_key: "uploads/k.mp4",
file_size: 12345,
} as never)
const completeCalls = mockPost.mock.calls.filter(
([u]: [string]) => u === "/upload/direct/complete",
)
expect(completeCalls).toHaveLength(1)
expect(completeCalls[0][1]).toMatchObject({ file_size: 12345 })
})
it("should reject on API error", async () => {
mockGet.mockRejectedValue(new Error("Network error"))
mockPost.mockRejectedValue(new Error("Network error"))
@@ -354,6 +368,8 @@ describe("assets API", () => {
([u]: [string]) => u === "/upload/direct/complete",
)
expect(completeCalls).toHaveLength(1)
// complete 请求必须带上 file_size,否则后端同名兜底会误杀同名新视频
expect(completeCalls[0][1]).toMatchObject({ file_size: file.size })
XMLHttpRequest.prototype.open = origOpen
XMLHttpRequest.prototype.send = origSend
if (origSetReadyState) {