From c97b58fc5fdce8a1b06a766c964e2396333a1b3d Mon Sep 17 00:00:00 2001 From: wangdl Date: Sat, 4 Jul 2026 12:18:30 +0800 Subject: [PATCH] feat: verify-files --- .../admin-knowledge.controller.ts | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/modules/admin-knowledge/admin-knowledge.controller.ts b/src/modules/admin-knowledge/admin-knowledge.controller.ts index 4e87df3..4bfc402 100644 --- a/src/modules/admin-knowledge/admin-knowledge.controller.ts +++ b/src/modules/admin-knowledge/admin-knowledge.controller.ts @@ -1,6 +1,7 @@ import { Controller, Get, Param, Delete, Query, UseGuards } from '@nestjs/common'; import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; import { PrismaService } from '../../infrastructure/database/prisma.service'; +import { StorageService } from '../../infrastructure/storage/storage.service'; import { enrichWithNames } from '../../common/helpers/name-resolver'; import { AdminAuthGuard } from '../../common/guards/admin-auth.guard'; import { AdminRolesGuard } from '../../common/guards/admin-roles.guard'; @@ -10,7 +11,10 @@ import { AdminRolesGuard } from '../../common/guards/admin-roles.guard'; @UseGuards(AdminAuthGuard, AdminRolesGuard) @ApiBearerAuth() export class AdminKnowledgeController { - constructor(private readonly prisma: PrismaService) {} + constructor( + private readonly prisma: PrismaService, + private readonly storage: StorageService, + ) {} @Get() @ApiOperation({ summary: '知识库列表(管理员,支持搜索)' }) @@ -108,6 +112,31 @@ export class AdminKnowledgeController { }); } + // ── File verification ── + + @Get('verify-files') + @ApiOperation({ summary: '校验所有 source 文件是否存在' }) + async verifyFiles() { + const sources = await this.prisma.knowledgeSource.findMany({ + where: { deletedAt: null, originalObjectKey: { not: null } }, + select: { id: true, originalObjectKey: true, originalFilename: true }, + }); + + const results = await Promise.all( + sources.map(async (s) => { + try { + const info = await this.storage.verifyUpload(s.originalObjectKey!); + return { sourceId: s.id, filename: s.originalFilename, objectKey: s.originalObjectKey, exists: !!info }; + } catch { + return { sourceId: s.id, filename: s.originalFilename, objectKey: s.originalObjectKey, exists: false }; + } + }), + ); + + const missing = results.filter(r => !r.exists); + return { total: results.length, missing: missing.length, missingFiles: missing }; + } + // ── Candidates ── @Get('candidates')