NestJS中Multer文件上传fileFilter引发请求无限挂起问题
问题:NestJS + Multer文件上传时fileFilter导致请求无限挂起
我在使用NestJS和Multer实现文件上传时遇到以下问题:
- 尝试在
fileFilter中检查文件是否已存在,若存在则返回错误,首次操作正常 - 再次上传相同文件时,请求会无限挂起
- 移除
fileFilter后无挂起问题,但会直接覆盖已有文件
会不会是第一次请求没有正确关闭/终止?
控制器代码
@UseGuards(JwtAuthGuard) @UseGuards(RolesGuard) @Role('SCENARISTE') @Post('upload/sound') @UseInterceptors(FileInterceptor('file', { storage: diskStorage({ destination: 'files/sounds', filename: function (req, file, callback) { return callback(null, file.originalname); } }), fileFilter: (req, file, callback) => { if (existsSync(join('files/sounds', file.originalname))) { return callback(new NotAcceptableException(ErrorType.FILE_ALREADY_EXIST), false); } else { return callback(null, true); } }, })) uploadSound(@UploadedFile() file: Express.Multer.File) { const fileReponse = { originalname: file.originalname, mimetype: file.mimetype, filename: file.filename, size: file.size, destination: file.destination, fieldname: file.fieldname, path: file.path }; return fileReponse; }
问题原因
核心问题在于Multer的fileFilter回调不支持直接传入NestJS的HTTP异常。Multer的回调函数期望接收标准的Error对象,而NotAcceptableException是Nest封装的HTTP异常,Multer无法识别并处理这种异常,导致请求处理流程中断,客户端无法收到响应,最终表现为请求无限挂起。
解决方案
方案1:修改fileFilter,使用普通Error配合异常过滤器
- 调整
fileFilter,返回普通Error对象:
fileFilter: (req, file, callback) => { const targetPath = join('files/sounds', file.originalname); if (existsSync(targetPath)) { // 传递普通Error,携带自定义错误标识 return callback(new Error(ErrorType.FILE_ALREADY_EXIST), false); } callback(null, true); }
- 创建一个Multer异常过滤器,将普通Error转换为Nest HTTP响应:
import { ExceptionFilter, Catch, ArgumentsHost, HttpStatus } from '@nestjs/common'; import { MulterError } from 'multer'; @Catch(MulterError, Error) export class MulterExceptionFilter implements ExceptionFilter { catch(exception: Error, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const request = ctx.getRequest(); let status = HttpStatus.BAD_REQUEST; let message = '文件上传失败'; // 根据错误标识匹配对应HTTP状态 if (exception.message === ErrorType.FILE_ALREADY_EXIST) { status = HttpStatus.NOT_ACCEPTABLE; message = '文件已存在'; } response.status(status).json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, message, }); } }
- 在控制器上应用该过滤器:
@UseFilters(MulterExceptionFilter) // 添加这一行 @UseInterceptors(FileInterceptor('file', { /* ...配置... */ })) uploadSound(@UploadedFile() file: Express.Multer.File) { // ...原有逻辑... }
方案2:移除fileFilter,在控制器方法中检查文件
如果不需要在上传前拦截,也可以先让Multer完成文件上传,再在控制器中检查文件是否存在,若存在则删除已上传文件并抛出异常:
@UseInterceptors(FileInterceptor('file', { storage: diskStorage({ destination: 'files/sounds', filename: (req, file, callback) => callback(null, file.originalname), }), // 移除fileFilter })) uploadSound(@UploadedFile() file: Express.Multer.File) { const targetPath = join('files/sounds', file.originalname); // 检查文件是否已存在(注:Multer已完成上传,若存在则已被覆盖) if (existsSync(targetPath)) { // 删除刚上传的文件 unlinkSync(file.path); throw new NotAcceptableException(ErrorType.FILE_ALREADY_EXIST); } // ...返回文件信息逻辑... }
注意:方案2存在一定风险,因为Multer会先覆盖已有文件再执行检查,仅适合对文件版本要求不高的场景。
内容的提问来源于stack exchange,提问作者Erendis
相关产品推荐
相关产品推荐

