You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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配合异常过滤器

  1. 调整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);
}
  1. 创建一个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,
    });
  }
}
  1. 在控制器上应用该过滤器:
@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 06:32:10