import { BadRequestException, ForbiddenException } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; type Delegate = { findUnique(args: { where: any }): Promise; findFirst(args: { where: any }): Promise; }; export async function findByIdAndUserId( delegate: T, id: number | bigint, userId: number | bigint, resourceName: string, ) { const record = await delegate.findUnique({ where: { id } } as any); if (!record) { throw new BadRequestException(`${resourceName}不存在`); } if (record.userId !== userId) { throw new ForbiddenException(`无权访问该${resourceName}`); } return record; } export function ensureOwnership( record: any, userId: number | bigint, resourceName: string, ) { if (!record) { throw new BadRequestException(`${resourceName}不存在`); } if (record.userId !== userId) { throw new ForbiddenException(`无权访问该${resourceName}`); } return record; } export function sanitizeFilename(originalName: string): string { const ext = originalName.split('.').pop()?.toLowerCase() || ''; const safeExt = ext.replace(/[^a-z0-9]/g, ''); const randomName = Date.now().toString(36) + Math.random().toString(36).substring(2, 15); return `${randomName}.${safeExt}`; } export const ALLOWED_FILE_TYPES = [ 'application/pdf', 'text/plain', 'text/markdown', 'text/csv', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'image/png', 'image/jpeg', 'image/webp', ]; export const MAX_FILE_SIZE = 20 * 1024 * 1024; export function validateFileUpload( mimeType: string, sizeBytes: number, ): void { if (!ALLOWED_FILE_TYPES.includes(mimeType)) { throw new BadRequestException( `不支持的文件类型: ${mimeType},仅支持 PDF/Word/Excel/文本/图片`, ); } if (sizeBytes > MAX_FILE_SIZE) { throw new BadRequestException( `文件大小不能超过 ${MAX_FILE_SIZE / 1024 / 1024}MB`, ); } } export function maskSecret(secret: string): string { if (!secret || secret.length < 8) return '***'; return secret.slice(0, 4) + '***' + secret.slice(-4); }