如何在使用 Cloud Function 程序上传图像到 Firebase 存储后,以编程方式获取下载 URL?
如何在使用 Cloud Function 程序上传图像到 Firebase 存储后,以编程方式获取下载 URL?
在使用Functions for Firebase上传文件到Firebase Storage之后,我想要获取该文件的下载URL。
我有这个:
... return bucket .upload(fromFilePath, {destination: toFilePath}) .then((err, file) => { // Get the download url of file });
文件对象有很多参数,甚至有一个名为mediaLink
的参数。但是,如果我尝试访问此链接,我会得到以下错误:
匿名用户没有storage.objects.get访问对象的权限...
有人能告诉我如何获取公共下载URL吗?
谢谢
本答案将总结上传文件到Google/Firebase云存储时获取下载URL的选项。有三种类型的下载URL:
- 令牌下载URL,它们是持久性的,并具有安全功能
- 签名下载URL,它们是临时的,并具有安全功能
- 公共下载URL,它们是持久性的,并且缺乏安全性
有两种方法可以获得令牌下载URL。签名和公共下载URL每种只有一种获取方法。
令牌URL方法#1:从Firebase存储控制台获取
您可以从Firebase存储控制台获取下载URL:
下载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
第一部分是文件的标准路径。末尾是令牌。此下载URL是永久的,即它不会过期,但您可以撤销它。
令牌URL方法#2:来自前端
文档告诉我们要使用 getDownloadURL()
:
let url = await firebase.storage().ref('Audio/English/United_States-OED-' + i +'/' + $scope.word.word + ".mp3").getDownloadURL();
这获取与从Firebase存储控制台获取的相同的下载URL。此方法很容易,但需要您知道文件的路径,在我的应用程序中这很困难。您可以从前端上传文件,但这将使任何下载您的应用程序的人会看到您的凭据。因此,对于大多数项目,您将希望从您的云函数上传文件,然后获取下载URL并将其保存到与文件有关的其他数据及其数据库中。
当我从云函数将文件写入存储时,我找不到获取令牌下载URL的方法(因为我找不到一种方法告诉前端文件已写入存储),但对我有效的方法是将文件写入公开可用的URL,将公开可用的URL写入Firebase,然后当我的Angular前端从Firebase获取下载URL时,它还运行 getDownloadURL()
,其中包含令牌,然后将Firestore中的下载URL与令牌下载URL进行比较,如果它们不匹配,则将令牌下载URL更新到公开可用URL在Firestore中的位置。这仅向公众公开了您的文件一次。
这比听起来容易。以下代码遍历存储下载URL数组,并将公开可用的下载URL替换为令牌下载URL。
const storage = getStorage(); var audioFiles: string[] = []; if (this.pronunciationArray[0].pronunciation != undefined) { for (const audioFile of this.pronunciationArray[0].audioFiles) { // for each audio file in array let url = await getDownloadURL(ref(storage, audioFile)); // get the download url with token if (audioFile !== url) { // download URLs don't match audioFiles.push(url); } // end inner if }; // end for of loop if (audioFiles.length > 0) { // update Firestore only if we have new download URLs await updateDoc(doc(this.firestore, 'Dictionaries/' + this.l2Language.long + '/Words/' + word + '/Pronunciations/', this.pronunciationArray[0].pronunciation), { audioFiles: audioFiles }); } } // end outer if
您可能会认为,“我将从我的云函数将存储位置返回到我的前端,然后使用该位置与 getDownloadURL()
一起写入令牌下载URL Firestore。”那不起作用,因为云函数只能返回同步结果,异步操作会返回 null
。
"没问题," 你说。 "我会在 Storage 上设置一个 Observer,从 Observer 获取位置,然后使用该位置与 getDownloadURL()
一起将令牌下载 URL 写入 Firestore。" 不行。Firestore 有观察者,而 Storage 没有观察者。
"那么," 你说,"从我的前端调用 listAll(),获取所有 Storage 文件列表,然后调用每个文件的 metadata,提取每个文件的下载 URL 和令牌,然后将这些写入 Firestore 如何?" 尝试不错,但是 Storage metadata 不包括下载 URL 或令牌。
签名 URL 方法 #1:用于临时下载 URL 的 getSignedUrl()
getSignedUrl() 在 Cloud Function 中易于使用:
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); } }); }); }); }); }
一个签名的下载 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
签名 URL 有一个过期日期和长的签名。命令行gsutil signurl -d的文档表示,签名 URL 是临时的:默认过期时间为一小时,最大过期时间为七天。
我要在这里抱怨一下 getSignedUrl 的文档从来没有说过你的签名 URL 将在一周后过期。文档代码将 3-17-2025
作为过期日期,暗示你可以将到期日设置在未来的几年。我的应用程序完美运行,然后一周后崩溃了。错误消息说签名不匹配,并没有说下载 URL 已过期。我对代码进行了各种更改,一切都好了...直到一周后又全部崩溃了。这持续了一个多月的挫折感。 3-17-2025
日期是内部玩笑吗?就像当小精灵不在视野范围内时,其金币会消失一样,一个未来数年的圣帕特里克节过期日期在两周后消失,正当你认为自己的代码没有缺陷时。
公共 URL #1:使您的文件公开可用
你可以将你的文件权限设置为公共读取,如文档中所解释的那样。这可以通过 Cloud Storage Browser 或您的 Node 服务器完成。您可以使一个文件公共或整个 Storage 数据库的一个目录或全部文件都公开可用。下面是 Node 代码:
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)); });
你在 Cloud Storage Browser 中将看到如下结果:
任何人都可以使用标准路径下载您的文件:
https://storage.googleapis.com/languagetwo-cd94d.appspot.com/Audio/English/United_States-OED-0/system.mp3
另一种将文件公开的方法是使用方法makePublic()。我无法让其起作用,正确设置桶和文件路径很棘手。
一个有趣的选择是使用访问控制列表。您可以仅将文件提供给您列出的用户,或使用authenticatedRead
将文件提供给从谷歌帐户登录的任何人。如果有一个“使用Firebase Auth登录我的应用程序的任何人”的选项,我会使用它,因为这将仅限制对我的用户的访问。
已弃用:使用firebaseStorageDownloadTokens构建自己的下载URL
几个答案描述了一个未记录的谷歌存储对象属性firebaseStorageDownloadTokens
。这从未是官方谷歌云存储功能,它已不再起作用。以下是它的工作原理。
您告诉Storage要使用的令牌。然后您使用uuid
Node模块生成令牌。四行代码,您就可以构建自己的下载URL,与控制台或getDownloadURL()
获得的相同下载URL。这四行代码是:
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);
这里是完整的代码:
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)); });
这不是笔误-您必须在metadata:
的双重层中嵌套firebaseStorageDownloadTokens
!
您需要使用getSignedUrl通过@google-cloud/storage NPM模块生成已签名的URL。
示例:
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 });
您需要使用您的服务账户凭证初始化@google-cloud/storage
,因为应用程序默认凭据不足以使用。
更新:现在可以通过Firebase Admin SDK访问Cloud Storage SDK,它会作为@google-cloud/storage的包装器。它只会是:
- 使用特殊服务帐户启动SDK,通常通过第二个非默认实例。
- 或者,不使用服务帐户,通过将默认的App Engine服务帐户授予“signBlob”权限来进行授权。