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

Express Multer中间件data事件异常及文件上传进度获取问题

文件上传进度监听的环境差异问题及解决方法

问题描述

需求为获取文件上传过程中的实时请求进度,最初采用如下写法:

app.post('/upload', upload.single('file'), (req,res,next) => {
  req.on('data', ()=> {
    // 处理进度逻辑
  })
  // 处理上传后的文件
})

本地及AWS EC2环境下均无法实现需求:处理进度逻辑仅在Multer中间件接收完所有文件后执行。

调整写法后:

app.post('/upload', (req,res,next) => {
  req.on('data', ()=> {
    // 处理进度逻辑
  })
  next()

}, upload.single('file'), (req, res, next) => {
  // 处理上传后的文件
})
  • 本地环境:正常工作,每收到一段文件分片就触发进度逻辑;
  • AWS EC2环境:进度逻辑仍在文件接收完成后批量触发,无法实时监听。

补充:曾尝试在data事件内上传完成后调用next(),但Multer抛出异常:错误:表单在Multipart._final阶段意外结束(原英文:Error: Unexpected end of form at Multipart._final)。

差异原因

本地环境无代理层,请求数据会直接流式传输到后端服务,因此req.data事件会随分片数据到达实时触发;而AWS EC2通常搭配**应用负载均衡(ALB)**使用,ALB默认会对multipart/form-data类型的请求进行完整缓冲,即等待整个请求接收完毕后再转发给后端服务,导致后端的req.data事件一次性触发,无法实时获取上传进度。

解决方案

方案1:关闭AWS ALB的请求缓冲

可在ALB的目标组配置中关闭“请求缓冲”功能,让请求数据直接流式转发给EC2实例。但此操作可能会增加ALB的性能开销,需根据业务场景权衡。

方案2:基于Multer底层的Busboy监听进度

Multer基于Busboy实现,直接使用Busboy处理请求,可绕过代理缓冲对req.data事件的影响,实时监听文件分片上传:

const Busboy = require('busboy');

app.post('/upload', (req, res) => {
  const busboy = new Busboy({ headers: req.headers });
  let totalFileSize = 0;
  let uploadedBytes = 0;

  // 监听文件字段
  busboy.on('file', (fieldname, fileStream, filename, encoding, mimetype) => {
    // 获取文件总大小
    totalFileSize = fileStream.headers['content-length'];
    // 监听分片数据
    fileStream.on('data', (chunk) => {
      uploadedBytes += chunk.length;
      // 实时计算并处理进度,例如返回给前端
      const progress = ((uploadedBytes / totalFileSize) * 100).toFixed(2);
      console.log(`当前上传进度:${progress}%`);
    });
    // 文件上传完成回调
    fileStream.on('end', () => {
      console.log(`${filename} 上传完成`);
    });
  });

  // 监听表单字段
  busboy.on('field', (fieldname, val) => {
    console.log(`表单字段 ${fieldname}: ${val}`);
  });

  // 整个表单处理完成
  busboy.on('finish', () => {
    res.status(200).send('上传完成');
  });

  // 将请求流导入Busboy
  req.pipe(busboy);
});

方案3:自定义Multer存储引擎监听进度

通过自定义Multer的存储引擎,在文件写入过程中监听数据流的data事件:

const multer = require('multer');
const fs = require('fs');
const path = require('path');

const customStorage = multer.diskStorage({
  destination: (req, file, cb) => {
    const uploadDir = './uploads';
    // 确保上传目录存在
    if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir);
    cb(null, uploadDir);
  },
  filename: (req, file, cb) => {
    // 自定义文件名
    cb(null, `${Date.now()}-${file.originalname}`);
  },
  // 重写_write方法监听进度
  _write: function (file, encoding, cb) {
    const filePath = path.join(this.destination, this.filename);
    const writeStream = fs.createWriteStream(filePath);
    let uploadedBytes = 0;

    file.pipe(writeStream);
    // 监听分片数据
    file.on('data', (chunk) => {
      uploadedBytes += chunk.length;
      const progress = ((uploadedBytes / file.size) * 100).toFixed(2);
      console.log(`上传进度:${progress}%`);
    });
    // 写入完成回调
    writeStream.on('finish', cb);
  }
});

const upload = multer({ storage: customStorage });

app.post('/upload', upload.single('file'), (req, res) => {
  res.status(200).send({
    message: '文件上传完成',
    file: req.file
  });
});

异常说明

之前在data事件内调用next()导致的错误:表单在Multipart._final阶段意外结束,是因为提前调用next()会中断请求流的传输,Multer还未完成整个表单的解析就收到了流结束信号,从而抛出异常。

内容的提问来源于stack exchange,提问作者Laurent Schwitter

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 10:01:43