从使用 Cloud Functions for Firebase 上传的文件获取下载 URL
Get Download URL from file uploaded with Cloud Functions for Firebase
在使用 Functions for Firebase 将文件上传到 Firebase Storage 后,我想下载 url 文件。
我有这个:
...
return bucket
.upload(fromFilePath, {destination: toFilePath})
.then((err, file) => {
// Get the download url of file
});
目标文件有很多参数。甚至一位名叫 mediaLink
。但是,如果我尝试访问此 link,我会收到此错误:
Anonymous users does not have storage.objects.get access to object ...
有人可以告诉我如何获得 public 下载 Url 吗?
谢谢
您需要使用 getSignedURL via the @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
});
您需要使用 your service account credentials 初始化 @google-cloud/storage
,因为应用程序默认凭据不够。
更新:现在可以通过 Firebase Admin SDK 访问 Cloud Storage SDK,acts as a wrapper @google-cloud/storage。唯一的方法是:
- 使用特殊服务帐户初始化 SDK,通常是通过第二个非默认实例。
- 或者,在没有服务帐户的情况下,为默认的 App Engine 服务帐户授予 "signBlob" 权限。
抱歉,由于缺少声誉,我无法 post 对您上面的问题发表评论,因此我将其包含在这个答案中。
通过生成带符号的 Url 来执行上述操作,但不是使用服务-account.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
的键,然后 assemble 下载 URL 我自己遵循 Firebase 用来生成这些 URL 的结构,例如:
https://firebasestorage.googleapis.com/v0/b/[BUCKET_NAME]/o/[FILE_PATH]?alt=media&token=[THE_TOKEN_YOU_CREATED]
我不知道有多少 "safe" 会使用这种方法(假设 Firebase 将来可能会改变它生成下载 URL 的方式)但它很容易实现。
对于那些想知道 Firebase Admin SDK serviceAccountKey.json 文件应该放在哪里的人。只需将它放在 functions 文件夹中并照常部署即可。
我仍然感到困惑,为什么我们不能像在 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 值将告诉 Cloud Storage 将其用作其 public 身份验证令牌。
非常感谢@martemorfosis
如果您只需要一个带有简单 URL 的 public 文件,则此方法有效。请注意,这可能会否决您的 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 前缀进行硬编码,尤其是当有替代方案时。我建议在使用 Cloud Storage NodeJS 1.6.x 或 +:
上传文件时使用选项 predefinedAcl: 'publicRead'
const options = {
destination: yourFileDestination,
predefinedAcl: 'publicRead'
};
bucket.upload(attachment, options);
然后,得到 public 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);
});
随着函数 object 响应的最新变化,您可以获得 "stitch" 所需的一切,一起下载 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);
我无法对 James Daniels 给出的答案发表评论,但我认为这非常重要,值得一读。
像他那样发出签名的 URL 在很多情况下看起来很 糟糕 并且可能 危险 。
根据 Firebase 的文档,已签名的 url 会在一段时间后过期,因此将其添加到您的数据库将导致在特定时间段
后为空 url
可能是误解了那里的文档,签名的 url 不会过期,因此会产生一些安全问题。
每个上传文件的密钥似乎都相同。这意味着一旦您获得了一个文件的 url,某人就可以轻松访问他不应该访问的文件,只需知道它们的名称即可。
如果我误解了这一点,那么我希望得到纠正。
其他人可能应该更新上面命名的解决方案。
如果我可能错了
对于使用 Firebase SDK 和admin.initializeApp
的用户:
1 - Generate a Private Key 并放置在 /functions 文件夹中。
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。此线程上的答案也没有帮助...
您可以通过执行以下操作来避免传递配置文件:
Go to your project's Cloud Console > IAM & admin > IAM, Find the App
Engine default service account and add the Service Account Token
Creator role to that member. This will allow your app to create signed
public URLs to the images.
来源:Automatically Generate Thumbnails function README
您在 App Engine 中的角色应如下所示:
从 firebase 6.0.0 开始,我可以像这样通过管理员直接访问存储:
const bucket = admin.storage().bucket();
所以我不需要添加服务帐户。然后如上所述设置 UUID 用于获取 firebase url.
如果您正在处理 Firebase 项目,则可以在 Cloud Functions 中创建签名的 URLs,而无需包含其他库或下载凭据文件。您只需启用 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')
然后你用
生成一个带符号的URL
myFile.getSignedUrl({action: 'read', expires: someDateObj}).then(urls => {
const signedUrl = urls[0]
})
确保您的 Firebase 服务帐户有足够的权限来运行这个
- 转到 Google API 控制台并启用 IAM API (https://console.developers.google.com/apis/api/iam.googleapis.com/overview)
- 仍在 API 控制台中,转到主菜单,"IAM & admin" -> "IAM"
- 单击 "App Engine default service account" 角色的编辑
- 单击 "Add another role",然后添加名为 "Service Account Token Creator"
- 保存并等待更改传播
使用 vanilla Firebase 配置,第一次 运行 上面的代码会出现错误 Identity and Access Management (IAM) API has not been used在项目 XXXXXX 之前或者它被禁用。。如果您按照错误消息中的 link 并启用 IAM API,您将收到另一个错误:Permission iam.serviceAccounts.signBlob is required to perform this operation on service account我的服务帐户。添加令牌创建者角色修复了第二个权限问题。
这是我目前使用的,简单而且完美。
您无需对 Google Cloud 执行任何操作。它与 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 的选项。一共有三种下载方式URLS:
- 已签名的下载 URL,它们是临时的并且具有安全功能
- 令牌下载 URLs,它们是持久的并且具有安全特性
- public 下载 URLs,持久且缺乏安全性
可通过三种方式获取令牌下载URL。其他两个下载 URL 只有一种获取方式。
来自 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 是永久性的,即它不会过期,但您可以撤销它。
getDownloadURL() 来自前端
documentation 告诉我们使用 getDownloadURL()
:
let url = await firebase.storage().ref('Audio/English/United_States-OED-' + i +'/' + $scope.word.word + ".mp3").getDownloadURL();
这与您从 Firebase 存储控制台获得的下载相同 URL。这种方法很简单,但需要您知道文件的路径,对于相对简单的数据库结构,在我的应用程序中大约有 300 行代码。如果您的数据库很复杂,这将是一场噩梦。您可以从前端上传文件,但这会将您的凭据暴露给下载您应用程序的任何人。因此,对于大多数项目,您需要从 Node 后端或 Google Cloud Functions 上传文件,然后下载 URL 并将其与文件的其他数据一起保存到数据库中。
getSignedUrl() 用于临时下载 URLs
getSignedUrl() 易于从节点后端或 Google 云函数使用:
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 是临时的:默认有效期为一小时,最长有效期为 7 天。
我要在这里咆哮 getSignedUrl 从来没有说过你签名的 URL 将在一周内过期。文档代码中有 3-17-2025
作为到期日期,建议您可以设置未来的到期年份。我的应用程序运行完美,但一周后崩溃了。错误消息说签名不匹配,而不是下载 URL 已过期。我对我的代码进行了各种更改,一切正常……直到一周后全部崩溃。这持续了一个多月的挫败感。
公开您的文件
您可以将文件的权限设置为 public 读取,如 documentation 中所述。这可以从云存储浏览器或您的节点服务器完成。您可以创建一个文件 public 或一个目录或整个存储数据库。这是节点代码:
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));
});
结果在您的云存储浏览器中将如下所示:
然后任何人都可以使用标准路径下载您的文件:
https://storage.googleapis.com/languagetwo-cd94d.appspot.com/Audio/English/United_States-OED-0/system.mp3
另一种制作文件的方法 public 是使用方法 makePublic()。我一直无法让它工作,正确设置存储桶和文件路径很棘手。
一个有趣的替代方法是使用 Access Control Lists。您可以使文件只对您列入列表的用户可用,或使用 authenticatedRead
使文件对从 Google 帐户登录的任何人可用。如果有一个选项 "anyone who logged into my app using Firebase Auth" 我会使用它,因为它会限制只有我的用户可以访问。
使用 firebaseStorageDownloadTokens 创建您自己的下载 URL
几个答案描述了一个未记录的 Google 存储对象 属性 firebaseStorageDownloadTokens
。有了它,您可以告诉 Storage 您要使用的令牌。您可以使用 uuid
节点模块生成令牌。四行代码,您可以构建自己的下载 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:
的双层中!
Doug Stevenson 指出 firebaseStorageDownloadTokens
不是正式的 Google 云存储功能。您不会在任何 Google 文档中找到它,并且不保证它会出现在 @google-cloud
的未来版本中。我喜欢 firebaseStorageDownloadTokens
因为这是获得我想要的东西的唯一方法,但它有一个 "smell" 使用起来不安全。
为什么没有来自 Node 的 getDownloadURL()?
正如@Clinton 所写,Google 应该在 @google-cloud/storage
中创建一个 file.getDownloadURL()
方法(即,您的 Node 后端)。我想从 Google Cloud Functions 上传文件并下载令牌 URL.
如果您遇到错误:
Google Cloud Functions: require(…) is not a function
试试这个:
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);
.....
没有 signedURL()
使用 makePublic()
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);
});
});
});
我已经 post 我的答案...在下面 URL 你可以在哪里获得完整的代码和解决方案
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://whosebug.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
对于那些尝试使用 token 参数共享文件并想使用 gsutil 命令的人,我是这样做的:
首先您需要通过 运行ning 进行身份验证:gcloud auth
然后 运行:
gsutil setmeta -h "x-goog-meta-firebaseStorageDownloadTokens:$FILE_TOKEN"
gs://$FIREBASE_REPO/$FILE_NAME
然后就可以下载文件了link:
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()
Async/Await
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"
。
请注意,为了使上述代码正常工作,您必须创建存储桶或文件 public。为此,请按照此处 https://cloud.google.com/storage/docs/access-control/making-data-public 的说明进行操作。另外,我直接导入了 @google-cloud/storage
包,而不是通过 Firebase SDK。
在使用 Functions for Firebase 将文件上传到 Firebase Storage 后,我想下载 url 文件。
我有这个:
...
return bucket
.upload(fromFilePath, {destination: toFilePath})
.then((err, file) => {
// Get the download url of file
});
目标文件有很多参数。甚至一位名叫 mediaLink
。但是,如果我尝试访问此 link,我会收到此错误:
Anonymous users does not have storage.objects.get access to object ...
有人可以告诉我如何获得 public 下载 Url 吗?
谢谢
您需要使用 getSignedURL via the @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
});
您需要使用 your service account credentials 初始化 @google-cloud/storage
,因为应用程序默认凭据不够。
更新:现在可以通过 Firebase Admin SDK 访问 Cloud Storage SDK,acts as a wrapper @google-cloud/storage。唯一的方法是:
- 使用特殊服务帐户初始化 SDK,通常是通过第二个非默认实例。
- 或者,在没有服务帐户的情况下,为默认的 App Engine 服务帐户授予 "signBlob" 权限。
抱歉,由于缺少声誉,我无法 post 对您上面的问题发表评论,因此我将其包含在这个答案中。
通过生成带符号的 Url 来执行上述操作,但不是使用服务-account.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
的键,然后 assemble 下载 URL 我自己遵循 Firebase 用来生成这些 URL 的结构,例如:
https://firebasestorage.googleapis.com/v0/b/[BUCKET_NAME]/o/[FILE_PATH]?alt=media&token=[THE_TOKEN_YOU_CREATED]
我不知道有多少 "safe" 会使用这种方法(假设 Firebase 将来可能会改变它生成下载 URL 的方式)但它很容易实现。
对于那些想知道 Firebase Admin SDK serviceAccountKey.json 文件应该放在哪里的人。只需将它放在 functions 文件夹中并照常部署即可。
我仍然感到困惑,为什么我们不能像在 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 值将告诉 Cloud Storage 将其用作其 public 身份验证令牌。
非常感谢@martemorfosis
如果您只需要一个带有简单 URL 的 public 文件,则此方法有效。请注意,这可能会否决您的 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 前缀进行硬编码,尤其是当有替代方案时。我建议在使用 Cloud Storage NodeJS 1.6.x 或 +:
上传文件时使用选项predefinedAcl: 'publicRead'
const options = {
destination: yourFileDestination,
predefinedAcl: 'publicRead'
};
bucket.upload(attachment, options);
然后,得到 public 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);
});
随着函数 object 响应的最新变化,您可以获得 "stitch" 所需的一切,一起下载 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);
我无法对 James Daniels 给出的答案发表评论,但我认为这非常重要,值得一读。
像他那样发出签名的 URL 在很多情况下看起来很 糟糕 并且可能 危险 。 根据 Firebase 的文档,已签名的 url 会在一段时间后过期,因此将其添加到您的数据库将导致在特定时间段
后为空 url可能是误解了那里的文档,签名的 url 不会过期,因此会产生一些安全问题。 每个上传文件的密钥似乎都相同。这意味着一旦您获得了一个文件的 url,某人就可以轻松访问他不应该访问的文件,只需知道它们的名称即可。
如果我误解了这一点,那么我希望得到纠正。 其他人可能应该更新上面命名的解决方案。 如果我可能错了
对于使用 Firebase SDK 和admin.initializeApp
的用户:
1 - Generate a Private Key 并放置在 /functions 文件夹中。
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。此线程上的答案也没有帮助...
您可以通过执行以下操作来避免传递配置文件:
Go to your project's Cloud Console > IAM & admin > IAM, Find the App Engine default service account and add the Service Account Token Creator role to that member. This will allow your app to create signed public URLs to the images.
来源:Automatically Generate Thumbnails function README
您在 App Engine 中的角色应如下所示:
从 firebase 6.0.0 开始,我可以像这样通过管理员直接访问存储:
const bucket = admin.storage().bucket();
所以我不需要添加服务帐户。然后如上所述设置 UUID 用于获取 firebase url.
如果您正在处理 Firebase 项目,则可以在 Cloud Functions 中创建签名的 URLs,而无需包含其他库或下载凭据文件。您只需启用 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')
然后你用
生成一个带符号的URLmyFile.getSignedUrl({action: 'read', expires: someDateObj}).then(urls => {
const signedUrl = urls[0]
})
确保您的 Firebase 服务帐户有足够的权限来运行这个
- 转到 Google API 控制台并启用 IAM API (https://console.developers.google.com/apis/api/iam.googleapis.com/overview)
- 仍在 API 控制台中,转到主菜单,"IAM & admin" -> "IAM"
- 单击 "App Engine default service account" 角色的编辑
- 单击 "Add another role",然后添加名为 "Service Account Token Creator"
- 保存并等待更改传播
使用 vanilla Firebase 配置,第一次 运行 上面的代码会出现错误 Identity and Access Management (IAM) API has not been used在项目 XXXXXX 之前或者它被禁用。。如果您按照错误消息中的 link 并启用 IAM API,您将收到另一个错误:Permission iam.serviceAccounts.signBlob is required to perform this operation on service account我的服务帐户。添加令牌创建者角色修复了第二个权限问题。
这是我目前使用的,简单而且完美。
您无需对 Google Cloud 执行任何操作。它与 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 的选项。一共有三种下载方式URLS:
- 已签名的下载 URL,它们是临时的并且具有安全功能
- 令牌下载 URLs,它们是持久的并且具有安全特性
- public 下载 URLs,持久且缺乏安全性
可通过三种方式获取令牌下载URL。其他两个下载 URL 只有一种获取方式。
来自 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 是永久性的,即它不会过期,但您可以撤销它。
getDownloadURL() 来自前端
documentation 告诉我们使用 getDownloadURL()
:
let url = await firebase.storage().ref('Audio/English/United_States-OED-' + i +'/' + $scope.word.word + ".mp3").getDownloadURL();
这与您从 Firebase 存储控制台获得的下载相同 URL。这种方法很简单,但需要您知道文件的路径,对于相对简单的数据库结构,在我的应用程序中大约有 300 行代码。如果您的数据库很复杂,这将是一场噩梦。您可以从前端上传文件,但这会将您的凭据暴露给下载您应用程序的任何人。因此,对于大多数项目,您需要从 Node 后端或 Google Cloud Functions 上传文件,然后下载 URL 并将其与文件的其他数据一起保存到数据库中。
getSignedUrl() 用于临时下载 URLs
getSignedUrl() 易于从节点后端或 Google 云函数使用:
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 是临时的:默认有效期为一小时,最长有效期为 7 天。
我要在这里咆哮 getSignedUrl 从来没有说过你签名的 URL 将在一周内过期。文档代码中有 3-17-2025
作为到期日期,建议您可以设置未来的到期年份。我的应用程序运行完美,但一周后崩溃了。错误消息说签名不匹配,而不是下载 URL 已过期。我对我的代码进行了各种更改,一切正常……直到一周后全部崩溃。这持续了一个多月的挫败感。
公开您的文件
您可以将文件的权限设置为 public 读取,如 documentation 中所述。这可以从云存储浏览器或您的节点服务器完成。您可以创建一个文件 public 或一个目录或整个存储数据库。这是节点代码:
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));
});
结果在您的云存储浏览器中将如下所示:
然后任何人都可以使用标准路径下载您的文件:
https://storage.googleapis.com/languagetwo-cd94d.appspot.com/Audio/English/United_States-OED-0/system.mp3
另一种制作文件的方法 public 是使用方法 makePublic()。我一直无法让它工作,正确设置存储桶和文件路径很棘手。
一个有趣的替代方法是使用 Access Control Lists。您可以使文件只对您列入列表的用户可用,或使用 authenticatedRead
使文件对从 Google 帐户登录的任何人可用。如果有一个选项 "anyone who logged into my app using Firebase Auth" 我会使用它,因为它会限制只有我的用户可以访问。
使用 firebaseStorageDownloadTokens 创建您自己的下载 URL
几个答案描述了一个未记录的 Google 存储对象 属性 firebaseStorageDownloadTokens
。有了它,您可以告诉 Storage 您要使用的令牌。您可以使用 uuid
节点模块生成令牌。四行代码,您可以构建自己的下载 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:
的双层中!
Doug Stevenson 指出 firebaseStorageDownloadTokens
不是正式的 Google 云存储功能。您不会在任何 Google 文档中找到它,并且不保证它会出现在 @google-cloud
的未来版本中。我喜欢 firebaseStorageDownloadTokens
因为这是获得我想要的东西的唯一方法,但它有一个 "smell" 使用起来不安全。
为什么没有来自 Node 的 getDownloadURL()?
正如@Clinton 所写,Google 应该在 @google-cloud/storage
中创建一个 file.getDownloadURL()
方法(即,您的 Node 后端)。我想从 Google Cloud Functions 上传文件并下载令牌 URL.
如果您遇到错误:
Google Cloud Functions: require(…) is not a function
试试这个:
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);
.....
没有 signedURL()
使用 makePublic()
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);
});
});
});
我已经 post 我的答案...在下面 URL 你可以在哪里获得完整的代码和解决方案
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://whosebug.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
对于那些尝试使用 token 参数共享文件并想使用 gsutil 命令的人,我是这样做的:
首先您需要通过 运行ning 进行身份验证:gcloud auth
然后 运行:
gsutil setmeta -h "x-goog-meta-firebaseStorageDownloadTokens:$FILE_TOKEN"
gs://$FIREBASE_REPO/$FILE_NAME
然后就可以下载文件了link:
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()
Async/Await
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"
。
请注意,为了使上述代码正常工作,您必须创建存储桶或文件 public。为此,请按照此处 https://cloud.google.com/storage/docs/access-control/making-data-public 的说明进行操作。另外,我直接导入了 @google-cloud/storage
包,而不是通过 Firebase SDK。