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

Node.js+Express+Firebase Functions上传Firebase Storage生产环境报错排查

问题

我用Firebase Functions搭配Express实现图片、PDF等文件上传到Firebase Storage的功能,本地模拟器测试完全正常,但部署到生产环境后出现如下错误:

{ error1: TypeError [ERR_INVALID_ARG_TYPE]: The "paths[0]" argument must be of type string. Received an instance of Object at Object.resolve (node:path:1129:7) at GoogleAuth.getClient (/workspace/node_modules/google-auth-library/build/src/auth/googleauth.js:690:39) at GoogleAuth.request (/workspace/node_modules/google-auth-library/build/src/auth/googleauth.js:748:35) at Upload.makeRequest (/workspace/node_modules/@google-cloud/storage/build/cjs/src/resumable-upload.js:744:43) at uri.retries (/workspace/node_modules/@google-cloud/storage/build/cjs/src/resumable-upload.js:410:40) at RetryOperation.runAttempt [as _fn] (/workspace/node_modules/async-retry/lib/index.js:42:15) at RetryOperation.attempt (/workspace/node_modules/retry/lib/retry_operation.js:116:8) at run (/workspace/node_modules/async-retry/lib/index.js:55:8) at new Promise (<anonymous>) at retry (/workspace/node_modules/async-retry/lib/index.js:58:10) { code: 'ERR_INVALID_ARG_TYPE' }}

代码实现如下:

require('dotenv').config();
const functions = require('firebase-functions');
const admin = require("firebase-admin");
const bcrypt = require("bcrypt");
const _ = require('lodash');
const Joi = require("joi");
const jwt = require("jsonwebtoken");
const express = require("express");
const formidable = require('formidable-serverless');
const bucket = require('../utils/clouStorage');
const { v4: uuidv4 } = require('uuid');
const os = require('os');
const path = require('path');
const fs = require('fs');
const sendEMail = require('../utils/emailService');
const router = express.Router();

const JWT_SECRET = process.env.JWT_SECRET || functions.config().jwt.secret;

// DOCTORS REGISTRATION LOGIC
router.post("/register", (req, res, next) => {
  // Had to validate the request inputs in the formidable lib because the multipart/form-data
  // was a bit challenging to parse with only the request object.
  const form = new formidable.IncomingForm();
  form.parse(req, async (err, fields, files) => {
    if (err) {
      console.error('Error parsing form data:', err);
      next(err)
      return res.status(500).send("Internal server error");
    }
    const { name, surname, email, phone, gender, password,
      nationalID, passport, citizenship, city, speciality,
      school, school_from, school_to, license_number, yearsOfExperience
    } = fields;
    let doctor;
    const { avatar, license, resume } = files;
    console.log(files);

    const { error } = validate(fields);
    if (error) return res.status(400).send(error.details[0].message);
    const filesError = validateFile(files);
    if (filesError.error) return res.status(400).send(filesError.error.details[0].message);

    try {
      const doctorSnapshot = await admin.firestore().collection('doctors').where('email', '==', email).get();
      if (!doctorSnapshot.empty) return res.status(400).send("Doctor already registered.");

      doctor = await admin.auth().createUser({
        email,
        password
      });
      const userId = doctor.uid;
      const salt = await bcrypt.genSalt(10);
      const hashed_password = await bcrypt.hash(password, salt);

      let avatarUrl = "";
      let licenseUrl = "";
      let resumeUrl = "";

      // Upload avatar
      if (avatar) {
        const avatarFileName = `${uuidv4()}_${avatar.name}`;
        try {
          console.log({path1: avatar.path})
          const avatarUpload = await bucket.upload(avatar.path, {
            destination: `doctorsImage/${avatarFileName}`,
            metadata: {
              contentType: avatar.type,
            },
          });
          avatarUrl = `https://storage.googleapis.com/${bucket.name}/${avatarUpload[0].name}`;
        } catch (error) {
          // console.log({ error1: error })
          // return res.status(400).send(error.message);
        }
      }

      // Upload license
      if (license) {
        const licenseFileName = `${uuidv4()}_${license.name}`;
        try {
          console.log({path2: license.path})
          const licenseUpload = await bucket.upload(license.path, {
            destination: `licenses/${licenseFileName}`,
            metadata: {
              contentType: license.type,
            },
          });
          licenseUrl = `https://storage.googleapis.com/${bucket.name}/${licenseUpload[0].name}`;
        } catch (error) {
          // console.log({ error2: error })
          // return res.status(400).send(error.message);
        }
      }

      // Upload resume
      if (resume) {
        const resumeFileName = `${uuidv4()}_${resume.name}`;
        try {
          console.log({path3: resume.path})
          const resumeUpload = await bucket.upload(resume.path, {
            destination: `resumes/${resumeFileName}`,
            metadata: {
              contentType: resume.type,
            },
          });
          resumeUrl = `https://storage.googleapis.com/${bucket.name}/${resumeUpload[0].name}`;
        } catch (error) {
          // console.log({ error3: error })
          // return res.status(400).send(error.message);
        }
      };

      await admin.firestore().collection("doctors").doc(userId).set({
        id: userId,
        name,
        surname,
        email,
        phone,
      });

      const adminsSnapshot = await admin.firestore().collection("admins").get();
      const adminEmails = adminsSnapshot.docs.map(doc => doc.data().email);

      res.status(201).send({ message: 'Please check you email to verify your account.', userId });
    } catch (error) {
      if (doctor && doctor.uid) {
        await admin.auth().deleteUser(doctor.uid);
      }
      console.log({ error })
      if (error.code) {
        return res.status(400).send(error.message);
      }
      res.status(500).send("Internal server error");
    }
  })
});

我打印文件路径的类型显示为字符串,但仍不清楚报错中提到的Object实例来源,使用formidable库解析multipart/form-data格式的请求。

解决方案

核心原因分析

报错指向paths[0]参数类型错误,本质是生产环境中bucket.upload()接收的路径参数并非预期的字符串,而是对象。这和formidable-serverless在Firebase Functions生产环境的临时文件处理逻辑有关——本地环境中文件路径是字符串,但生产环境可能因权限或库的兼容性问题,导致路径返回异常。

解决办法1:改用文件缓冲区直接上传

绕开文件路径读取,直接读取文件内容为缓冲区,调用bucket.file().save()上传:

// 替换原有的avatar上传代码
if (avatar) {
  const avatarFileName = `${uuidv4()}_${avatar.name}`;
  try {
    // 读取文件为缓冲区
    const fileBuffer = fs.readFileSync(avatar.path);
    // 创建Storage文件实例
    const targetFile = bucket.file(`doctorsImage/${avatarFileName}`);
    // 上传缓冲区内容
    await targetFile.save(fileBuffer, {
      metadata: {
        contentType: avatar.type,
      },
    });
    avatarUrl = `https://storage.googleapis.com/${bucket.name}/${targetFile.name}`;
  } catch (error) {
    console.log({ error1: error });
  }
}

按同样逻辑修改license和resume的上传代码。

解决办法2:替换formidable库

formidable-serverless可能存在环境兼容问题,建议改用Firebase官方推荐的busboy库处理multipart/form-data请求,或者升级formidable-serverless到最新版本。

解决办法3:检查临时目录权限

Firebase Functions生产环境的临时目录(/tmp)权限有限,确保代码中没有修改临时目录权限的操作,且文件路径指向的是/tmp下的有效文件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 21:35:56