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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 14:21:13