Firebase Function写入/tmp目录文件失败问题排查问询
核心问题排查
你的报错本质是两个异步逻辑错误导致的,和Firebase Functions的/tmp目录权限无关:
- 第一处:调用
downloadFile时没有加await关键字。downloadFile是异步函数,代码不会等下载完成就直接往下执行读取文件的逻辑,此时文件还没开始写入或者没写完,自然触发ENOENT找不到文件的错误。 - 第二处:
downloadFile内部的Promise逻辑错误。你现在监听的是axios返回的响应流的end事件,但响应流把数据pipe到磁盘写入流的过程是独立的,响应流传输结束不代表数据已经全部落盘;同时写入流本身的错误(比如磁盘满、写入中断)完全没有被捕获,就算加了await也可能出现Promise提前resolve、文件没写完的情况。
除此之外代码还有几个会触发运行异常的隐患:
- 代码中调用了
cors()中间件但没有引入对应依赖,运行时会直接抛出未定义错误 - 响应逻辑里调用
res.json()之后又执行了res.send(),会触发重复响应的报错 - 写入/tmp目录的文件用完没有清理,多次调用后会占满函数实例的临时存储空间,导致后续调用写入失败
- 大量使用
existsSync等同步API会阻塞Node.js事件循环,高并发场景下会拖慢接口响应速度
修复后代码
const functions = require("firebase-functions"); const express = require("express"); const axios = require("axios"); const fs = require('fs-extra'); const cors = require('cors')(); // 提前安装cors依赖:npm i cors require("dotenv").config(); const key = process.env.REACT_APP_PINATA_KEY; const secret = process.env.REACT_APP_PINATA_SECRET; const pinataSDK = require('@pinata/sdk'); const pinata = pinataSDK(key, secret); const app = express(); // 全局注册cors,不用在单个接口里重复写响应头 app.use(cors); const downloadFile = async (fileUrl, downloadFilePath) => { const response = await axios({ method: 'GET', url: fileUrl, responseType: 'stream', // 如果外部图片有鉴权/防盗链要求,在这里补对应请求头 // headers: { 'Referer': 'xxx', 'Authorization': 'xxx' } }); return new Promise((resolve, reject) => { const writeStream = fs.createWriteStream(downloadFilePath, {flags:'w'}); response.data.pipe(writeStream); // 监听写入流的完成事件,确认数据全部落盘再resolve writeStream.on('finish', resolve); // 同时捕获响应流和写入流的错误 writeStream.on('error', reject); response.data.on('error', reject); }); }; app.post('/pinata/pinFileToIPFS', async (req, res) => { const id = req.query.id; const url = '<URL of API endpoint to download the image>'; const filename = `/tmp/image-${id}.png`; try { // 确保临时目录存在,不需要额外用同步方法检查 await fs.ensureDir('/tmp'); // 等待文件完全下载写入完成再往下执行 await downloadFile(url, filename); const image = fs.createReadStream(filename); const options = { pinataOptions: {cidVersion: 1} }; const result = await pinata.pinFileToIPFS(image, options); console.log(JSON.stringify(result)); res.status(200).json(result); } catch (err) { console.log('接口处理失败', err); res.status(500).json({error: err.message}); } finally { // 不管成功失败,最后都删除临时文件,避免占满磁盘 if (await fs.pathExists(filename)) { await fs.unlink(filename); } } }); exports.api = functions.https.onRequest(app);
额外排查点
如果改完代码仍有问题,按以下顺序检查:
- 确认部署Firebase Functions时已经正确配置了Pinata的密钥环境变量,本地
.env文件的配置不会自动同步到云端 - 确认外部图片接口没有拦截云函数的请求:Firebase函数的出口IP是动态的,如果图片接口有IP白名单、Referer校验、登录鉴权逻辑,需要在axios请求里补上对应的请求头,或者把云函数出口配置为固定IP
- 检查函数的内存配置:如果处理的是大尺寸图片,内存配置过低会导致函数进程被系统杀掉,文件写入中断,建议最低配256MB内存
- 不要依赖/tmp目录的文件跨调用留存:/tmp目录是单个函数实例独占的,不同实例、不同调用之间的文件不共享,每次调用产生的临时文件必须在处理完成后清理。
内容的提问来源于stack exchange,提问作者Maestros
相关产品推荐
相关产品推荐

