存储在Cloud Functions中:如何获取下载地址?
存储在Cloud Functions中:如何获取下载地址?
在Firebase Storage中使用Firebase Functions上传文件后,我想要获取该文件的下载链接。
我有以下代码:
... return bucket .upload(fromFilePath, {destination: toFilePath}) .then((err, file) => { // Get the download url of file });
文件对象有很多参数,其中一个被命名为mediaLink
。但是,如果我尝试访问此链接,我会收到以下错误信息:
匿名用户没有对象的storage.objects.get访问权限...
有没有人能告诉我如何获取公共下载网址呢?
谢谢
本答案将总结上传文件到Google/Firebase云存储时获取下载URL的选项。有三种类型的下载URL:
- 令牌下载URL,它们是持久的并具有安全功能
- 签名下载URL,这些是暂时的并具有安全功能
- 公共下载URL,这些是持久的并缺乏安全性
有两种方法可以获取令牌下载URL。签名和公共下载URL每个只有一种获取方式。
令牌URL方法1:从Firebase Storage控制台
您可以从Firebase Storage控制台获取下载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 Storage控制台获得的相同下载URL相同的下载URL。这种方法很简单,但需要您知道文件的路径,这在我的应用程序中很困难。您可以从前端上传文件,但这会将您的凭据暴露给下载您的任何人。因此,对于大多数项目,您将想要从您的Cloud Functions上传文件,然后获取下载URL并将其与有关文件的其他数据一起保存到数据库中。
我找不到一种方法来从Cloud Function将文件写入Storage时获取令牌下载URL(因为我找不到一种方法告诉前端文件已写入Storage),但对我有效的方法是将文件写入一个公开可用的URL,将公开可用的URL写入Firebase,然后当我的Angular前端从Firebase获取下载URL时,它还运行getDownloadURL()
,其中有令牌,然后将Firestore中的下载URL与令牌下载URL进行比较,如果不匹配,则替换Firestore中的公开可用URL的令牌下载URL。这只将您的文件暴露给公众一次。
这比听起来要容易得多。以下代码通过存储下载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
您可能会想,“我将从我的Cloud Function将Storage位置返回到我的前端,然后使用位置与getDownloadURL()
一起将令牌下载URL写入Firestore。”那不会起作用,因为Cloud Functions只能返回同步结果。异步操作返回null
。
"没问题,"你说。 "我会在Storage上设置一个观察者,从观察者获取位置,然后使用位置和getDownloadURL()
将令牌下载URL写入Firestore。" 不行。 Firestore 有观察者,而Storage 没有。
"你说怎么样,"你说, "我从我的前端调用listAll(),获取所有 Storage 文件的列表,然后调用每个文件的metadata,并提取每个文件的下载URL和令牌,然后将它们写入Firestore?" 不错的尝试,但 Storage 的元数据不包括下载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 是临时的:默认过期时间为1小时,最长可以是7天。
我要在这里发牢骚,getSignedUrl 的文档从未说过您的签名 URL 将在一周内过期。文档代码将3-17-2025
设置为过期日期,暗示您可以将过期时间设置在未来几年。我的应用程序运行得很完美,然后一个星期后就崩溃了。错误消息说签名不匹配,并没有说下载URL已过期。我对代码进行了各种变化,一切正常……直到一个星期后又崩溃了。这种状况已经持续了一个月的挫败感。 3-17-2025
日期是内部玩笑吗?就像消失时小精灵的金币一样,到了两个星期后,你以为你的代码没有 bug 了,圣帕特里克节未来几年到期的日期就消失了。
公共 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()。我无法使用此项技术,因为正确获取桶和文件路径比较棘手。
一个有趣的替代方案是使用Access Control Lists。您可以将文件仅提供给您放在列表中的用户,或使用authenticatedRead
将文件提供给从Google帐户登录的任何人。如果有一个选项“使用Firebase Auth登录我的应用程序的任何人”,我将使用它,因为它将仅限制对我的用户的访问。
已弃用:使用firebaseStorageDownloadTokens构建自己的下载URL
几个答案描述了一个未记录的Google Storage对象属性firebaseStorageDownloadTokens
。这从未是官方的Google Cloud Storage功能,它已经停止工作。以下是它以前是如何工作的。
您告诉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)); });
这不是打字错误-您必须将firebaseStorageDownloadTokens
嵌套在metadata:
的双层中!
您需要使用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,该SDK作为@google-cloud/storage的包装器。唯一的方法是:
- 使用特殊服务帐户初始化SDK,通常通过第二个非默认实例。
- 或者,通过为默认的App Engine服务帐户授予"signBlob"权限来在没有服务帐户的情况下进行操作。