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

如何将Firebase v8的图片上传代码适配升级至Firebase v9?

问题根因定位

代码运行失败是Firebase9升级连带的多个兼容问题共同导致的,核心故障点如下:

  • 冗余客户端SDK初始化冲突:你贴出的代码中仍保留旧版客户端引入语句const firebase = require("firebase");,且后续逻辑完全没有使用该客户端实例。升级到v9后如果没有补全所有compat层依赖,这段未使用的初始化代码会直接抛错阻断函数执行,属于历史遗留的冗余代码。
  • 图片访问URL格式非法:构造imageUrl的模板字符串存在多余换行和缩进,生成的URL中混入不可见的换行符、空白字符,最终写入数据库的链接完全无法正常访问。
  • Busboy版本兼容问题:和Firebase9同期更新的Busboy 1.x版本存在两处行为变更:一是Windows端上传的文件filename会携带本地完整路径,直接按.分割取后缀会概率性取错;二是未监听文件流错误时,临时文件写入失败会导致函数挂起超时,没有任何错误返回。
  • 临时文件泄漏:函数执行完成后未删除临时目录下的上传文件,云函数实例复用时会逐步占满磁盘空间,触发无明确日志的隐性运行错误。
适配修复方案

直接替换为以下修复后的代码即可,所有兼容逻辑已经内置:

const { admin, db } = require("../util/admin");
const config = require("../util/config");
const { uuid } = require("uuidv4");
const path = require("path");
const os = require("os");
const fs = require("fs");
const BusBoy = require("busboy");

// 已删除冗余的客户端Firebase引入与初始化逻辑,全链路使用Admin SDK即可

// 用户头像上传接口
exports.uploadImage = (req, res) => {
  const busboy = new BusBoy({ headers: req.headers });

  let imageToBeUploaded = {};
  let imageFileName;
  const generatedToken = uuid();
  let uploadError = null;

  busboy.on("file", (fieldname, file, filename, encoding, mimetype) => {
    // 兼容新版Busboy返回filename为对象、携带全路径的场景
    const actualFilename = typeof filename === 'object' ? filename.filename : filename;
    console.log(fieldname, file, actualFilename, encoding, mimetype);
    
    if (mimetype !== "image/jpeg" && mimetype !== "image/png") {
      uploadError = { code: 400, msg: "Wrong file type submitted" };
      // 消费掉文件流避免请求挂起
      return file.resume();
    }

    // 用官方pathAPI提取文件后缀,避免路径带.导致后缀识别错误
    const imageExtension = path.extname(actualFilename);
    imageFileName = `${Math.round(Math.random() * 1000000000000).toString()}${imageExtension}`;
    const filepath = path.join(os.tmpdir(), imageFileName);
    imageToBeUploaded = { filepath, mimetype };
    
    // 监听文件流错误,避免异常挂起
    file.on('error', (err) => {
      uploadError = { code: 500, msg: err.message };
    });
    
    file.pipe(fs.createWriteStream(filepath));
  });

  busboy.on("finish", async () => {
    if (uploadError) {
      return res.status(uploadError.code).json({ error: uploadError.msg });
    }
    try {
      await admin.storage().bucket().upload(imageToBeUploaded.filepath, {
        resumable: false,
        metadata: {
          metadata: {
            contentType: imageToBeUploaded.mimetype,
            firebaseStorageDownloadTokens: generatedToken,
          },
        },
      });
      // 修复URL拼接:移除多余空白字符,用encodeURIComponent处理文件名避免特殊字符转义问题
      const imageUrl = `https://firebasestorage.googleapis.com/v0/b/${config.storageBucket}/o/${encodeURIComponent(imageFileName)}?alt=media&token=${generatedToken}`;
      await db.doc(`/users/${req.user.handle}`).update({ imageUrl });
      
      // 执行完成后清理临时文件
      fs.unlinkSync(imageToBeUploaded.filepath);
      
      return res.json({ message: "image uploaded successfully" });
    } catch (err) {
      console.error(err);
      // 异常场景下也尝试清理临时文件
      if (imageToBeUploaded.filepath && fs.existsSync(imageToBeUploaded.filepath)) {
        fs.unlinkSync(imageToBeUploaded.filepath);
      }
      return res.status(500).json({ error: "something went wrong" });
    }
  });

  // 兼容新版云函数rawBody不存在的场景
  if (req.rawBody) {
    busboy.end(req.rawBody);
  } else {
    req.pipe(busboy);
  }
};
额外注意事项
  • 如果你使用的是Firebase Functions 3.20及以上版本部署,确保函数没有配置默认的multipart/form-data解析规则,否则req.rawBody会被提前消费,导致Busboy无法接收到文件数据。
  • 整个上传逻辑不需要引入客户端firebase/compat相关包,Admin SDK自带存储操作权限,多余的客户端引入反而会增大包体积、触发初始化冲突。
  • 生成文件名时可以直接用uuid()替换随机数逻辑,避免极端场景下文件名重名导致旧文件被覆盖。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 02:18:19