import { Controller, Post, UseGuards, UseInterceptors, UploadedFile, BadRequestException, } from '@nestjs/common'; import { Throttle } from '@nestjs/throttler'; import { FileInterceptor } from '@nestjs/platform-express'; import { diskStorage } from 'multer'; import { extname, join } from 'path'; import { randomUUID } from 'crypto'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; const UPLOAD_DIR = join(process.cwd(), 'uploads'); // Explicit safe-image whitelist. SVG is deliberately excluded: it can // carry scripts and is served from the same origin (stored XSS). const ALLOWED_EXTENSIONS = /\.(png|jpe?g|webp|gif)$/i; const ALLOWED_MIMETYPES = /^image\/(png|jpe?g|webp|gif)$/i; @UseGuards(JwtAuthGuard) @Controller('upload') export class UploadController { @Post('image') @Throttle({ default: { limit: 10, ttl: 60_000 } }) @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: UPLOAD_DIR, filename: (_req, file, cb) => { const ext = ALLOWED_EXTENSIONS.test(extname(file.originalname)) ? extname(file.originalname).toLowerCase() : '.png'; cb(null, `${randomUUID()}${ext}`); }, }), limits: { fileSize: 5 * 1024 * 1024 }, fileFilter: (_req, file, cb) => { if (!ALLOWED_EXTENSIONS.test(file.originalname) || !ALLOWED_MIMETYPES.test(file.mimetype)) { return cb(new BadRequestException('仅支持 png/jpg/webp/gif 图片'), false); } cb(null, true); }, }), ) uploadImage(@UploadedFile() file: Express.Multer.File) { if (!file) { throw new BadRequestException('请选择要上传的文件'); } return { url: `/uploads/${file.filename}`, filename: file.filename }; } }