获取通过 Firebase 的云函数上传的文件下载 URL

在 Firebase Storage 中上传了一个带有 Firebase 函数的文件后,我想获得该文件的下载 URL。

我有这个:

...


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


// Get the download url of file


});

目标文件有很多参数。甚至还有一个叫 mediaLink的。但是,如果我尝试访问这个链接,就会得到这个错误:

匿名用户没有 storage.objects.get 访问 object..。

谁能告诉我如何让公众下载 Url?

谢谢你

126339 次浏览

您需要通过 @ google-cloud/Storage NPM 模块使用 GetSignedURL生成一个签名 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,因为应用程序默认凭据不足以满足需要。

更新 : 云存储 SDK 现在可以通过 Firebase Admin SDK 访问,该 SDK 围绕@google 云/存储进行 起包装作用访问。唯一的解决办法就是:

  1. 使用一个特殊的服务帐户初始化 SDK,通常通过第二个非默认实例。
  2. 或者,在没有服务帐户的情况下,通过向默认 AppEngine 服务帐户授予“ signBlob”权限。

对不起,我不能发表一个评论你的问题以上,因为缺少声誉,所以我会包括在这个答案。

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

Https://console.firebase.google.com/project/yourprojectid/settings/serviceaccounts/adminsdk

例如:

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
})

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

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

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

对于那些想知道 Firebase Admin SDK serviceAccountKey.json 文件应该放在哪里的人来说。只需将其放在 function 文件夹中,然后像往常一样进行部署。

我仍然不明白为什么我们不能像在 Javascript SDK 中那样从元数据中获取下载 URL。生成最终会过期的 URL 并将其保存在数据库中是不可取的。

下面是一个关于如何在上传时指定下载令牌的示例:

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);
});
}

然后打电话给

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

这里的关键是,在 metadata选项属性中嵌套了一个 metadata对象。将 firebaseStorageDownloadTokens设置为 uuid-v4值将告诉云存储使用该值作为其公共认证令牌。

非常感谢@martemorfosis

如果您只需要一个带有简单 URL 的公共文件,那么可以使用这种方法。请注意,这可能会推翻 Firebase 存储规则。

bucket.upload(file, function(err, file) {
if (!err) {
//Make the file public
file.acl.add({
entity: 'allUsers',
role: gcs.acl.READER_ROLE
}, function(err, aclObject) {
if (!err) {
var URL = "https://storage.googleapis.com/[your bucket name]/" + file.id;
console.log(URL);
} else {
console.log("Failed to set permissions: " + err);
}
});
} else {
console.log("Upload failed: " + err);
}
});

您应该避免在代码中硬编码 URL 前缀,特别是当存在 选项时。我建议在上传带有 云存储 NodeJS1.6. x或 + 的文件时使用选项 predefinedAcl: 'publicRead':

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


bucket.upload(attachment, options);

然后,获取公共 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);
});

随着功能 对象最近的变化响应,你可以得到一切你需要“缝合”在一起的下载网址如下:

 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);

我不能评论詹姆斯 · 丹尼尔斯给出的答案,但我认为这是非常重要的阅读。

给出一个签名的网址,就像他做的似乎很多情况下相当 很糟糕和可能的 危险。 根据 Firebase 的文档,签名的 URL 在一段时间后过期,因此添加到您的数据库将导致一个空的 URL 在一定的时间框架之后

这可能是因为误解了这里的文档,并且签名的 URL 不会过期,这会导致一些安全问题。 密钥似乎对于每个上传的文件都是相同的。这意味着一旦你得到了一个文件的网址,某人可以轻松地访问他不应该访问的文件,只要知道他们的名字。

如果我理解错了,那么我希望得到纠正。 其他人可能应该更新上述命名的解决方案。 如果我错了的话

对于那些使用 Firebase SDK 和 admin.initializeApp的用户:

1-生成私钥并放置在/function 文件夹中。

2-按以下方式配置代码:

const serviceAccount = require('../../serviceAccountKey.json');
try { admin.initializeApp(Object.assign(functions.config().firebase, { credential: admin.credential.cert(serviceAccount) })); } catch (e) {}

文件

Try/catch 是因为我使用了一个 index.js,它导入其他文件并为每个文件创建一个函数。如果所有函数都使用一个 index.js 文件,那么使用 admin.initializeApp(Object.assign(functions.config().firebase, { credential: admin.credential.cert(serviceAccount) }));应该没有问题。

我也有同样的问题,但是,我看的是 firebase 函数示例的代码,而不是 README。这个帖子上的答案也没什么帮助。

通过执行以下操作可以避免传递配置文件:

转到你的项目的 云端控制台 > IAM & admin > IAM,找到应用程序 引擎默认服务帐户并添加服务帐户令牌 创建者角色。这将允许您的应用程序创建已签名的 图像的公共 URL。

来源: 自动生成缩略图函数

应用程序引擎的角色应该是这样的:

Cloud Console

在 firebase 6.0.0时,我可以通过管理员像下面这样直接访问存储:

const bucket = admin.storage().bucket();

所以我不需要添加服务账户。然后按照上面引用的方式设置 UUID 可以获得 firebase URL。

如果您正在从事一个 Firebase 项目,您可以在一个云函数中创建签名 URL,而不需要包含其他库或下载凭证文件。您只需要启用 IAM API 并向现有服务帐户添加一个角色(见下文)。

初始化管理库并获得一个正常情况下的文件引用:

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')

然后使用

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

确保 Firebase 服务帐户有足够的权限运行此

  1. 转到 GoogleAPI 控制台并启用 IAMAPI (https://console.developers.google.com/apis/api/iam.googleapis.com/overview)
  2. 仍然在 API 控制台中,转到主菜单“ IAM & admin”-> “ IAM”
  3. 单击“ AppEngine 默认服务帐户”角色的编辑
  4. 单击“添加另一个角色”,然后添加名为“ ServiceAccount 令牌创建器”的角色
  5. 保存并等待一分钟,以便更改传播

使用普通的 Firebase 配置,第一次运行上面的代码时会得到一个错误 身份和访问管理(IAM) API 在项目 XXXXXX 之前没有使用过,或者已经禁用。。如果按照错误消息中的链接并启用 IAM API,则会得到另一个错误: 要在服务帐户 my-service-account 上执行此操作,需要 Permission iam.serviceAccounts.signBlob。添加 Token Creator 角色修复了第二个权限问题。

这是我目前使用的,它很简单,它的工作完美无缺。

你不需要对谷歌云做任何事情,它可以在 Firebase 中开箱即用。

// Save the base64 to storage.
const file = admin.storage().bucket('url found on the storage part of firebase').file(`profile_photos/${uid}`);
await file.save(base64Image, {
metadata: {
contentType: 'image/jpeg',
},
predefinedAcl: 'publicRead'
});
const metaData = await file.getMetadata()
const url = metaData[0].mediaLink

编辑: 同样的例子,但是上传:

await bucket.upload(fromFilePath, {destination: toFilePath});
file = bucket.file(toFilePath);
metaData = await file.getMetadata()
const trimUrl = metaData[0].mediaLink

更新: 不需要在上传方法中执行两个不同的调用来获取元数据:

let file = await bucket.upload(fromFilePath, {destination: toFilePath});
const trimUrl = file[0].metaData.mediaLink

如果您使用预定义的访问控制列表值‘ publicRead’,您可以上传文件并使用一个非常简单的 url 结构访问它:

// Upload to GCS
const opts: UploadOptions = {
gzip: true,
destination: dest, // 'someFolder/image.jpg'
predefinedAcl: 'publicRead',
public: true
};
return bucket.upload(imagePath, opts);

然后您可以像这样构造 url:

const storageRoot = 'https://storage.googleapis.com/';
const bucketName = 'myapp.appspot.com/'; // CHANGE TO YOUR BUCKET NAME
const downloadUrl = storageRoot + bucketName + encodeURIComponent(dest);

这是我想到的最好的解决方案,虽然有点多余,但是对我来说是唯一合理的解决方案。

await bucket.upload(localFilePath, {destination: uploadPath, public: true});
const f = await bucket.file(uploadPath)
const meta = await f.getMetadata()
console.log(meta[0].mediaLink)

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

  1. 签名的下载 URL,这是临时的,具有安全特性
  2. 令牌下载 URL,具有持久性和安全特性
  3. 公共下载 URL,持久性和缺乏安全性

有三种获取令牌下载 URL 的方法。另外两个下载 URL 只有一种获取它们的方法。

从 Firebase 存储控制台

您可以从 Firebase 存储控制台获得下载 URL:

enter image description here

下载 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 是永久的,也就是说,它不会过期,尽管您可以撤销它。

来自前端的 getDownloadURL ()

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

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

这将获得与您可以从 FirebaseStorage 控制台获得的相同的下载 URL。这个方法很简单,但是对于一个相对简单的数据库结构,需要知道文件的路径,在我的应用程序中大约有300行代码。如果您的数据库很复杂,这将是一场噩梦。你可以从前端上传文件,但是这会让任何下载你的应用程序的人看到你的证书。因此,对于大多数项目来说,你需要从你的 Node 后端或者 Google Cloud 函数上传你的文件,然后获得下载 URL 并将它和其他关于你文件的数据一起保存到你的数据库中。

用于临时下载 URL 的 getSignedUrl ()

GetSignedUrl () 在 Node 后端或 Google Cloud 函数中很容易使用:

  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 是临时的: 默认过期时间为一小时,最长过期时间为七天。

我要在这里抱怨,获取 SignedUrl从来没有说过,你签署的网址将在一个星期内过期。文档代码将 3-17-2025作为过期日期,这表明您可以在将来设置过期年份。我的应用程序运行得很好,一周后就崩溃了。错误消息说签名不匹配,并不是说下载的 URL 已经过期。我对代码做了很多修改,一切都正常,直到一周后崩溃。这种挫折持续了一个多月。

使您的文件公开可用

您可以将文件的权限设置为公共读取,如 文件中所解释的。这可以通过云存储浏览器或节点服务器完成。您可以公开一个文件、一个目录或整个 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));
});

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

enter image description here

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

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

使文件公开的另一种方法是使用方法 Make Public ()。我还没能让这个工作,这是棘手的桶和文件路径正确。

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

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

有几个答案描述了未记录的 GoogleStorage 对象属性 firebaseStorageDownloadTokens。通过这种方式,您可以告诉 Storage 要使用的令牌。您可以使用 uuid Node 模块生成一个令牌。四行代码,您可以建立自己的下载 URL,相同的下载 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);

下面是上下文中的代码:

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:中!

Doug Stevenson 指出,firebaseStorageDownloadTokens并不是 Google 云存储的官方特性。你不会在任何 Google 文档中找到它,也不能保证它会出现在 @google-cloud的未来版本中。我喜欢 firebaseStorageDownloadTokens,因为它是唯一能得到我想要的东西的方法,但是它有一种“气味”,使用它并不安全。

为什么 Node 没有 getDownloadURL () ?

正如@Clinton 所写的,Google 应该在 @google-cloud/storage中将 file.getDownloadURL()作为一个方法(也就是说,你的 Node 后端)。我想上传一个文件从谷歌云功能,并得到令牌下载的网址。

如果出现错误:

谷歌云功能: request (...)不是一个函数

试试这个:

const {Storage} = require('@google-cloud/storage');
const storage = new Storage({keyFilename: 'service-account-key.json'});
const bucket = storage.bucket(object.bucket);
const file = bucket.file(filePath);
.....

没有使用 makePublic()signedURL()

const functions = require('firebase-functions');
const admin = require('firebase-admin');


admin.initializeApp()
var bucket = admin.storage().bucket();


// --- [Above] for admin related operations, [Below] for making a public url from a GCS uploaded object


const { Storage } = require('@google-cloud/storage');
const storage = new Storage();


exports.testDlUrl = functions.storage.object().onFinalize(async (objMetadata) => {
console.log('bucket, file', objMetadata.bucket + ' ' + objMetadata.name.split('/').pop()); // assuming file is in folder
return storage.bucket(objMetadata.bucket).file(objMetadata.name).makePublic().then(function (data) {
return admin.firestore().collection('publicUrl').doc().set({ publicUrl: 'https://storage.googleapis.com/' + objMetadata.bucket + '/' + objMetadata.name }).then(writeResult => {
return console.log('publicUrl', writeResult);
});
});
});

我已经张贴我的答案... 在下面的网址,你可以得到完整的解决方案代码

如何使用 Node.js 将 base64编码的图像(字符串)直接上传到 Google Cloud Storage bucket?

const uuidv4 = require('uuid/v4');
const uuid = uuidv4();


const os = require('os')
const path = require('path')
const cors = require('cors')({ origin: true })
const Busboy = require('busboy')
const fs = require('fs')
var admin = require("firebase-admin");




var serviceAccount = {
"type": "service_account",
"project_id": "xxxxxx",
"private_key_id": "xxxxxx",
"private_key": "-----BEGIN PRIVATE KEY-----\jr5x+4AvctKLonBafg\nElTg3Cj7pAEbUfIO9I44zZ8=\n-----END PRIVATE KEY-----\n",
"client_email": "xxxx@xxxx.iam.gserviceaccount.com",
"client_id": "xxxxxxxx",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-5rmdm%40xxxxx.iam.gserviceaccount.com"
}


admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
storageBucket: "xxxxx-xxxx" // use your storage bucket name
});




const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.post('/uploadFile', (req, response) => {
response.set('Access-Control-Allow-Origin', '*');
const busboy = new Busboy({ headers: req.headers })
let uploadData = null
busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
const filepath = path.join(os.tmpdir(), filename)
uploadData = { file: filepath, type: mimetype }
console.log("-------------->>",filepath)
file.pipe(fs.createWriteStream(filepath))
})


busboy.on('finish', () => {
const bucket = admin.storage().bucket();
bucket.upload(uploadData.file, {
uploadType: 'media',
metadata: {
metadata: { firebaseStorageDownloadTokens: uuid,
contentType: uploadData.type,
},
},
})


.catch(err => {
res.status(500).json({
error: err,
})
})
})
busboy.end(req.rawBody)
});








exports.widgets = functions.https.onRequest(app);

https://stackoverflow.com/users/269447/laurent回答最好

const uploadOptions: UploadOptions = {
public: true
};


const bucket = admin.storage().bucket();
[ffile] = await bucket.upload(oPath, uploadOptions);
ffile.metadata.mediaLink // this is what you need

对于那些试图使用令牌参数来共享文件并希望使用 gsutil 命令的用户,以下是我是如何做到的:

首先,您需要通过运行 gcloud auth进行身份验证

然后跑:

gsutil setmeta -h "x-goog-meta-firebaseStorageDownloadTokens:$FILE_TOKEN" gs://$FIREBASE_REPO/$FILE_NAME

你可透过以下连结下载该文件:

Https://firebasestorage.googleapis.com/v0/b/$firebase_repo/o/$file_name?alt=media&token=$file_token

我在管理存储文档上看到了这个

const options = {
version: 'v4',
action: 'read',
expires: Date.now() + 15 * 60 * 1000, // 15 minutes
};


// Get a v4 signed URL for reading the file
const [url] = await storage
.bucket(bucketName)
.file(filename)
.getSignedUrl(options);


console.log('Generated GET signed URL:');
console.log(url);
console.log('You can use this URL with any user agent, for example:');
console.log(`curl '${url}'`);

使用 file.publicUrl()

异步/等待

const bucket = storage.bucket('bucket-name');
const uploadResponse = await bucket.upload('image-name.jpg');
const downloadUrl = uploadResponse[0].publicUrl();

复试

const bucket = storage.bucket('bucket-name');
bucket.upload('image-name.jpg', (err, file) => {
if(!file) {
throw err;
}


const downloadUrl = file.publicUrl();
})

downloadUrl将是 "https://storage.googleapis.com/bucket-name/image-name.jpg"

请注意,为了让上面的代码工作,您必须将 bucket 或文件公开。要这样做,请按照这里的说明 https://cloud.google.com/storage/docs/access-control/making-data-public。另外,我没有直接通过 FirebaseSDK 导入 @google-cloud/storage包。