如何将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
相关产品推荐
相关产品推荐

