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

Vercel生产环境/tmp文件夹报EORFS错误,本地运行正常求助

解决Vercel部署Multer Excel上传的EORFS只读文件系统错误

问题描述

本地开发时用Multer.js实现Excel文件上传,读取数据存入MongoDB完全正常,但部署到Vercel生产环境后,出现EORFS只读文件系统错误,/tmp文件夹无法正常工作。

原Multer代码如下:

const multer = require("multer");
const path = require("path");
const fs = require("fs");
const httpStatus = require("http-status");

const ApiError = require("../utils/ApiError")
const logger = require("../utils/logger");

const multerUpload = async (req, res, next) => {
    let fileName = "";
    let storage = multer.diskStorage({
        destination: function (req, file, callback) {
            fs.mkdir(path.join(path.resolve(), "/tmp"), (err) => {
                if (err) {
                    logger.error("mkdir tmp %o", err);
                }
                callback(null, path.join(path.resolve(), "/tmp"));
            });
        },
        filename: function (req, file, callback) {
            fileName = file.fieldname + "-" + req.query.eventId + Date.now() + path.extname(file.originalname);
            logger.info("filename of uploadSheet===> %s", fileName);
            callback(null, fileName);
        },
    });

    // below code is to read the added data to DB from file
    var upload = multer({
        storage: storage,
        fileFilter: function (req, file, callback) {
            var ext = path.extname(file.originalname);
            if (ext !== '.xlsx') {
                return callback(new Error('Only Excel sheets are allowed'))
            }
            callback(null, true)
        },
    }).single("sheet");
    upload(req, res, async function (err) {
        if (err) {
            next(new ApiError(httpStatus.INTERNAL_SERVER_ERROR, err.message));
        } else {
            req.fileName = fileName;
            next();
        }
    })
}
module.exports = multerUpload;

问题原因

  1. 路径错误:代码中用path.join(path.resolve(), "/tmp")生成的是项目根目录下的tmp子目录,而Vercel环境中只有系统级的/tmp目录是可读写的,自定义路径会触发只读权限错误。
  2. 异步操作未等待:fs.mkdir是异步操作,但代码里直接调用了callback,可能导致目录还没创建完成就尝试写入文件,引发错误。
  3. 磁盘存储适配问题:Multer的diskStorage依赖磁盘IO,Vercel的无服务器函数执行环境短暂,磁盘操作不仅效率低,还容易触发权限和文件清理问题。

解决方案

方案1:改用内存存储(推荐)

直接把文件存在内存里,跳过磁盘写入,完美适配Vercel的无服务器环境:

const multer = require("multer");
const httpStatus = require("http-status");
const ApiError = require("../utils/ApiError");
const logger = require("../utils/logger");

// 使用内存存储,文件直接存在内存缓冲区
const storage = multer.memoryStorage();

const multerUpload = (req, res, next) => {
  const upload = multer({
    storage: storage,
    fileFilter: function (req, file, callback) {
      // 校验文件后缀
      const ext = file.originalname.split('.').pop().toLowerCase();
      if (ext !== 'xlsx') {
        return callback(new Error('Only Excel sheets are allowed'));
      }
      callback(null, true);
    },
  }).single("sheet");

  upload(req, res, function (err) {
    if (err) {
      next(new ApiError(httpStatus.INTERNAL_SERVER_ERROR, err.message));
    } else {
      // 文件内容存在req.file.buffer里,直接传给后续中间件处理
      next();
    }
  });
};

module.exports = multerUpload;

后续处理Excel数据时,直接读取内存缓冲区的内容即可(以xlsx库为例):

const XLSX = require('xlsx');

// 在后续处理的中间件中
const processExcelData = (req, res, next) => {
  // 从内存读取Excel文件
  const workbook = XLSX.read(req.file.buffer, { type: 'buffer' });
  // 解析工作表、提取数据并存入MongoDB的逻辑...
  next();
};

方案2:修正磁盘存储路径(不推荐,仅作参考)

如果一定要用磁盘存储,需要修正/tmp路径,并且确保目录创建完成后再写入:

const multer = require("multer");
const path = require("path");
const fs = require("fs").promises; // 使用Promise版fs,方便异步等待
const httpStatus = require("http-status");
const ApiError = require("../utils/ApiError");
const logger = require("../utils/logger");

const multerUpload = async (req, res, next) => {
  let fileName = "";
  const tmpDir = "/tmp"; // 直接用系统默认的可读写tmp目录

  try {
    // 检查tmp目录是否存在,不存在则创建(递归创建父目录)
    await fs.access(tmpDir);
  } catch (err) {
    if (err.code === 'ENOENT') {
      await fs.mkdir(tmpDir, { recursive: true });
    } else {
      logger.error("无法访问tmp目录: %o", err);
      return next(new ApiError(httpStatus.INTERNAL_SERVER_ERROR, "创建临时目录失败"));
    }
  }

  const storage = multer.diskStorage({
    destination: function (req, file, callback) {
      callback(null, tmpDir);
    },
    filename: function (req, file, callback) {
      fileName = `${file.fieldname}-${req.query.eventId}${Date.now()}${path.extname(file.originalname)}`;
      logger.info("上传文件名称===> %s", fileName);
      callback(null, fileName);
    },
  });

  const upload = multer({
    storage: storage,
    fileFilter: function (req, file, callback) {
      const ext = path.extname(file.originalname);
      if (ext !== '.xlsx') {
        return callback(new Error('Only Excel sheets are allowed'));
      }
      callback(null, true);
    },
  }).single("sheet");

  upload(req, res, function (err) {
    if (err) {
      next(new ApiError(httpStatus.INTERNAL_SERVER_ERROR, err.message));
    } else {
      req.fileName = fileName;
      next();
    }
  });
};

module.exports = multerUpload;

方案选择建议

优先选方案1,因为Vercel无服务器函数的执行周期短,内存存储更高效,还能避免磁盘操作带来的权限、路径和文件清理问题;磁盘存储的文件在函数执行结束后会被Vercel自动清除,完全没有持久化的必要,内存存储是更适配的方案。

内容的提问来源于stack exchange,提问作者Bhanudatsinh Jhala

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 10:55:13