通过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:
- 先安装依赖:
npm install busboy
- 云函数代码:
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
相关产品推荐
相关产品推荐

