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

Vercel部署Node.js后端上传图片时POST请求返回500错误求助

Vercel部署Node.js后端图片上传500错误解决

问题场景

部署在Vercel上的Node.js后端,更新用户信息时包含Cloudinary图片上传功能,发送带图片的POST请求会触发500内部服务器错误,服务器日志显示“Function Invocation Failed”;但不上传图片时API能正常运行。

错误日志

POST /quiz/userIndividualDetail 200 1919.543 ms - 602
Uncaught Exception: Error: ENOENT: no such file or directory, mkdir '/var/task/tmp'
at Object.mkdirSync (node:fs:1391:3)
at checkAndMakeDir (/var/task/node_modules/express-fileupload/lib/utilities.js:155:38)
at module.exports (/var/task/node_modules/express-fileupload/lib/tempFileHandler.js:14:3)
at Multipart. (/var/task/node_modules/express-fileupload/lib/processMultipart.js:75:9)
at Multipart.emit (node:events:517:28)
at HeaderParser.cb (/var/task/node_modules/busboy/lib/types/multipart.js:358:14)
at HeaderParser.push (/var/task/node_modules/busboy/lib/types/multipart.js:162:20)
at SBMH.ssCb [as _cb] (/var/task/node_modules/busboy/lib/types/multipart.js:394:37)
at feed (/var/task/node_modules/streamsearch/lib/sbmh.js:248:10)
at SBMH.push (/var/task/node_modules/streamsearch/lib/sbmh.js:104:16) {
errno: -2,
syscall: 'mkdir',
code: 'ENOENT',
path: '/var/task/tmp'
}

原代码

handleUpdateUser函数

const handleUpdateUser = async (req, res) => {
    const { name, email, password, gender, role } = req.body;
    let userimage = "";

    try {
        // Check if all required fields are provided
        if (!name || !email || !password || !gender || !role) {
            return res.status(400).send({
                status: false,
                message: "All fields are required"
            });
        }

        // Check if the email exists in the database
        let result = await AuthModal.findOne({ email });
        if (!result) {
            return res.status(400).send({
                status: false,
                message: "Email does not exist in the database"
            });
        }

        // Prepare the object with new user data
        let newObj = {
            name,
            email,
            password,
            gender,
            role
        };

        // Handle image file attachment
        if (req.files) {
            const files = req.files.image;

            // Ensure the image file is provided
            if (!files) {
                return res.status(400).send({
                    status: false,
                    message: "Image Key is required"
                });
            }

            // Check if tempFilePath exists before uploading
            if (files.tempFilePath !== null) {
                const uploadResult = await cloudinary.uploader.upload(files.tempFilePath);
                if (uploadResult) {
                    // Remove the temp file after successful upload
                    fs.unlinkSync(files.tempFilePath);
                    if (userimage === "") {
                        newObj.userimage = uploadResult.secure_url;
                    }
                }
            }
        }

        // Update user data in the database
        let updateDB_Result = await AuthModal.updateOne(
            { email },
            { $set: newObj },
            { returnDocument: 'after' }
        );

        const { matchedCount } = updateDB_Result;
        if (matchedCount === 1) {
            return res.status(200).send({
                status: true,
                message: "Your changes have been successfully updated",
                data: newObj
            });
        }

    } catch (error) {
        console.error(error);
        return res.status(500).send({
            status: false,
            message: "An internal server error occurred"
        });
    }
};

express-fileupload中间件配置

const fileUpload = require('express-fileupload');
const path = require('path');

app.use(fileUpload({
    useTempFiles: true,
    tempFileDir: path.join(__dirname, '../tmp/') // Temp directory for uploaded files
}));

错误分析

核心问题是Vercel Serverless函数的文件系统限制:

  • Vercel的Serverless环境中,只有/tmp目录是可写的,且该目录由平台自动创建,无需手动生成
  • 原配置的tempFileDir: path.join(__dirname, '../tmp/')在本地开发时能正常指向项目的tmp目录,但部署到Vercel后,项目代码会被打包到/var/task目录,此时路径变成/var/task/tmp,这个目录不存在且无权限创建,导致express-fileupload创建临时目录时抛出ENOENT错误。

另外代码还有几个潜在问题:

  1. files.tempFilePath在useTempFiles: true配置下必然存在,无需判断是否为null
  2. fs.unlinkSync是同步操作,若临时文件已被删除会抛出错误,需添加异常捕获
  3. updateOne的returnDocument: 'after'选项无效,MongoDB的updateOne只返回修改计数,若要获取更新后的文档,应该用findOneAndUpdate

解决方案

1. 修正临时文件目录配置

将express-fileupload的tempFileDir直接设置为Vercel允许的/tmp目录:

const fileUpload = require('express-fileupload');

app.use(fileUpload({
    useTempFiles: true,
    tempFileDir: '/tmp/' // 直接使用Vercel提供的可写临时目录
}));

2. 优化handleUpdateUser函数

修复潜在问题,确保代码在Serverless环境稳定运行:

const handleUpdateUser = async (req, res) => {
    const { name, email, password, gender, role } = req.body;

    try {
        // 校验必填字段
        if (!name || !email || !password || !gender || !role) {
            return res.status(400).send({
                status: false,
                message: "所有字段均为必填项"
            });
        }

        // 检查用户是否存在
        const existingUser = await AuthModal.findOne({ email });
        if (!existingUser) {
            return res.status(400).send({
                status: false,
                message: "该邮箱未在数据库中注册"
            });
        }

        // 准备更新数据
        const updateData = {
            name,
            email,
            password,
            gender,
            role
        };

        // 处理图片上传
        if (req.files?.image) {
            const imageFile = req.files.image;
            try {
                // 上传到Cloudinary
                const uploadResult = await cloudinary.uploader.upload(imageFile.tempFilePath);
                if (uploadResult) {
                    updateData.userimage = uploadResult.secure_url;
                    // 尝试删除临时文件,忽略删除失败的情况
                    try {
                        fs.unlinkSync(imageFile.tempFilePath);
                    } catch (unlinkError) {
                        console.warn("临时文件删除失败:", unlinkError);
                    }
                }
            } catch (uploadError) {
                console.error("图片上传失败:", uploadError);
                return res.status(500).send({
                    status: false,
                    message: "图片上传失败"
                });
            }
        }

        // 更新用户信息并返回更新后的文档
        const updatedUser = await AuthModal.findOneAndUpdate(
            { email },
            { $set: updateData },
            { new: true, runValidators: true }
        );

        if (updatedUser) {
            return res.status(200).send({
                status: true,
                message: "用户信息更新成功",
                data: updatedUser
            });
        } else {
            return res.status(404).send({
                status: false,
                message: "用户不存在"
            });
        }

    } catch (error) {
        console.error("服务器内部错误:", error);
        return res.status(500).send({
            status: false,
            message: "服务器内部错误"
        });
    }
};

关键修改点

  • 直接使用/tmp作为临时文件目录,适配Vercel环境
  • 简化图片上传逻辑,移除不必要的tempFilePath判断
  • 为fs.unlinkSync添加try-catch,避免删除失败导致整个请求崩溃
  • 替换updateOne为findOneAndUpdate,并使用new: true获取更新后的用户文档,同时开启runValidators确保数据校验生效
  • 细化错误提示,方便排查问题

内容的提问来源于stack exchange,提问作者Asim Atiq Shah

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 18:30:20