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

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返回的文件列表顺序不固定,并发场景下根本拿不到当前请求上传的文件。

修复方案与代码

核心修复点

  1. 用UUID生成唯一文件名,彻底避免并发冲突
  2. 直接使用Multer返回的req.files获取上传文件信息,无需读目录
  3. 用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 11:23:23