Node.js使用Multer批量上传文件时出现异常的问题求助
批量上传文件时的ENOENT与ERR_HTTP_HEADERS_SENT问题修复
问题根源分析
- 并发文件名冲突:你用
fs.readdir获取目录文件数量作为文件名前缀,批量上传时多个请求会同时读取目录,得到相同的文件数量,生成重复文件名。后上传的文件会覆盖先上传的,导致后续unlink时找不到原文件,抛出ENOENT错误。 - 重复响应客户端:错误处理时未终止函数,比如
fs.readdir出错后发送了500响应,但后续代码仍会执行;fs.unlink出错时又再次发送响应,导致重复设置响应头,触发ERR_HTTP_HEADERS_SENT。 - 错误的原文件获取逻辑:
files[0]完全不可靠,fs.readdir返回的文件列表顺序不固定,并发场景下根本拿不到当前请求上传的文件。
修复方案与代码
核心修复点
- 用UUID生成唯一文件名,彻底避免并发冲突
- 直接使用Multer返回的
req.files获取上传文件信息,无需读目录 - 用async/await+Promise化API简化异步逻辑,统一错误处理,确保只响应客户端一次
修复后的完整代码:
const multer = require('multer'); const sharp = require('sharp'); const fs = require('fs').promises; const path = require('path'); const { v4: uuidv4 } = require('uuid'); // 执行npm install uuid安装依赖 // 生成全局唯一文件名 const generateUniqueFilename = (originalName) => { const ext = path.extname(originalName); const baseName = path.basename(originalName, ext); return `${uuidv4()}_${baseName}${ext}`; }; const storage = multer.diskStorage({ destination: function (req, file, callback) { const uploadDir = path.join(__dirname, 'uploads'); // 自动创建上传目录(避免目录不存在报错) fs.access(uploadDir).catch(() => fs.mkdir(uploadDir, { recursive: true })); callback(null, uploadDir); }, filename: function (req, file, callback) { callback(null, generateUniqueFilename(file.originalname)); }, }); const upload = multer({ storage: storage, fileFilter: (req, file, cb) => { const allowedTypes = ['image/png', 'image/jpg', 'image/jpeg']; if (allowedTypes.includes(file.mimetype)) { cb(null, true); } else { cb(new Error('Only .png, .jpg and .jpeg format allowed!')); } }, limits: { fileSize: 3000000 }, // 限制文件大小3MB }); // 使用async/await简化异步流程 app.all('/api/upload-picture', upload.array('files'), async (req, res) => { try { const results = []; // 遍历所有上传的文件逐一处理 for (const file of req.files) { const originalPath = file.path; const resizedFilename = `resized_${file.filename}`; const resizedPath = path.join(__dirname, 'uploads', resizedFilename); // 调整图片尺寸 await sharp(originalPath) .resize({ width: 512 }) .toFile(resizedPath); // 删除原文件 await fs.unlink(originalPath); console.log(`原文件 ${file.filename} 已删除`); // 收集结果链接 results.push(`localhost:3000/api/uploads/${resizedFilename}`); } res.send({ code: '0', response: results }); } catch (err) { console.error(err); // 确保只发送一次错误响应 if (!res.headersSent) { res.status(500).send('Internal Server Error'); } } });
额外说明
- UUID生成的文件名不会重复,彻底解决并发上传的文件名冲突问题
- 直接使用
req.files获取上传文件的路径和名称,避免了读目录带来的不可靠性 - async/await让异步逻辑更清晰,try/catch统一捕获所有异常,配合
res.headersSent检查,完全解决重复响应的问题 - 自动创建上传目录,避免因目录不存在导致的上传失败
内容的提问来源于stack exchange,提问作者pioucraft
相关产品推荐
相关产品推荐

