【问题标题】:Error while uploading file to Firebase Storage using Firebase Cloud Functions使用 Firebase Cloud Functions 将文件上传到 Firebase 存储时出错
【发布时间】:2021-06-16 06:16:27
【问题描述】:

我正在尝试使用 Firebase Cloud Functions 将 pdf 上传到 Firebase 存储,我有这个带有以下正文的 post 函数:

{
    "email":"gianni@test.it",
    "name":"Gianni",
    "surname":"test",
    "cellphone":"99999999",
    "data": 
    {
    "file":BASE_64,
    "fileName":"test.pdf"
    }
}

我想将base64值保存在“file”字段中并命名为“fileName”字段,这里是保存文件的函数:

const admin = require("firebase-admin");
  /**
   * Create a new file in the storage.
   * @param {Object} wrapper [File to upload in the storage.]
   * @param {String} path [Path to upload file to.]
   * @return {object} [Containing the response]
   */
  postStorageFileAsync(wrapper, path) {
    return new Promise((res, rej)=>{
      System.prototype.writeLog({
        wrapper: wrapper,
        path: path,
      });
      const newFile = admin.storage().bucket().file(wrapper.path);
      return newFile.save(wrapper.file).then((snapshot) => {
        System.prototype.writeLog({snap: snapshot});
        return snapshot.ref.getDownloadURL().then((downloadURL) => {
          System.prototype.writeLog({fileUrl: downloadURL});
          return res({code: 200, data: {url: downloadURL}});
        });
      }).catch((err)=>{
        System.writeLog(err);
        return rej(err);
      });
    });
  }

但我得到了:

课后

错误:必须指定文件名。在 Bucket.file (/workspace/node_modules/@google-cloud/storage/build/src/bucket.js:1612:19) at /workspace/Firebase/Firebase.js:43:48 at new Promise () 在 Object. postStorageFileAsync (/workspace/Firebase/Firebase.js:38:12) at /workspace/PersistanceStorage/PersistanceStorage.js:407:35 at processTicksAndRejections (internal/process/task_queues.js:97:5)

除了错误本身,是否有人提供有关如何通过函数将文件上传到 Firebase 存储的有效示例/教程链接?文档真的很缺乏。

谢谢

【问题讨论】:

  • wrapper.path 似乎为空或未定义。另外,没有将文件直接上传到 Cloud Storage 的任何特殊原因(并在必要时触发 Cloud Function 进行任何额外处理)?
  • 您好 Renauld,感谢您的回答,我打印了 wrapper.path 值,它确实已填充,我会更加努力地搜索。无论如何,我在前端使用官方 API 没有问题,但我想实现这个“实验”,因为我认为这是一种更正确的方法:你可以对 REST 端点进行一次调用而不是 2 次调用:(一个到 firebase API 来上传文件并获取 url,一个到你的端点。),因此实际上最大限度地减少了资源使用和可能的传输错误。

标签: node.js firebase google-cloud-functions google-cloud-storage firebase-storage


【解决方案1】:

因此,根据 Donnald Cucharo 的回答,我将为像我这样仍在使用 javascript 引擎(不支持方法签名中的异步)而不是 typescript 引擎的人发布我的工作解决方案:

步骤 1) 在您的项目中安装@google-cloud/storage npm,据我了解,每个 Firebase 存储都构建在谷歌云存储实例之上。

第 2 步)按照 this guide 从您的 firebase 项目控制台生成一个管理员密钥,以便将其上传到您的函数文件夹中,以授权您的函数项目与您的存储项目进行身份验证和通信。

第 3 步)使用以下代码(它与 Donnald Cucharo 的解决方案非常相似,但大量使用了 Promise):

const functions = require("firebase-functions");
const path = require("path");
const os = require("os");

  
class System {
  
  ...

  /**
   * Extract th extension of a file from it's path, and returns it.
   * @param {String} directory [Dir from where to extract the extension from.]
   * @return {String} [Containing the extension of the file from the directory or undefined]
   */
  getFileExtensionFromDirectory(directory) {
    const ext = path.extname(directory);
    if (ext === "") {
      return undefined;
    }
    return ext;
  }

  /**
   * Extract the name of the file from a directory.
   * @param {String} directory [Dir from where to extract the extension from.]
   * @return {String} [Containing the filename]
   */
  getFileNameFromDirectory(directory) {
    const extension = this.getFileExtensionFromDirectory(directory);
    if (extension === undefined) return extension;
    return path.basename(directory, extension);
  }

  /**
   * Returns the system temp directory.
   * @return {String} [Containing the system temporary directory.]
   */
  getSystemTemporarydirectory() {
    return os.tmpdir();
  }
   
}

module.exports = System;

然后,是时候实际上传文件了:

const admin = require("firebase-admin");
const functions = require("firebase-functions");
const bucket = admin.storage().bucket(functions.config().bucket.url);
const System = require("../System/System");
const fs = require("fs");

/**
 * Pure fabrication class to access the Firebase services
 */
class Firebase {

  /**
   * Create a new file in the storage.
   * @param {Object} wrapper [File to upload in the storage.]
   * @param {String} path [Path to upload file to.]
   * @return {object} [Containing the response]
   */
  postStorageFileAsync(wrapper) {
    return new Promise((res, rej)=>{
      if (wrapper.file === undefined) {
        return rej({code: 400, error:
          {it: "Devi forninre il valore in base64 del file che vuoi caricare.",
            en: "You must provide the base64 value of the file to upload."}});
      }


      if (wrapper.path === undefined) {
        return rej({code: 400, error:
          {it: "Devi fornire il percorso dove vuoi caricare il tuo file.",
            en: "Missing path filed in wrapper."}});
      }

      const fileName = System.prototype.getFileNameFromDirectory(wrapper.path);
      const fileExtension = System.prototype.getFileExtensionFromDirectory(wrapper.path);

      if (fileName === undefined || fileExtension === undefined) {
        return rej({code: 400, error:
          {it: "Formato del file non riconosciuto.",
            en: "Unrecognized file type or file name, the file should be in fileName.extension format"}});
      }

      const file = fileName + fileExtension;
      const tmpDirectory = System.prototype.getSystemTemporarydirectory() + "/" + file;

      return fs.promises.writeFile(tmpDirectory, wrapper.file, "base64").then(()=>{
        const options = {
          destination: wrapper.path,
        };

        return bucket.upload(tmpDirectory, options).then(() =>{
          fs.unlinkSync(tmpDirectory);
          return this.getStorageFileFromPathAsync(wrapper.path).then((response)=>{
            return res(response);
          });
        });
      }).catch((err)=>{
        fs.unlinkSync(tmpDirectory);
        System.prototype.writeLog(err);
        return rej({code: 500, error: err});
      });
    });
  }

  /**
   * Retrieve the url of a file in the storage from the path.
   * @param {String} path [Path of the file to get the url]
   * @return {Object} [Containing te response]
   */
  getStorageFileFromPathAsync(path) {
    return new Promise((res, rej)=>{
      bucket.file(path).makePublic().then(()=>{
        bucket.file(path).getMetadata() .then((response)=>{
          System.prototype.writeLog({response: response});
          const metadata = response[0];
          System.prototype.writeLog({metadata: metadata});
          return res({code: 200, url: metadata.mediaLink});
        });
      }).catch((err)=>{
        System.prototype.writeLog(err);
        return rej({code: 500, error: err});
      });
    });
  }

}

module.exports = Firebase;

现在让我们谈谈代码,因为有些部分我很难理解,但主要的原因是这样的:

  1. 我们需要在系统的临时目录下创建一个空文件目录。

  2. 通过使用 fs 库,我们需要打开一个流并将数据转储到我们创建的临时目录中:

    fs.promises.writeFile(tmpDirectory, wrapper.file, "base64").then(()=>{ ....})

  3. 我们现在可以使用我们在步骤 1-2 中创建的路径将文件上传到 firebase 存储,但我们还无法访问下载 url。

  4. 因为我们在更改新创建的文件可见性并最终通过使用 getStorageFileFromPathAsync() 方法获取下载 url 之前作为管理员进行了身份验证。

希望对你有帮助。

【讨论】:

    【解决方案2】:

    除了@Renaud 的评论,Firebase Admin SDK 还依赖Cloud Storage client library 来访问存储桶。如果您查看 Nodejs API 文档,您会发现 getDownloadURL() 不存在,因此如果您想继续使用 Admin SDK 并获取下载 URL,则必须在文件上传后获取文件的元数据。

    这是我想出的示例代码:

    const admin = require("firebase-admin");
    const os = require('os');
    const fs = require('fs');
    const path = require('path');
    
    ...
    
    const wrapper = {
      "file" : "BASE_64",
      "fileName" : "filename.pdf",
    }
    const pathName = "test/dir"
    
    const bucket = admin.storage().bucket();
    
    async function postStorageFileAsync(wrapper, pathName) {
      // Convert Base64 to PDF. You're only allowed to write in /tmp on Cloud Functions
      const tmp = `${os.tmpdir()}/converted.pdf`
      fs.writeFileSync(tmp, wrapper.file, 'base64', (error) => {
        if (error) throw error;
      });   
      
      // Upload to GCS
      const target = path.join(pathName,wrapper.fileName);
      await bucket.upload(tmp, {
        destination: target
      }).then(res =>{
        fs.unlinkSync(tmp)
        console.log(`Uploaded to GCS`)
      })
    
      // Get Download URL
      const newFile =  await bucket.file(target); 
      const [metadata] = await newFile.getMetadata();
      const url = metadata.mediaLink;
      console.log(url);  
    }
    
    postStorageFileAsync(wrapper,pathName)
    

    请注意,在此代码中,对象不是公开的,因此未经授权访问 URL 将显示权限错误。如果您想让您的对象可供公众访问,请参阅File.makePublic()。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-03-23
      • 2019-04-08
      • 2023-04-01
      • 2020-01-25
      • 2018-01-14
      • 1970-01-01
      • 2018-01-12
      • 2021-11-04
      相关资源
      最近更新 更多