NestJS头像上传接口问题:无效文件仍被存储如何解决?
问题描述
我创建了一个用于上传头像的NestJS接口,控制器代码如下:
@Post('/avatar') @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: './public/avatars', filename: (req, file, cb) => { const { user } = req as unknown as { user: typeof ReqUser }; const { id } = user as unknown as { id: string }; const filename: string = id; const extension: string = path.parse(file.originalname).ext; cb(null, `${filename}${extension}`); }, }), }), ) async uploadAvatar( @UploadedFile( new ParseFilePipe({ validators: [ new MaxFileSizeValidator({ maxSize: 5000000 }), new FileTypeValidator({ fileType: /image\/(jpeg|png|jpg)/ }), ], }), ) file: Express.Multer.File, @ReqUser() user, ) { return this.meService.setAvatar(file, user); }
目前遇到的问题是:当文件不符合验证规则时,系统会抛出对应错误,但文件仍会被上传至服务器。例如,非.jpg、.png、.jpeg格式的文件依然会被存储,请问哪里操作有误?
解决方案
问题出在执行顺序:Multer的FileInterceptor会先完成文件的存储操作,之后才会触发ParseFilePipe的验证逻辑。因此验证不通过时,文件已经被写入服务器了。
有两种可靠的解决方式:
方式一:在Multer配置中提前拦截不符合规则的文件(推荐)
直接在FileInterceptor的配置里添加fileFilter和limits,在文件存储前就完成验证拦截,这样不符合规则的文件根本不会被保存到服务器。
修改后的代码:
import * as path from 'path'; // ... @Post('/avatar') @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: './public/avatars', filename: (req, file, cb) => { const { user } = req as unknown as { user: typeof ReqUser }; const { id } = user as unknown as { id: string }; const filename: string = id; const extension: string = path.parse(file.originalname).ext; cb(null, `${filename}${extension}`); }, }), // 新增:文件类型过滤 fileFilter: (req, file, cb) => { const allowedMimeTypes = ['image/jpeg', 'image/png', 'image/jpg']; const allowedExtensions = ['.jpg', '.jpeg', '.png']; const isValidMimeType = allowedMimeTypes.includes(file.mimetype); const isValidExtension = allowedExtensions.includes(path.extname(file.originalname).toLowerCase()); if (isValidMimeType && isValidExtension) { cb(null, true); } else { cb(new Error('仅支持JPEG、PNG格式的图片文件'), false); } }, // 新增:文件大小限制 limits: { fileSize: 5000000 // 5MB,和ParseFilePipe的限制保持一致 } }), ) async uploadAvatar( @UploadedFile() file: Express.Multer.File, @ReqUser() user, ) { return this.meService.setAvatar(file, user); }
这种方式的好处是从源头拦截无效文件,避免不必要的磁盘IO操作。
方式二:在ParseFilePipe验证失败时删除已存储的文件
如果你希望保留ParseFilePipe的验证逻辑,可以通过自定义errorHandler在验证失败后删除已上传的文件:
修改后的@UploadedFile部分:
import * as fs from 'fs'; // ... @UploadedFile( new ParseFilePipe({ validators: [ new MaxFileSizeValidator({ maxSize: 5000000 }), new FileTypeValidator({ fileType: /image\/(jpeg|png|jpg)/ }), ], errorHandler: (error, file) => { // 检查文件路径是否存在,存在则删除 if (file?.path && fs.existsSync(file.path)) { fs.unlinkSync(file.path); } // 抛出原错误 throw error; } }), ) file: Express.Multer.File,
注意:这种方式依赖于文件已经被完整写入磁盘,若存储过程中出现异常可能无法触发删除,因此优先级低于方式一。
内容的提问来源于stack exchange,提问作者lumgaashi
相关产品推荐
相关产品推荐

