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

Node.js上传图片至Firebase失败求助:Unexpected end of form错误

图片上传至Firebase云函数适配方案(Node.js v20 + 最新Firebase)

我尝试用busboy、multer、formidable、express file parser等工具包实现Firebase云函数的图片上传,但全部失败,大多触发如下错误:

Multer error: Error: Unexpected end of form
    at Multipart._final (/Users/xxx/Desktop/xxx/backend/functions/node_modules/busboy/lib/types/multipart.js:588:17)
    at prefinish (node:internal/streams/writable:907:14)
    at finishMaybe (node:internal/streams/writable:921:5)
    at Writable.end (node:internal/streams/writable:836:5)
    at onend (node:internal/streams/readable:946:10)
    at process.processTicksAndRejections (node:internal/process/task_queues:77:11) {
  storageErrors: []
}

以下是我用multer实现的云函数代码:

// Multer setup for handling file uploads (5MB limit)
const upload = multer({
    storage: multer.memoryStorage(), // Store the file in memory
    limits: { fileSize: 5 * 1024 * 1024 }, // Limit file size to 5MB
});

// Cloud Function to handle image upload
exports.updateClientProfileImg = functions.https.onRequest((req, res) => {
    upload.single("image")(req, res, async (err) => {
        if (err) {
            console.error("Multer error:", err);
            return res
                .status(400)
                .json({ error: `File upload error: ${err.message}` });
        }

        const file = req.file;
        if (!file) {
            console.error("No file uploaded.");
            return res.status(400).json({ error: "No file uploaded" });
        }

        try {
            console.log("File uploaded:", file);

            const fileUpload = bucket.file(file.originalname);

            // Create a stream and upload the file
            const stream = Readable.from(file.buffer);
            await new Promise((resolve, reject) => {
                stream
                    .pipe(
                        fileUpload.createWriteStream({
                            metadata: { contentType: file.mimetype },
                        })
                    )
                    .on("finish", resolve)
                    .on("error", reject);
            });

            // Make the file public and generate the URL
            await fileUpload.makePublic();
            const imageUrl = `https://storage.googleapis.com/${bucket.name}/${fileUpload.name}`;

            return res.status(200).json({
                message: "Image uploaded successfully",
                imageUrl: imageUrl,
            });
        } catch (error) {
            console.error("Error uploading image:", error);
            return res.status(500).json({ error: "Failed to upload image" });
        }
    });
});

适配解决方案

  • 校验请求格式
    这个错误核心原因是前端请求格式不匹配:确保前端发送的是multipart/form-data类型,且表单字段名与后端upload.single("image")中的image完全一致;同时Node.js v20中需正确导入Stream模块:const { Readable } = require('stream');。

  • 改用官方推荐的Busboy实现
    最新Firebase云函数适配Node.js v20的稳定方案,直接使用Busboy处理表单数据,结合@google-cloud/storage完成上传:

const functions = require('firebase-functions');
const { Storage } = require('@google-cloud/storage');
const Busboy = require('busboy');
const os = require('os');
const path = require('path');
const fs = require('fs');

const storage = new Storage();
const bucket = storage.bucket('你的存储桶名称');

exports.updateClientProfileImg = functions.https.onRequest((req, res) => {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: '仅支持POST请求' });
  }

  const busboy = new Busboy({ headers: req.headers });
  let uploadData = null;
  let tempFilePath = '';

  busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
    if (fieldname !== 'image') {
      file.resume();
      return;
    }
    const tempDir = os.tmpdir();
    tempFilePath = path.join(tempDir, filename);
    uploadData = { filename, mimetype };
    file.pipe(fs.createWriteStream(tempFilePath));
  });

  busboy.on('finish', async () => {
    if (!uploadData) {
      return res.status(400).json({ error: '未上传文件' });
    }

    try {
      const fileUpload = bucket.file(uploadData.filename);
      await new Promise((resolve, reject) => {
        fs.createReadStream(tempFilePath)
          .pipe(fileUpload.createWriteStream({
            metadata: { contentType: uploadData.mimetype }
          }))
          .on('finish', resolve)
          .on('error', reject);
      });
      
      await fileUpload.makePublic();
      const imageUrl = `https://storage.googleapis.com/${bucket.name}/${fileUpload.name}`;
      
      await fs.promises.unlink(tempFilePath); // 清理临时文件
      return res.status(200).json({ message: '图片上传成功', imageUrl });
    } catch (error) {
      console.error('上传失败:', error);
      return res.status(500).json({ error: '图片上传失败' });
    }
  });

  busboy.end(req.rawBody); // 适配云函数请求体格式,关键步骤
});
  • 依赖与部署配置
    更新package.json依赖至最新版本:
{
  "dependencies": {
    "firebase-functions": "^4.5.0",
    "@google-cloud/storage": "^7.10.0",
    "busboy": "^1.6.0"
  },
  "engines": {
    "node": "20.x"
  }
}

部署时指定Node.js版本:

firebase deploy --only functions --runtime nodejs20
  • 本地测试注意事项
    必须使用Firebase模拟器运行云函数:firebase emulators:start,避免直接用node命令启动导致的请求对象格式差异问题。

内容的提问来源于stack exchange,提问作者lamb p

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 03:47:08