node.js 从使用 Cloud Functions for Firebase 上传的文件中获取下载 URL

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/42956250/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 18:06:25  来源:igfitidea点击:

Get Download URL from file uploaded with Cloud Functions for Firebase

node.jsfirebasefirebase-storagegoogle-cloud-functions

提问by Valentin

After uploading a file in Firebase Storage with Functions for Firebase, I'd like to get the download url of the file.

使用 Firebase 函数在 Firebase 存储中上传文件后,我想获取文件的下载 url。

I have this :

我有这个 :

...

return bucket
    .upload(fromFilePath, {destination: toFilePath})
    .then((err, file) => {

        // Get the download url of file

    });

The object file has a lot of parameters. Even one named mediaLink. However, if I try to access this link, I get this error :

目标文件有很多参数。甚至一个名为mediaLink. 但是,如果我尝试访问此链接,则会收到此错误:

Anonymous users does not have storage.objects.get access to object ...

匿名用户没有 storage.objects.get 访问对象...

Can somebody tell me how to get the public download Url?

有人可以告诉我如何获得公共下载网址吗?

Thank you

谢谢

回答by James Daniels

You'll need to generate a signed URL using getSignedURLvia the @google-cloud/storageNPM module.

您需要通过@google-cloud/storageNPM 模块使用getSignedURL生成签名 URL 。

Example:

例子:

const gcs = require('@google-cloud/storage')({keyFilename: 'service-account.json'});
// ...
const bucket = gcs.bucket(bucket);
const file = bucket.file(fileName);
return file.getSignedUrl({
  action: 'read',
  expires: '03-09-2491'
}).then(signedUrls => {
  // signedUrls[0] contains the file's public URL
});

You'll need to initialize @google-cloud/storagewith your service account credentialsas the application default credentials will not be sufficient.

您需要@google-cloud/storage使用您的服务帐户凭据进行初始化,因为应用程序默认凭据是不够的。

UPDATE: The Cloud Storage SDK can now be accessed via the Firebase Admin SDK, which acts as a wrapperaround @google-cloud/storage. The only way it will is if you either:

更新:现在可以通过 Firebase Admin SDK 访问 Cloud Storage SDK,它充当@google-cloud/storage的包装器。唯一的方法是,如果您:

  1. Init the SDK with a special service account, typically through a second, non-default instance.
  2. Or, without a service account, by giving the default App Engine service account the "signBlob" permission.
  1. 使用特殊服务帐户初始化 SDK,通常是通过第二个非默认实例。
  2. 或者,在没有服务帐户的情况下,通过向默认 App Engine 服务帐户授予“signBlob”权限。

回答by Drew Beaupre

Here's an example on how to specify the download token on upload:

以下是有关如何在上传时指定下载令牌的示例:

const UUID = require("uuid-v4");

const fbId = "<YOUR APP ID>";
const fbKeyFile = "./YOUR_AUTH_FIlE.json";
const gcs = require('@google-cloud/storage')({keyFilename: fbKeyFile});
const bucket = gcs.bucket(`${fbId}.appspot.com`);

var upload = (localFile, remoteFile) => {

  let uuid = UUID();

  return bucket.upload(localFile, {
        destination: remoteFile,
        uploadType: "media",
        metadata: {
          contentType: 'image/png',
          metadata: {
            firebaseStorageDownloadTokens: uuid
          }
        }
      })
      .then((data) => {

          let file = data[0];

          return Promise.resolve("https://firebasestorage.googleapis.com/v0/b/" + bucket.name + "/o/" + encodeURIComponent(file.name) + "?alt=media&token=" + uuid);
      });
}

then call with

然后打电话给

upload(localPath, remotePath).then( downloadURL => {
    console.log(downloadURL);
  });

The key thing here is that there is a metadataobject nested within the metadataoption property. Setting firebaseStorageDownloadTokensto a uuid-v4 value will tell Cloud Storage to use that as its public auth token.

这里的关键是metadatametadataoption 属性中嵌套了一个对象。设置firebaseStorageDownloadTokens为 uuid-v4 值将告诉 Cloud Storage 将其用作其公共身份验证令牌。

Many thanks to @martemorfosis

非常感谢@martemorfosis

回答by Thomas David Kehoe

This answer will summarize the options for getting the download URL when uploading a file to Google/Firebase Cloud Storage. There are three types of download URLS:

此答案将总结在将文件上传到 Google/Firebase 云存储时获取下载 URL 的选项。下载 URL 分为三种类型:

  1. signed download URLs, which are temporary and have security features
  2. token download URLs, which are persistent and have security features
  3. public download URLs, which are persistent and lack security
  1. 已签名的下载 URL,它们是临时的且具有安全功能
  2. 令牌下载 URL,它们是持久的并且具有安全功能
  3. 公共下载 URL,持久且缺乏安全性

There are three ways to get a token download URL. The other two download URLs have only one way to get them.

可以通过三种方式获取令牌下载 URL。其他两个下载 URL 只有一种获取方式。

From the Firebase Storage Console

从 Firebase 存储控制台

You can get the download URL from Firebase Storage console:

您可以从 Firebase Storage 控制台获取下载 URL:

enter image description here

在此处输入图片说明

The download URL looks like this:

下载 URL 如下所示:

https://firebasestorage.googleapis.com/v0/b/languagetwo-cd94d.appspot.com/o/Audio%2FEnglish%2FUnited_States-OED-0%2Fabout.mp3?alt=media&token=489c48b3-23fb-4270-bd85-0a328d2808e5

The first part is a standard path to your file. At the end is the token. This download URL is permanent, i.e., it won't expire, although you can revoke it.

第一部分是文件的标准路径。最后是令牌。此下载 URL 是永久性的,即它不会过期,但您可以撤销它。

getDownloadURL() From the Front End

getDownloadURL() 从前端

The documentationtells us to use getDownloadURL():

文件告诉我们要使用getDownloadURL()

let url = await firebase.storage().ref('Audio/English/United_States-OED-' + i +'/' + $scope.word.word + ".mp3").getDownloadURL();

This gets the same download URL that you can get from your Firebase Storage console. This method is easy but requires that you know the path to your file, which in my app is about 300 lines of code, for a relatively simple database structure. If your database is complex this would be a nightmare. And you could upload files from the front end, but this would expose your credentials to anyone who downloads your app. So for most projects you'll want to upload your files from your Node back end or Google Cloud Functions, then get the download URL and save it to your database along with other data about your file.

这与您可以从 Firebase Storage 控制台获得的下载 URL 相同。这种方法很简单,但要求您知道文件的路径,在我的应用程序中,对于相对简单的数据库结构,大约有 300 行代码。如果您的数据库很复杂,这将是一场噩梦。您可以从前端上传文件,但这会将您的凭据暴露给下载您的应用程序的任何人。因此,对于大多数项目,您需要从 Node 后端或 Google Cloud Functions 上传文件,然后获取下载 URL 并将其与有关文件的其他数据一起保存到数据库中。

getSignedUrl() for Temporary Download URLs

getSignedUrl() 用于临时下载 URL

getSignedUrl()is easy to use from a Node back end or Google Cloud Functions:

getSignedUrl()很容易从 Node 后端或 Google Cloud Functions 使用:

  function oedPromise() {
    return new Promise(function(resolve, reject) {
      http.get(oedAudioURL, function(response) {
        response.pipe(file.createWriteStream(options))
        .on('error', function(error) {
          console.error(error);
          reject(error);
        })
        .on('finish', function() {
          file.getSignedUrl(config, function(err, url) {
            if (err) {
              console.error(err);
              return;
            } else {
              resolve(url);
            }
          });
        });
      });
    });
  }

A signed download URL looks like this:

签名的下载 URL 如下所示:

https://storage.googleapis.com/languagetwo-cd94d.appspot.com/Audio%2FSpanish%2FLatin_America-Sofia-Female-IBM%2Faqu%C3%AD.mp3?GoogleAccessId=languagetwo-cd94d%40appspot.gserviceaccount.com&Expires=4711305600&Signature=WUmABCZIlUp6eg7dKaBFycuO%2Baz5vOGTl29Je%2BNpselq8JSl7%2BIGG1LnCl0AlrHpxVZLxhk0iiqIejj4Qa6pSMx%2FhuBfZLT2Z%2FQhIzEAoyiZFn8xy%2FrhtymjDcpbDKGZYjmWNONFezMgYekNYHi05EPMoHtiUDsP47xHm3XwW9BcbuW6DaWh2UKrCxERy6cJTJ01H9NK1wCUZSMT0%2BUeNpwTvbRwc4aIqSD3UbXSMQlFMxxWbPvf%2B8Q0nEcaAB1qMKwNhw1ofAxSSaJvUdXeLFNVxsjm2V9HX4Y7OIuWwAxtGedLhgSleOP4ErByvGQCZsoO4nljjF97veil62ilaQ%3D%3D

The signed URL has an expiration date and long signature. The documentation for the command line gsutil signurl -dsays that signed URLs are temporary: the default expiration is one hour and the maximum expiration is seven days.

签名 URL 具有到期日期和长签名。命令行gsutil signurl -d的文档说签名 URL 是临时的:默认过期时间为一小时,最长过期时间为 7 天。

I'm going to rant here that getSignedUrlnever says that your signed URL will expire in a week. The documentation code has 3-17-2025as the expiration date, suggesting that you can set the expiration years in the future. My app worked perfectly, and then crashed a week later. The error message said that the signatures didn't match, not that the download URL had expired. I made various changes to my code, and everything worked...until it all crashed a week later. This went on for more than a month of frustration.

我要在这里咆哮getSignedUrl从来没有说你的签名 URL 会在一周内过期。文档代码有3-17-2025作为过期日期,建议您可以设置未来的过期年份。我的应用程序运行良好,然后在一周后崩溃了。错误消息表示签名不匹配,而不是下载 URL 已过期。我对我的代码进行了各种更改,一切正常……直到一周后它都崩溃了。这持续了一个多月的挫败感。

Make Your File Publicly Available

公开您的文件

You can set the permissions on your file to public read, as explained in the documentation. This can be done from the Cloud Storage Browser or from your Node server. You can make one file public or a directory or your entire Storage database. Here's the Node code:

您可以将文件的权限设置为公开读取,如文档中所述。这可以通过云存储浏览器或您的节点服务器完成。您可以公开一个文件或一个目录或整个 Storage 数据库。这是节点代码:

var webmPromise = new Promise(function(resolve, reject) {
      var options = {
        destination: ('Audio/' + longLanguage + '/' + pronunciation + '/' + word + '.mp3'),
        predefinedAcl: 'publicRead',
        contentType: 'audio/' + audioType,
      };

      synthesizeParams.accept = 'audio/webm';
      var file = bucket.file('Audio/' + longLanguage + '/' + pronunciation + '/' + word + '.webm');
      textToSpeech.synthesize(synthesizeParams)
      .then(function(audio) {
        audio.pipe(file.createWriteStream(options));
      })
      .then(function() {
        console.log("webm audio file written.");
        resolve();
      })
      .catch(error => console.error(error));
    });

The result will look like this in your Cloud Storage Browser:

结果在您的云存储浏览器中将如下所示:

enter image description here

在此处输入图片说明

Anyone can then use the standard path to download your file:

然后任何人都可以使用标准路径下载您的文件:

https://storage.googleapis.com/languagetwo-cd94d.appspot.com/Audio/English/United_States-OED-0/system.mp3

Another way to make a file public is to use the method makePublic(). I haven't been able to get this to work, it's tricky to get the bucket and file paths right.

另一种公开文件的方法是使用makePublic()方法。我一直无法让它工作,让存储桶和文件路径正确是很棘手的。

An interesting alternative is to use Access Control Lists. You can make a file available only to users whom you put on a list, or use authenticatedReadto make the file available to anyone who is logged in from a Google account. If there were an option "anyone who logged into my app using Firebase Auth" I would use this, as it would limit access to only my users.

一个有趣的替代方法是使用访问控制列表。您可以将文件仅提供给您列入列表的用户使用,或用于authenticatedRead将文件提供给从 Google 帐户登录的任何人。如果有一个选项“任何使用 Firebase Auth 登录我的应用程序的人”,我都会使用它,因为它会限制只有我的用户才能访问。

Build Your Own Download URL with firebaseStorageDownloadTokens

使用 firebaseStorageDownloadTokens 构建您自己的下载 URL

Several answers describe an undocumented Google Storage object property firebaseStorageDownloadTokens. With this you can tell Storage the token you want to use. You can generate a token with the uuidNode module. Four lines of code and you can build your own download URL, the same download URL you get from the console or getDownloadURL(). The four lines of code are:

几个答案描述了一个未记录的 Google Storage 对象属性firebaseStorageDownloadTokens。有了这个,您可以告诉 Storage 您要使用的令牌。您可以使用uuidNode 模块生成令牌。四行代码,您可以构建自己的下载 URL,与您从控制台或getDownloadURL(). 这四行代码是:

const uuidv4 = require('uuid/v4');
const uuid = uuidv4();
metadata: { firebaseStorageDownloadTokens: uuid }
https://firebasestorage.googleapis.com/v0/b/" + bucket.name + "/o/" + encodeURIComponent('Audio/' + longLanguage + '/' + pronunciation + '/' + word + '.webm') + "?alt=media&token=" + uuid);

Here's the code in context:

这是上下文中的代码:

var webmPromise = new Promise(function(resolve, reject) {
  var options = {
    destination: ('Audio/' + longLanguage + '/' + pronunciation + '/' + word + '.mp3'),
    contentType: 'audio/' + audioType,
    metadata: {
      metadata: {
        firebaseStorageDownloadTokens: uuid,
      }
    }
  };

      synthesizeParams.accept = 'audio/webm';
      var file = bucket.file('Audio/' + longLanguage + '/' + pronunciation + '/' + word + '.webm');
      textToSpeech.synthesize(synthesizeParams)
      .then(function(audio) {
        audio.pipe(file.createWriteStream(options));
      })
      .then(function() {
        resolve("https://firebasestorage.googleapis.com/v0/b/" + bucket.name + "/o/" + encodeURIComponent('Audio/' + longLanguage + '/' + pronunciation + '/' + word + '.webm') + "?alt=media&token=" + uuid);
      })
      .catch(error => console.error(error));
});

That's not a typo--you have to nest firebaseStorageDownloadTokensin double layers of metadata:!

这不是打错字——你必须嵌套firebaseStorageDownloadTokens在双层metadata:!

Doug Stevenson pointed out that firebaseStorageDownloadTokensis not an official Google Cloud Storage feature. You won't find it in any Google documentation, and there's no promise it will be in future version of @google-cloud. I like firebaseStorageDownloadTokensbecause it's the only way to get what I want, but it has a "smell" that it's not safe to use.

道格史蒂文森指出,这firebaseStorageDownloadTokens不是谷歌云存储的官方功能。您不会在任何 Google 文档中找到它,也不保证它会出现在@google-cloud. 我喜欢,firebaseStorageDownloadTokens因为这是获得我想要的东西的唯一方法,但它有一种“气味”,使用起来并不安全。

Why No getDownloadURL() from Node?

为什么没有来自 Node 的 getDownloadURL()?

As @Clinton wrote, Google should make a file.getDownloadURL()a method in @google-cloud/storage(i.e., your Node back end). I want to upload a file from Google Cloud Functions and get the token download URL.

正如@Clinton 所写,Google 应该file.getDownloadURL()@google-cloud/storage(即您的 Node 后端)中创建一个方法。我想从 Google Cloud Functions 上传文件并获取令牌下载 URL。

回答by Demian S

With the recent changes in the functions objectresponse you can get everything you need to "stitch" together the download URL like so:

随着函数对象响应的最新变化,您可以获得“拼接”下载 URL 所需的一切,如下所示:

 const img_url = 'https://firebasestorage.googleapis.com/v0/b/[YOUR BUCKET]/o/'
+ encodeURIComponent(object.name)
+ '?alt=media&token='
+ object.metadata.firebaseStorageDownloadTokens;

console.log('URL',img_url);

回答by SMX

If you're working on a Firebase project, you can create signed URLs in a Cloud Function without including other libraries or downloading a credentials file. You just need to enable the IAM API and add a role to your existing service account (see below).

如果您正在处理 Firebase 项目,则可以在 Cloud Function 中创建签名网址,而无需包含其他库或下载凭据文件。您只需要启用 IAM API 并向您现有的服务账户添加一个角色(见下文)。

Initialize the admin library and get a file reference as your normally would:

像往常一样初始化管理库并获取文件引用:

import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'

admin.initializeApp(functions.config().firebase)

const myFile = admin.storage().bucket().file('path/to/my/file')

You then generate a signed URL with

然后您生成一个签名的 URL

myFile.getSignedUrl({action: 'read', expires: someDateObj}).then(urls => {
    const signedUrl = urls[0]
})

Make sure your Firebase service account has sufficient permissions to run this

确保您的 Firebase 服务帐户有足够的权限来运行它

  1. Go to the Google API console and enable the IAM API (https://console.developers.google.com/apis/api/iam.googleapis.com/overview)
  2. Still in the API console, go to the main menu, "IAM & admin" -> "IAM"
  3. Click edit for the "App Engine default service account" role
  4. Click "Add another role", and add the one called "Service Account Token Creator"
  5. Save and wait a minute for the changes to propagate
  1. 转到 Google API 控制台并启用 IAM API ( https://console.developers.google.com/apis/api/iam.googleapis.com/overview)
  2. 还是在API控制台,进入主菜单,“IAM & admin”->“IAM”
  3. 单击“App Engine 默认服务帐户”角色的编辑
  4. 点击“添加另一个角色”,添加名为“Service Account Token Creator”的角色
  5. 保存并等待更改传播

With a vanilla Firebase config, the first time you run the above code you'll get an error Identity and Access Management (IAM) API has not been used in project XXXXXX before or it is disabled.. If you follow the link in the error message and enable the IAM API, you'll get another error: Permission iam.serviceAccounts.signBlob is required to perform this operation on service account my-service-account. Adding the Token Creator role fixes this second permission issue.

使用 vanilla Firebase 配置时,第一次运行上述代码时,您会收到错误Identity and Access Management (IAM) API has not been used in project XXXXXX before or is disabled. . 如果您点击错误消息中的链接并启用 IAM API,您将收到另一个错误:Permission iam.serviceAccounts.signBlob is required to perform this operation on service account my-service-account。添加 Token Creator 角色修复了第二个权限问题。

回答by Clinton

For those wondering where the Firebase Admin SDK serviceAccountKey.json file should go. Just place it in the functions folder and deploy as usual.

对于那些想知道 Firebase Admin SDK serviceAccountKey.json 文件应该放在何处的人。只需将它放在函数文件夹中并像往常一样部署。

It still baffles me why we can't just get the download url from the metadata like we do in the Javascript SDK. Generating a url that will eventually expire and saving it in the database is not desirable.

它仍然让我感到困惑,为什么我们不能像在 Javascript SDK 中那样从元数据中获取下载 url。生成一个最终会过期的 url 并将其保存在数据库中是不可取的。

回答by Laurent

I suggest using the option predefinedAcl: 'publicRead'when uploading a file with Cloud Storage NodeJS 1.6.xor +:

我建议在使用Cloud Storage NodeJS 1.6.x或 +predefinedAcl: 'publicRead'上传文件时使用该选项:

const options = {
    destination: yourFileDestination,
    predefinedAcl: 'publicRead'
};

bucket.upload(attachment, options);

Then, getting the public URL is as simple as:

然后,获取公共 URL 非常简单:

bucket.upload(attachment, options).then(result => {
    const file = result[0];
    return file.getMetadata();
}).then(results => {
    const metadata = results[0];
    console.log('metadata=', metadata.mediaLink);
}).catch(error => {
    console.error(error);
});

回答by martemorfosis

One method I'm using with success is to set a UUID v4 value to a key named firebaseStorageDownloadTokensin the metadata of the file after it finishes uploading and then assemble the download URL myself following the structure Firebase uses to generate these URLs, eg:

我成功使用的一种方法是firebaseStorageDownloadTokens在完成上传后将UUID v4 值设置为文件元数据中命名的键,然后按照 Firebase 用于生成这些 URL 的结构自己组装下载 URL,例如:

https://firebasestorage.googleapis.com/v0/b/[BUCKET_NAME]/o/[FILE_PATH]?alt=media&token=[THE_TOKEN_YOU_CREATED]

I don't know how much "safe" is to use this method (given that Firebase could change how it generates the download URLs in the future ) but it is easy to implement.

我不知道使用这种方法有多“安全”(假设 Firebase 将来可能会改变它生成下载 URL 的方式),但它很容易实现。

回答by NiVeK92

Sorry but i can't post a comment to your question above because of missing reputation, so I will include it in this answer.

抱歉,由于缺少声誉,我无法对上述问题发表评论,因此我会将其包含在此答案中。

Do as stated above by generating a signed Url, but instead of using the service-account.json I think you have to use the serviceAccountKey.json which you can generate at (replace YOURPROJECTID accordingly)

如上所述通过生成一个签名的 Url,但不是使用 service-account.json 我认为你必须使用你可以生成的 serviceAccountKey.json (相应地替换 YOURPROJECTID)

https://console.firebase.google.com/project/YOURPROJECTID/settings/serviceaccounts/adminsdk

https://console.firebase.google.com/project/YOURPROJECTID/settings/serviceaccounts/adminsdk

Example:

例子:

const gcs = require('@google-cloud/storage')({keyFilename: 'serviceAccountKey.json'});
// ...
const bucket = gcs.bucket(bucket);
// ...
return bucket.upload(tempLocalFile, {
        destination: filePath,
        metadata: {
          contentType: 'image/jpeg'
        }
      })
      .then((data) => {
        let file = data[0]
        file.getSignedUrl({
          action: 'read',
          expires: '03-17-2025'
        }, function(err, url) {
          if (err) {
            console.error(err);
            return;
          }

          // handle url 
        })

回答by Renji

I can't comment on the answer James Daniels gave, but I think this is very Important to read.

我无法对 James Daniels 给出的答案发表评论,但我认为这是非常重要的阅读。

Giving out a signed URL Like he did seems for many cases pretty badand possible Dangerous. According to the documentation of Firebase the signed url expires after some time, so adding that to your databse will lead to a empty url after a certain timeframe

像他一样给出一个签名的 URL 在很多情况下看起来很糟糕,可能是危险的。根据 Firebase 的文档,签名的 url 会在一段时间后过期,因此将其添加到您的数据库将在特定时间范围后导致空 url

It may be that misunderstood the Documentation there and the signed url doesn't expire, which would have some security issues as a result. The Key seems to be the same for every uploaded file. This means once you got the url of one file, someone could easily access files that he is not suposed to access, just by knowing their names.

可能是误解了那里的文档并且签名的 url 没有过期,这会导致一些安全问题。每个上传的文件的密钥似乎都相同。这意味着一旦您获得了一个文件的 url,某人就可以轻松访问他不应该访问的文件,只需知道他们的名字。

If i missunderstood that then i would lvoe to be corrected. Else someone should probably Update the above named solution. If i may be wrong there

如果我误解了,那么我会得到纠正。其他人可能应该更新上面命名的解决方案。如果我可能错了