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;
问题原因
- 路径错误:代码中用
path.join(path.resolve(), "/tmp")生成的是项目根目录下的tmp子目录,而Vercel环境中只有系统级的/tmp目录是可读写的,自定义路径会触发只读权限错误。 - 异步操作未等待:
fs.mkdir是异步操作,但代码里直接调用了callback,可能导致目录还没创建完成就尝试写入文件,引发错误。 - 磁盘存储适配问题: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
相关产品推荐
相关产品推荐

