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

通过Callable向Cloud Function传递FormData返回空值问题

问题原因&解决方案

问题出在Firebase Cloud Functions的onCall函数特性上:onCall仅支持JSON序列化的参数,而FormData包含二进制文件,无法被直接序列化为JSON,所以传到云函数后就变成了空值。

下面是两种可行的解决方案:


方案1:改用HTTP类型云函数(onRequest)处理FormData

这是处理文件上传的标准方案,onRequest可以直接接收multipart/form-data格式的请求,适合各种大小的文件。

前端代码修改

替换原来的props.putFiles调用,直接用fetch请求云函数的HTTP端点:

let formData = new FormData();
formData.append("file", e.target.files[0], name);
formData.append("deal_id", deal.id);

console.log(formData.get("file"));

(async () => {
  console.log("call");
  // 替换为你的云函数HTTP地址,格式类似:https://us-central1-你的项目ID.cloudfunctions.net/putFiles
  const response = await fetch("YOUR_CLOUD_FUNCTION_HTTP_URL", {
    method: "POST",
    body: formData
    // 不要手动设置Content-Type,浏览器会自动添加带boundary的正确头信息
  });
  const result = await response.json();
  
  document.querySelector(`#Y${name}`).classList.add("uploaded");
  uploadedComplete(name);
  setUploading({
    ...uploading,
    [name]: false,
  });
  console.log(result);
})();

云函数代码修改

用onRequest替代onCall,并使用busboy(Firebase官方推荐的表单解析库)处理FormData:

  1. 先安装依赖:
npm install busboy
  1. 云函数代码:
const functions = require("firebase-functions");
const Busboy = require("busboy");
const os = require("os");
const path = require("path");
const fs = require("fs");

exports.putFiles = functions.https.onRequest(async (req, res) => {
  // 只允许POST请求
  if (req.method !== "POST") {
    return res.status(405).send("只支持POST请求");
  }

  const busboy = new Busboy({ headers: req.headers });
  const tmpdir = os.tmpdir(); // 临时目录存储上传的文件
  let formFields = {}; // 存储表单字段(比如deal_id)
  let uploadedFile = null; // 存储文件信息

  // 解析表单字段
  busboy.on("field", (key, value) => {
    formFields[key] = value;
  });

  // 解析上传的文件
  busboy.on("file", (fieldname, file, filename, encoding, mimetype) => {
    const filePath = path.join(tmpdir, filename);
    uploadedFile = { filePath, filename, mimetype };
    // 将文件流写入临时目录
    file.pipe(fs.createWriteStream(filePath));
  });

  // 解析完成后执行业务逻辑
  busboy.on("finish", async () => {
    try {
      // 这里可以拿到表单字段:formFields.deal_id
      // 拿到文件信息:uploadedFile
      // 在这里执行你原来的API调用逻辑(隐藏你的API KEY)
      // 示例:调用第三方文件上传API
      // const apiResponse = await fetch("第三方API地址", {
      //   method: "POST",
      //   headers: {
      //     "Authorization": `Bearer ${你的API_KEY}`
      //   },
      //   body: fs.createReadStream(uploadedFile.filePath)
      // });

      res.status(200).json({
        success: true,
        deal_id: formFields.deal_id,
        uploaded_filename: uploadedFile.filename
      });
    } catch (error) {
      res.status(500).json({ error: error.message });
    } finally {
      // 清理临时文件,避免占用存储空间
      if (uploadedFile) {
        fs.unlinkSync(uploadedFile.filePath);
      }
    }
  });

  // 开始解析请求体
  busboy.end(req.rawBody);
});

方案2:将文件转成Base64(仅适合小文件)

如果一定要用onCall函数,可以把文件转成Base64字符串后传输,但Base64会让文件体积增大30%左右,不适合大文件上传。

前端代码修改

const file = e.target.files[0];
const reader = new FileReader();

reader.onload = async () => {
  // 去掉Base64前缀(data:xxx;base64,)
  const base64Data = reader.result.split(",")[1];
  
  const response = await props.putFiles({
    deal_id: deal.id,
    file_info: {
      name: file.name,
      type: file.type,
      data: base64Data
    }
  });

  // 后续处理逻辑和之前一致
  document.querySelector(`#Y${name}`).classList.add("uploaded");
  uploadedComplete(name);
  setUploading({
    ...uploading,
    [name]: false,
  });
  console.log(response);
};

// 将文件转成Base64格式
reader.readAsDataURL(file);

云函数代码修改

const functions = require("firebase-functions");
const { Buffer } = require("buffer");

exports.putFiles = functions.https.onCall(async (data, context) => {
  const { deal_id, file_info } = data;
  
  // 将Base64字符串转回二进制Buffer
  const fileBuffer = Buffer.from(file_info.data, "base64");
  
  // 在这里执行你的API调用逻辑
  // 示例:
  // const apiResponse = await fetch("第三方API地址", {
  //   method: "POST",
  //   headers: {
  //     "Authorization": `Bearer ${你的API_KEY}`,
  //     "Content-Type": file_info.type
  //   },
  //   body: fileBuffer
  // });

  return {
    success: true,
    deal_id,
    filename: file_info.name
  };
});

推荐方案1,它更符合文件上传的场景,支持大文件,且性能更优。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 16:39:21