提问者:小点点

如何从可调用https云函数上传文件到Firebase存储


我一直在尝试使用一个可调用的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.');
  }
});


共1个答案

匿名用户

我将采取一种略微不同的方法,完全避免使用本地文件系统,因为它只是tmpfs,而且会占用您的函数用来保存缓冲区/blob的内存,因此更简单的方法是避免它,并使用GCS文件对象上的save方法直接从缓冲区写入GCS。

这里有一个例子。我已经简化了您的许多设置,我使用http函数而不是可调用函数。同样,我使用的是公共stackoverflow图像,而不是您的原始URL。在任何情况下,您都应该能够使用这个模板修改回您需要的内容(例如,更改原型并移除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计划。然而,这并不是你当前的问题。

同样,这也默认使用可恢复上传,当您正在处理大量小文件(<10MB文件)时,文档并不建议使用可恢复上传,因为这会带来一些开销。

您询问如何使用它来下载多个文件。这里有一种方法。首先,让我们假设您有一个函数,它返回一个承诺,该承诺将下载给定文件名的单个文件(我对上面的内容进行了删节,但除了将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 = ...
     });
}

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

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.all()-这可能会更快,因为您可以获得一些并行性,但是我不建议您这样做,除非您非常确定所有数据都能同时放入函数的内存中。即使使用这种方法,您也需要确保所有下载都能在函数超时内完成。