使用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
相关产品推荐
相关产品推荐

