NestJS v10+中ParseFilePipeBuilder校验错误文件仍被上传问题
NestJS v10+ 实现仅允许PNG文件上传(校验不通过则不上传)
你的问题核心在于:当前代码里Multer会先把文件保存到磁盘,之后才执行ParseFilePipeBuilder的校验,导致错误文件已经被上传到服务器。要解决这个问题,必须让文件校验逻辑在磁盘存储之前执行,下面是两种可行方案:
方案一:用Multer的fileFilter前置过滤
直接在Multer处理文件的阶段就过滤掉非PNG格式的文件,不符合条件的文件根本不会写入磁盘。
修改后的完整代码:
import { Controller, Post, UploadedFile, UseInterceptors, Request, HttpStatus, ParseFilePipeBuilder, BadRequestException, } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { diskStorage } from 'multer'; @Controller() export class UploadController { @Post('/upload') @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: './path', filename: (req, file, cb) => { const uniqueIdentifier: string = new Date().getTime().toString(); return cb( null, `${uniqueIdentifier}${file.originalname.replace(/\s/g, '')}`, ); }, }), // 添加文件过滤逻辑,存储前校验类型 fileFilter: (req, file, cb) => { // 校验MIME类型 if (file.mimetype !== 'image/png') { return cb(new BadRequestException('仅支持PNG格式文件'), false); } // 额外校验文件扩展名(防止篡改MIME类型的情况) const fileExt = file.originalname.split('.').pop()?.toLowerCase(); if (fileExt !== 'png') { return cb(new BadRequestException('仅支持PNG格式文件'), false); } cb(null, true); }, }), ) // 可保留ParseFilePipe做二次校验,双重保障 uploadCv( @UploadedFile( new ParseFilePipeBuilder() .addFileTypeValidator({ fileType: 'png' }) .build({ errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY }), ) file: Express.Multer.File, @Request() req, ) { // 处理上传成功后的逻辑 return { message: '文件上传成功', fileInfo: { filename: file.filename, size: file.size } }; } }
方案二:先存内存,校验通过后再写入磁盘
先把文件临时存在内存中,等ParseFilePipeBuilder校验通过后,再手动将文件写入磁盘,校验失败的文件不会被保存。
修改后的完整代码:
import { Controller, Post, UploadedFile, UseInterceptors, Request, HttpStatus, ParseFilePipeBuilder, } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { memoryStorage } from 'multer'; import * as fs from 'fs'; import * as path from 'path'; @Controller() export class UploadController { @Post('/upload') @UseInterceptors( FileInterceptor('file', { // 将文件临时存储到内存 storage: memoryStorage(), }), ) uploadCv( @UploadedFile( new ParseFilePipeBuilder() .addFileTypeValidator({ fileType: 'png' }) .build({ errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY }), ) file: Express.Multer.File, @Request() req, ) { const uploadDir = './path'; // 确保上传目录存在,不存在则创建 if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir, { recursive: true }); } // 生成唯一文件名 const uniqueIdentifier = new Date().getTime().toString(); const filename = `${uniqueIdentifier}${file.originalname.replace(/\s/g, '')}`; const filePath = path.join(uploadDir, filename); // 校验通过后,手动写入磁盘 fs.writeFileSync(filePath, file.buffer); return { message: '文件上传成功', fileInfo: { filename, filePath } }; } }
两种方案对比
- 方案一:性能更高,直接在Multer阶段拦截,适合简单的类型校验场景。
- 方案二:灵活性更强,适合需要在校验后对文件做自定义处理(比如压缩、加水印)的场景。
内容的提问来源于stack exchange,提问作者M Nouman
相关产品推荐
相关产品推荐

