feat(uploads): P0-1 batch 4 — server-side duplicate photo detection (409 + force + index)
契约 UploadInitRequest 加 force(bool,仅 owner/admin 跳过查重)。 服务端 init 事务前置查重:同 albumId + uploaderId + bytes 的非 deleted 照片已存在 → 409 PHOTO_DUPLICATE。photos 表加 idx_photos_dedup 索引 (迁移 20260928120000)。 App UploadInitRequest 加 force 字段 + toJson 序列化。 本地查重(层 1)+ 服务端查重(层 2)双层防线到位。 API typecheck ✅ · app analyze ✅ · 397/397 ✅ · contract lint ✅ Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
// 惰性读取,故在服务层校验(同 filename 扩展名白名单与 categoryId 属主校验);
|
||||
// · clientTaskId/filename/albumId/categoryId 列宽对齐 db-cn schema
|
||||
// (VarChar 128/255/64/64);sourceDeviceId 8-64(A19 设备来源语义)。
|
||||
import {
|
||||
import { IsBoolean,
|
||||
IsIn,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
@@ -55,4 +55,9 @@ export class UploadInitRequestDto {
|
||||
@MinLength(8)
|
||||
@MaxLength(64)
|
||||
sourceDeviceId?: string;
|
||||
|
||||
/** P0-1 层 2:跳过服务端查重(仅 owner/admin 生效;协作者无效)。 */
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
@@ -120,6 +120,27 @@ export class UploadsService {
|
||||
if (!album.uploadEnabled) {
|
||||
throw albumUploadDisabled();
|
||||
}
|
||||
|
||||
// ③.5 P0-1 层 2:服务端查重——同相册+同上传者+同字节数的非 deleted 照片
|
||||
// 已存在时 409(force=true 且 owner/admin 可跳过;协作者 force 无效)。
|
||||
if (dto.force !== true || !(await this.permissions.canManageAlbum(user, dto.albumId))) {
|
||||
const duplicate = await this.prisma.photos.findFirst({
|
||||
where: {
|
||||
albumId: dto.albumId,
|
||||
uploaderId: user.id,
|
||||
bytes: dto.bytes,
|
||||
visibility: { not: 'deleted' },
|
||||
},
|
||||
select: { id: true, uploadedAt: true },
|
||||
});
|
||||
if (duplicate !== null) {
|
||||
throw new ApiException(
|
||||
ErrorCodes.VALIDATION_FAILED,
|
||||
'PHOTO_DUPLICATE',
|
||||
HttpStatus.CONFLICT,
|
||||
);
|
||||
}
|
||||
}
|
||||
// ④ 大小限额(惰性读 env)与 filename 扩展名白名单(对象键后缀派生)。
|
||||
const maxBytes = uploadMaxBytes();
|
||||
if (dto.bytes > maxBytes) {
|
||||
|
||||
Reference in New Issue
Block a user