【问题标题】:How to upload a file into Firebase Storage from a callable https cloud function如何从可调用的 https 云函数将文件上传到 Firebase 存储
【发布时间】:2020-02-29 22:08:15
【问题描述】:

我一直在尝试使用可调用的 firebase 云函数将文件上传到 Firebase 存储。 我所做的只是使用axios 从 URL 中获取图像并尝试上传到存储。 我面临的问题是,我不知道如何保存来自 axios 的响应并将其上传到存储。

首先,如何将接收到的文件保存在os.tmpdir()创建的临时目录中。 然后如何将其上传到存储中。 在这里,我以arraybuffer 的形式接收数据,然后将其转换为 Blob 并尝试上传。 这是我的代码。我认为我错过了一个主要部分。 如果有更好的方法,请推荐我。我一直在查看大量文档,但没有找到明确的解决方案。请指导。提前致谢。


const bucket = admin.storage().bucket();
const path = require('path');
const os = require('os');
const fs = require('fs');
module.exports = functions.https.onCall((data, context) => {
  try {
    return new Promise((resolve, reject) => {
      const {
        imageFiles,
        companyPIN,
        projectId
      } = data;
      const filename = imageFiles[0].replace(/^.*[\\\/]/, '');
      const filePath = `ProjectPlans/${companyPIN}/${projectId}/images/${filename}`; // Path i am trying to upload in FIrebase storage
      const tempFilePath = path.join(os.tmpdir(), filename);
      const metadata = {
        contentType: 'application/image'
      };
      axios
        .get(imageFiles[0], { // URL for the image
          responseType: 'arraybuffer',
          headers: {
            accept: 'application/image'
          }
        })
        .then(response => {
          console.log(response);
          const blobObj = new Blob([response.data], {
            type: 'application/image'
          });
          return blobObj;
        })
        .then(async blobObj => {
          return bucket.upload(blobObj, {
            destination: tempFilePath    // Here i am wrong.. How to set the path of downloaded blob file
          });
        }).then(buffer => {
          resolve({ result: 'success' });
        })
        .catch(ex => {
          console.error(ex);
        });
    });
  } catch (error) {
    // unknown: 500 Internal Server Error
    throw new functions.https.HttpsError('unknown', 'Unknown error occurred. Contact the administrator.');
  }
});

【问题讨论】:

    标签: javascript firebase google-cloud-functions firebase-storage


    【解决方案1】:

    我会采取稍微不同的方法,并完全避免使用本地文件系统,因为它只是 tmpfs,并且会花费您的内存,您的函数无论如何都要使用它来保存缓冲区/blob,因此避免它更简单,并且使用 GCS 文件对象上的 save method 直接从该缓冲区写入 GCS。

    这是一个例子。我已经简化了很多设置,并且我使用的是 http 函数而不是可调用函数。同样,我使用的是公共 stackoverflow 图片,而不是您的原始网址。无论如何,您应该能够使用此模板修改回您需要的内容(例如,更改原型并删除 http 响应并将其替换为您需要的返回值):

    const functions = require('firebase-functions');
    const axios = require('axios');
    const admin = require('firebase-admin');
    admin.initializeApp();
    
    exports.doIt = functions.https.onRequest((request, response) => {
        const bucket = admin.storage().bucket();
        const IMAGE_URL = 'https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-logo.svg';
        const MIME_TYPE = 'image/svg+xml';
        return axios.get(IMAGE_URL, { // URL for the image
            responseType: 'arraybuffer',
            headers: {
              accept: MIME_TYPE
            }
          }).then(response => {
            console.log(response);  // only to show we got the data for debugging
            const destinationFile = bucket.file('my-stackoverflow-logo.svg');  
            return destinationFile.save(response.data).then(() => {  // note: defaults to resumable upload
              return destinationFile.setMetadata({ contentType: MIME_TYPE });
            });
          }).then(() => { response.send('ok'); })
          .catch((err) => { console.log(err); })
      });
    

    正如评论者所说,在上面的示例中,axios 请求本身会进行外部网络访问,您需要为此使用 Blaze 或 Flame 计划。但是,仅此一项似乎不是您当前的问题。

    同样,这也默认使用可恢复上传,the documentation 不建议您在处理大量小文件(


    您询问如何使用它来下载多个文件。这是一种方法。首先,假设您有一个函数返回一个承诺,该承诺根据文件名下载单个文件(我已经从上面删减了这一点,但除了将INPUT_URL 更改为filename 之外,它基本相同——注意它不返回最终结果,例如response.send(),并且隐含假设所有文件都相同MIME_TYPE):

    function downloadOneFile(filename) {
      const bucket = admin.storage().bucket();
      const MIME_TYPE = 'image/svg+xml';
      return axios.get(filename, ...)
        .then(response => {
           const destinationFile = ...
         });
    }
    

    然后,您只需要从文件列表中迭代地构建一个承诺链。假设他们在imageUrls。构建完成后,返回整个链:

    let finalPromise = Promise.resolve();
    imageUrls.forEach((item) => { finalPromise = finalPromise.then(() => downloadOneFile(item)); });
    
    // if needed, add a final .then() section for the actual function result
    
    return finalPromise.catch((err) => { console.log(err) });
    

    请注意,您还可以构建一个 Promise 数组并将它们传递给 Promise.all()——这可能会更快,因为您可以获得一些并行性,但我不建议您这样做,除非您非常确定所有数据将立即放入函数的内存中。即使使用这种方法,您也需要确保下载可以在您的函数超时时间内全部完成。

    【讨论】:

    • 不要忘记该项目需要采用“火焰”或“火焰”定价计划。事实上,免费的“Spark”计划“只允许向 Google 拥有的服务发出出站网络请求”。请参阅firebase.google.com/pricing(将鼠标悬停在“云功能”标题后面的问号上)
    • @RenaudTarnec 是的,谢谢。我已经扩展了答案以注意到这一点。
    • 像这样将其全部加载到内存中与将其加载到 /tmp 并没有什么不同。它们都是相同的内存,计费相同,并且在这两种情况下,文件在请求完成后占用的内存量相同。如果您真的想避免这样的内存使用,请将来自 HTTP 请求的流直接通过管道传输到 GCS 的写入流。
    • 没错。没有理由写入文件系统,因为它是更复杂的代码并且成本与仅在内存中缓冲相同。我还怀疑对于小文件,即使流式传输也会有类似的缓冲区,但是使用responseType: 'stream' 并将其连接到文件流是一种好方法(尽管它也会涉及some complexity to fit into the promise model,这与此关系不大问题)。
    • 对于多个文件,如何适配这个功能?
    猜你喜欢
    • 2020-11-10
    • 1970-01-01
    • 2017-12-21
    • 2020-07-05
    • 1970-01-01
    • 2021-10-28
    • 2022-12-15
    • 2020-04-08
    • 2018-11-16
    相关资源
    最近更新 更多