import { BadRequestException, NotFoundException } from '@nestjs/common'; import { extname } from 'node:path'; /** 上传文件的最小信息(兼容 multer 的 Express.Multer.File 与测试替身) */ export interface UploadedFileLike { originalname: string; mimetype: string; path: string; size: number; } /** * 修正 multipart 文件名编码。 * * busboy/multer 按 latin1 解出 `originalname`,含中文/日文/emoji 的文件名会变成 * 乱码(例如 `模型_初音…zip` → `模å_åé³…`)。这里用 latin1 还原原始字节再按 * UTF-8 解一次;只有当结果不含替换字符、且原文确实含高位字节时才替换, * 避免破坏真正的 latin1 文件名。 */ export function decodeUploadName(rawName: string): string { if (![...rawName].some((ch) => ch.charCodeAt(0) > 0x7f)) return rawName; const restored = Buffer.from(rawName, 'latin1').toString('utf8'); if (restored.includes('\uFFFD')) return rawName; return restored; } export function requireUploadedFile(file: UploadedFileLike | undefined, label = 'file'): UploadedFileLike { if (!file) throw new BadRequestException(`缺少上传文件字段 ${label}`); if (!file.path) throw new BadRequestException('上传文件未落盘'); return { ...file, originalname: decodeUploadName(file.originalname) }; } /** 按扩展名白名单解析格式;不在白名单内一律 422 */ export function resolveFormatByExtension( originalName: string, map: Record, kindLabel: string, ): { ext: string; format: string } { const ext = extname(originalName).toLowerCase(); const format = map[ext]; if (!format) { throw new BadRequestException( `${kindLabel}格式不支持:${ext || '(无扩展名)'},允许:${Object.keys(map).join(', ')}`, ); } return { ext, format }; } export function parseOptionalInt(value: unknown, field: string): number | undefined { if (value === undefined || value === null || value === '') return undefined; const num = Number(value); if (!Number.isFinite(num)) throw new BadRequestException(`${field} 必须为数字`); return Math.trunc(num); } export function parseOptionalNumber(value: unknown, field: string): number | undefined { if (value === undefined || value === null || value === '') return undefined; const num = Number(value); if (!Number.isFinite(num)) throw new BadRequestException(`${field} 必须为数字`); return num; } export function parseOptionalBool(value: unknown): boolean { if (value === undefined || value === null || value === '') return false; if (typeof value === 'boolean') return value; return value === 'true' || value === '1' || value === 1; } export function notFound(what: string, id: number | string): NotFoundException { return new NotFoundException(`${what} #${id} 不存在`); }