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

部署在GAE的Node.js服务器上传GCS遇只读文件系统错误求助

问题原因分析

Google App Engine(GAE)标准环境的文件系统大部分区域是只读的,仅实例的临时内存目录/tmp具备可写权限。你的代码中multer配置使用相对路径./tmp/uploads/testing/,会导致实际指向的目录不在GAE允许的可写区域内,或者创建子目录时因父目录只读而失败,最终触发EROFS: read-only file system错误。


解决方案

方案一:修正磁盘存储路径(兼容现有逻辑)

将multer的存储目录改为GAE允许的绝对路径/tmp,并确保子目录能被正确创建:

const fs = require('fs');
const multer = require('multer');

const testImageStorage = multer.diskStorage({
    destination: function (req, file, cb) {
        // 使用GAE允许的绝对临时目录路径
        const tempDir = '/tmp/uploads/testing/';
        try {
            // 递归创建目录(不存在则自动创建)
            fs.mkdirSync(tempDir, { recursive: true });
            cb(null, tempDir);
        } catch (error) {
            console.error('目录创建失败:', error);
            cb(error);
        }
    },
    filename: function (req, file, cb) {
        try {
            cb(null, new Date().getTime() + file.originalname);
        } catch (error) {
            console.error('文件名生成失败:', error);
            cb(error);
        }
    },
});

const uploadTestImages = multer({
    storage: testImageStorage,
    limits: { fileSize: 1024 * 1024 * 3 },
    fileFilter: fileFilter,
});

注意:GAE的/tmp目录是实例级临时存储,实例销毁时数据会丢失,但仅用于临时缓存上传文件(上传至GCS后即可丢弃),完全满足需求。

方案二:使用内存存储(更优,避免磁盘IO)

直接在内存中处理上传文件,跳过磁盘写入步骤,彻底规避文件系统权限问题,同时提升性能:

1. 修改multer配置

const multer = require('multer');

// 改用内存存储,无需磁盘目录
const uploadTestImages = multer({
    storage: multer.memoryStorage(),
    limits: { fileSize: 1024 * 1024 * 3 },
    fileFilter: fileFilter,
});

2. 调整路由处理逻辑

此时req.file将包含文件的Buffer数据,直接上传至GCS即可:

router.post(
    '/upload-to-gcp',
    uploadTestImages.single('testImage'),
    async (req, res) => {
        try {
            if (req.file) {
                const fileName = req.file.originalname;
                const destination = 'someFolderInBucket/' + fileName;

                try {
                    // 直接上传Buffer到GCS
                    const file = await storageService.uploadFromBuffer(
                        req.file.buffer,
                        destination,
                        req.file.mimetype // 传入文件MIME类型,优化GCS存储元数据
                    );

                    const publicFile = await storageService.makeFilePublic(file);
                    const publicUrl = storageService.getFilePublicUrl(publicFile);
                    console.log(`文件公网地址: ${publicUrl}`);

                    res.status(200).send('图片上传成功');
                } catch (error) {
                    console.error('GCS上传失败:', error);
                    res.status(500).send('图片上传失败');
                }
            } else {
                res.status(400).send('未上传任何文件');
            }
        } catch (error) {
            console.error('请求处理错误:', error);
            res.status(500).send('服务器内部错误');
        }
    }
);

3. 新增GCS Buffer上传方法

在storageService中添加直接处理Buffer的上传函数:

// storageService.js
const { Storage } = require('@google-cloud/storage');
const storage = new Storage();

async function uploadFromBuffer(buffer, destination, mimeType) {
    const bucket = storage.bucket('你的存储桶名称');
    const file = bucket.file(destination);

    return new Promise((resolve, reject) => {
        const writeStream = file.createWriteStream({
            metadata: { contentType: mimeType },
            resumable: false
        });

        writeStream.on('finish', () => resolve(file));
        writeStream.on('error', reject);
        writeStream.end(buffer);
    });
}

module.exports = { uploadFromBuffer, makeFilePublic, getFilePublicUrl };

方案选择建议

优先选择方案二:无磁盘IO操作,更适合无服务器环境的轻量化需求,同时彻底避免文件系统相关问题。

内容的提问来源于stack exchange,提问作者Tanzim Ahmed

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 08:17:29