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

如何用Multer上传文件至S3且不本地存储?需支持Socket.io进度反馈

问题

开发接收multipart/form-data的API上传文件到AWS S3,遇到以下问题:

  • 不使用Multer时请求体为空,无法解析文件
  • 使用Multer后能正常处理,但Multer会在本地生成文件副本,不符合需求
  • 不想用multer-s3-v3,因为需要通过Socket.io返回文件上传进度,该工具不支持
解决方案

方法1:修改Multer使用内存存储(不生成本地文件)

Multer提供memoryStorage引擎,会将文件存储在内存中而非本地磁盘,彻底避免本地副本生成。同时可以结合AWS SDK的httpUploadProgress事件,通过Socket.io推送进度。

修改后的路由代码

const multer  = require('multer');
// 使用内存存储引擎替代本地磁盘存储
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });
router.post('/image-upload', upload.single('image'), authController.imageUpload);

修改后的API代码

// 假设已提前初始化Socket.io实例并挂载到app上,可通过req.app.get('io')获取
exports.imageUpload = async (req, res) => {
  try {
    const s3Upload = s3.upload({
      Bucket: process.env.AWS_BUCKET,
      Key: `images/${req.file.originalname}`,
      Body: req.file.buffer, // 直接使用内存中的文件buffer,无需读取本地文件
    });

    // 监听S3上传进度,通过Socket.io推送给客户端
    s3Upload.on('httpUploadProgress', (progress) => {
      const progressPercentage = Math.round((progress.loaded / progress.total) * 100);
      // 这里可根据业务传入唯一标识(如用户ID、上传会话ID),确保进度推送给对应客户端
      req.app.get('io').emit('upload-progress', {
        percentage: progressPercentage,
        fileName: req.file.originalname
      });
    });

    const uploadedImage = await s3Upload.promise();
    
    return res.status(200).send({
      message: "Image uploaded successfully",
      image: uploadedImage.Location.substring(36),
    });
  } catch (error) {
    return res.status(500).send({ message: "Upload failed", error: error.message });
  }
};

方法2:直接使用busboy解析multipart数据流(替代Multer)

如果不想依赖Multer,可使用busboy直接解析multipart/form-data请求,完全控制文件流的处理逻辑,直接将流传给AWS S3,同时精准跟踪上传进度。

示例代码

先安装依赖:npm install busboy

修改路由与处理逻辑:

const busboy = require('busboy');
const { PassThrough } = require('stream');

router.post('/image-upload', async (req, res) => {
  try {
    const bb = busboy({ headers: req.headers });
    const io = req.app.get('io');

    bb.on('file', async (name, file, info) => {
      const { filename } = info;
      // 创建中转流,用于统计上传字节数
      const progressStream = new PassThrough();
      let totalBytes = 0;
      let uploadedBytes = 0;

      // 监听数据流,实时计算上传进度
      progressStream.on('data', (chunk) => {
        uploadedBytes += chunk.length;
        const percentage = Math.round((uploadedBytes / totalBytes) * 100);
        io.emit('upload-progress', {
          percentage,
          fileName: filename
        });
      });

      // 获取文件总大小
      file.on('end', () => {
        totalBytes = file.byteCount;
      });

      // 将文件流通过中转流传给S3
      const uploadParams = {
        Bucket: process.env.AWS_BUCKET,
        Key: `images/${filename}`,
        Body: file.pipe(progressStream)
      };

      const uploadedImage = await s3.upload(uploadParams).promise();
      res.status(200).send({
        message: "Image uploaded successfully",
        image: uploadedImage.Location.substring(36),
      });
    });

    bb.on('error', (err) => {
      res.status(500).send({ message: "Upload failed", error: err.message });
    });

    req.pipe(bb);
  } catch (error) {
    res.status(500).send({ message: "Upload failed", error: error.message });
  }
});

注意事项

  • 使用memoryStorage时,需注意大文件可能导致内存占用过高,更适合中小文件上传场景
  • busboy方案灵活性更强,适合大文件上传或需要高度定制上传流程的场景
  • Socket.io实例需提前在应用中初始化并挂载到req.app,确保能在路由/控制器中正常获取

内容的提问来源于stack exchange,提问作者Haidy Elnahass

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 09:54:14