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

