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

使用multer、multer-s3与Express时,如何回传文件上传进度?

问题

我当前通过Express服务器将文件上传至S3的实现代码如下:

import express from 'express';
import "dotenv/config";
import { S3Client } from '@aws-sdk/client-s3';
import multer from 'multer';
import multerS3 from 'multer-s3';

const router = express.Router();

const s3 = new S3Client({
    secretAccessKey: process.env.SECRET_KEY,
    accessKeyId: process.env.ACCESS_KEY,
    region: process.env.REGION
});

const upload = multer({
    storage: multerS3({
      s3: s3,
      bucket: process.env.BUCKET,
      metadata: function (req, file, cb) {
        cb(null, {fieldName: file.fieldname});
      },
      key: function (req, file, cb) {
        cb(null, `${req.user.id}/${Date.now()}-${file.originalname}`)
      }
    })
});

router.post('/upload', upload.single('file'), function(req, res, next) {
    res.send({
        message: "Uploaded!",
        urls: { name: `${req.user.id}/${Date.now()}-${req.file.originalname}`, type: req.file.mimetype, size: req.file.size }
    });
});

export default router;

我尝试将上传进度回传给用户,但在multer或multer-s3中未找到相关实现方法,是否遗漏了关键要点?

解决方案

multer-s3确实没有直接暴露上传进度的监听接口——它封装了AWS SDK的上传流程,屏蔽了底层事件。要实现进度回传,需要绕过multer-s3的自动上传逻辑,手动处理文件上传并监听AWS SDK的进度事件,结合Server-Sent Events(SSE) 向前端实时推送进度数据。

步骤1:修改multer存储为内存存储

先通过multer把文件暂存到内存,方便后续手动上传到S3:

import { PutObjectCommand } from '@aws-sdk/client-s3';
// 其他导入代码保持不变

// 替换原multer配置为内存存储
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });

步骤2:在路由中手动处理上传并监听进度

在上传路由里,使用AWS SDK的PutObjectCommand发起上传,通过onProgress回调监听进度,再用SSE推送给前端:

router.post('/upload', upload.single('file'), async (req, res) => {
  // 配置SSE响应头,保持连接开放
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  res.flushHeaders(); // 立即发送响应头,避免前端等待

  const file = req.file;
  const key = `${req.user.id}/${Date.now()}-${file.originalname}`;

  const uploadParams = {
    Bucket: process.env.BUCKET,
    Key: key,
    Body: file.buffer,
    ContentType: file.mimetype
  };

  const command = new PutObjectCommand(uploadParams);

  // 定义进度监听函数
  const handleProgress = (progress) => {
    const percent = Math.round((progress.loaded / progress.total) * 100);
    // 按SSE格式发送进度数据
    res.write(`data: ${JSON.stringify({ progress: percent })}\n\n`);
  };

  try {
    // 执行上传并传入进度监听回调
    await s3.send(command, {
      onProgress: handleProgress
    });

    // 上传完成,发送最终结果
    res.write(`data: ${JSON.stringify({
      message: "Uploaded!",
      urls: { name: key, type: file.mimetype, size: file.size }
    })}\n\n`);
    res.end();
  } catch (err) {
    // 上传失败,发送错误信息
    res.write(`data: ${JSON.stringify({ error: err.message })}\n\n`);
    res.end();
  }
});

步骤3:前端接收进度(示例)

前端通过EventSource监听SSE消息,同时用fetch发送上传请求:

const fileInput = document.getElementById('file-input');
fileInput.addEventListener('change', async () => {
  const file = fileInput.files[0];
  if (!file) return;

  const formData = new FormData();
  formData.append('file', file);

  // 初始化SSE连接
  const eventSource = new EventSource('/upload');
  eventSource.onmessage = (event) => {
    const data = JSON.parse(event.data);
    if (data.progress) {
      console.log(`上传进度:${data.progress}%`);
      // 此处可更新页面进度条UI
    } else if (data.message) {
      alert(data.message);
      eventSource.close(); // 上传完成后关闭连接
    } else if (data.error) {
      alert('上传失败:' + data.error);
      eventSource.close();
    }
  };

  // 发送POST上传请求,需携带用户凭证则添加credentials: 'include'
  await fetch('/upload', {
    method: 'POST',
    body: formData,
    credentials: 'include'
  });
});

注意事项

  • 若上传超大文件,内存存储可能导致服务器内存占用过高,可改用multer的磁盘存储,通过createReadStream读取文件流上传到S3。
  • 也可以用WebSocket替代SSE,适合双向通信场景,但SSE实现更简单,适配单向进度推送的需求。

内容的提问来源于stack exchange,提问作者Paul Jose

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 20:42:51