feat(api): upload duplicate pre-check endpoint (checkUploadDuplicates)
「上传查重前置到入队前」Task 1:POST /v1/uploads/duplicates/check 只读批量 查重预检——用户点上传(确认入队)时刻预判服务端历史重复(跨设备/重装/改名 均可查到),不再等任务在上传中心以 PHOTO_DUPLICATE 失败才暴露。 - 契约纯增量:/uploads/duplicates/check + UploadDuplicateCheck(Item|Request| Duplicate|Response) schemas;regen openapi.d.ts。 - 判定谓词与 init ③.5 服务端查重逐字一致:同 albumId + uploaderId=当前用户 + bytes 相等 + visibility != 'deleted'(filename 刻意不参与),保证「预检 即真相」;命中条目按请求下标逐项返回(同 bytes 多请求条目各一条)。 - 权限链同 init(canView 404 防枚举 / canUpload 403 / 相册空值兜底 404), 刻意不校验 uploadEnabled:只读预览,init 真正入队时仍按开关 409 兜底。 - 查询走 idx_photos_dedup:请求条目去重字节集一次 findMany,内存建 bytes→最早 uploadedAt 映射后按请求下标回射。 - items 1..500(minItems/maxItems + 嵌套逐项校验,越界 400)。 - e2e 追加 describe 7 用例:命中含双条目同 bytes/未命中/uploaderId 隔离/ deleted 不命中/非成员 404/items 边界 400/未认证 401。 契约 lint ✅ · contracts+api typecheck ✅ · api 单测 56/56 ✅ · uploads e2e 28/28 ✅ Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
+115
@@ -364,6 +364,31 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/uploads/duplicates/check": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/**
|
||||
* 上传查重预检(批量,只读)
|
||||
* @description 批量查重预检:对每个请求条目判定「同相册 + 同上传者(当前用户)+ 同字节数」
|
||||
* 的非 deleted 照片是否已存在——判定谓词与 initUpload 的 PHOTO_DUPLICATE
|
||||
* 查重逐字一致(含 uploaderId 隔离;filename 刻意不参与)。命中条目按请求
|
||||
* 下标逐项返回(两个请求条目共享同一命中 bytes 时各返回一条)。
|
||||
* 不校验 uploadEnabled:预检是只读预览,相册关闭上传时仍可查询历史重复
|
||||
* (init 在真正入队时仍按开关 409 兜底)。
|
||||
*/
|
||||
post: operations["checkUploadDuplicates"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/uploads/{uploadId}/renew": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -1732,6 +1757,43 @@ export interface components {
|
||||
UploadSessionList: components["schemas"]["CursorPageMeta"] & {
|
||||
items: components["schemas"]["UploadSessionRecord"][];
|
||||
};
|
||||
/** @description 查重预检请求条目(与 initUpload 入参同形;filename 仅元数据)。 */
|
||||
UploadDuplicateCheckItem: {
|
||||
/** @description 原始文件名(仅元数据;查重谓词不使用,与 init 查重一致)。 */
|
||||
filename: string;
|
||||
/**
|
||||
* Format: int64
|
||||
* @description 申明文件字节数(查重键)。
|
||||
*/
|
||||
bytes: number;
|
||||
};
|
||||
/** @description 批量查重预检请求;单批上限 500 项,客户端按 500 分块顺序调用。 */
|
||||
UploadDuplicateCheckRequest: {
|
||||
/** @description 目标相册 ID。 */
|
||||
albumId: string;
|
||||
/** @description 待查重条目(1..500 项)。 */
|
||||
items: components["schemas"]["UploadDuplicateCheckItem"][];
|
||||
};
|
||||
/** @description 单条命中结果;每个命中的「请求条目」一条(index 指向请求数组)。 */
|
||||
UploadDuplicateCheckDuplicate: {
|
||||
/** @description 命中条目在请求 items 数组中的下标(0 起)。 */
|
||||
index: number;
|
||||
/**
|
||||
* Format: int64
|
||||
* @description 命中字节数(与对应请求条目一致)。
|
||||
*/
|
||||
bytes: number;
|
||||
/**
|
||||
* Format: date-time
|
||||
* @description 服务端既有照片的上传时间(同 bytes 多条命中取最早)。
|
||||
*/
|
||||
uploadedAt: string;
|
||||
};
|
||||
/** @description 查重预检结果(命中子集,按请求下标升序)。 */
|
||||
UploadDuplicateCheckResponse: {
|
||||
/** @description 命中子集;无命中为空数组。 */
|
||||
duplicates: components["schemas"]["UploadDuplicateCheckDuplicate"][];
|
||||
};
|
||||
PhotoVisibilityUpdateRequest: {
|
||||
/**
|
||||
* @description 目标可见性;仅允许 hidden 与 published 间切换。
|
||||
@@ -3705,6 +3767,59 @@ export interface operations {
|
||||
500: components["responses"]["InternalError"];
|
||||
};
|
||||
};
|
||||
checkUploadDuplicates: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["UploadDuplicateCheckRequest"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description 查重结果(命中子集;无命中时 duplicates 为空数组)。 */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["UploadDuplicateCheckResponse"];
|
||||
};
|
||||
};
|
||||
/** @description 参数校验失败(VALIDATION_FAILED):items 为空/超 500 项或条目字段非法。 */
|
||||
400: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["Error"];
|
||||
};
|
||||
};
|
||||
401: components["responses"]["Unauthorized"];
|
||||
/** @description 无该相册的上传权限(AUTH_FORBIDDEN)。 */
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["Error"];
|
||||
};
|
||||
};
|
||||
/** @description 相册不可见或不存在(ALBUM_NOT_FOUND,不区分以避免枚举)。 */
|
||||
404: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["Error"];
|
||||
};
|
||||
};
|
||||
500: components["responses"]["InternalError"];
|
||||
};
|
||||
};
|
||||
renewUploadCredential: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
||||
Reference in New Issue
Block a user