代码之家  ›  专栏  ›  技术社区  ›  CodyBugstein

在Firebase云函数中,如何获取刚刚上传到存储桶的内容的URL[[副本]

  •  0
  • CodyBugstein  · 技术社区  · 6 年前

    我有这个:

    ...
    
    return bucket
        .upload(fromFilePath, {destination: toFilePath})
        .then((err, file) => {
    
            // Get the download url of file
    
        });
    

    对象文件有很多参数。哪怕是一个叫 mediaLink . 但是,如果尝试访问此链接,则会出现以下错误:

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

    谢谢你

    0 回复  |  直到 7 年前
        1
  •  155
  •   Frank van Puffelen    6 年前

    您需要使用 getSignedURL 通过 @google-cloud/storage NPM模块。

    例子:

    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 具有 your service account credentials 由于应用程序的默认凭据不足。

    :现在可以通过Firebase Admin SDK访问云存储SDK acts as a wrapper

    1. 使用特殊服务帐户初始化SDK,通常是通过第二个非默认实例。
        2
  •  101
  •   Panagiotis Panagi sly7_7    7 年前

    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 嵌套在 元数据 firebaseStorageDownloadTokens 一个uuid-v4值将告诉云存储使用它作为它的公共身份验证令牌。

    非常感谢@martemorphosis

        3
  •  96
  •   Thomas David Kehoe    5 年前

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

    1. 令牌下载URL,这些URL是持久的并且具有安全特性
    2. 公共下载的网址,这是持久的,缺乏安全性

    有三种方法可以获得令牌下载URL。其他两个下载URL只有一种方法可以获得它们。

    从Firebase存储控制台

    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
    

    前端的getDownloadURL()

    这个 documentation 告诉我们使用 getDownloadURL() :

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

    用于临时下载URL的getSignedUrl()

    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

    我要大声说 getSignedUrl 千万不要说你的签名网址将在一周内过期。文档代码 3-17-2025

    公开您的文件

    您可以将文件的权限设置为public read,如中所述 documentation

    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
    

    另一种公开文件的方法是使用 makePublic()

    一个有趣的选择是使用 Access Control Lists . 您可以使文件仅对您放在列表中或使用的用户可用 authenticatedRead 使从Google帐户登录的任何人都可以使用该文件。如果有一个选项“任何人谁登录到我的应用程序使用Firebase认证”我会使用这个,因为它将限制访问只有我的用户。

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

    几个答案描述了一个未记录的Google存储对象属性 firebaseStorageDownloadTokens uuid

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

    道格史蒂文森指出 @google-cloud firebaseStorageDownloadTokens 因为这是得到我想要的东西的唯一途径,但它有一种“气味”,使用起来不安全。

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

    file.getDownloadURL() 一种方法 @google-cloud/storage (即,节点后端)。我想上传一个文件从谷歌云功能,并获得令牌下载网址。

        4
  •  31
  •   SMX    6 年前

    如果您正在处理Firebase项目,则可以在云函数中创建已签名的url,而无需包含其他库或下载凭据文件。您只需启用iamapi并向现有服务帐户添加一个角色(见下文)。

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

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

    1. https://console.developers.google.com/apis/api/iam.googleapis.com/overview )
    2. 仍然在API控制台中,转到主菜单“IAM&管理“->”“我是”
    3. 单击“应用程序引擎默认服务帐户”角色的编辑
    4. 单击“添加另一个角色”,添加名为“服务帐户令牌创建者”的角色

    使用vanilla Firebase配置,第一次运行上述代码时会出现错误 身份和访问管理(IAM)API以前未在项目XXXXXX中使用,或已被禁用。 需要具有iam.serviceAccounts.signBlob权限才能对服务帐户my service account执行此操作

        5
  •  22
  •   Demian S    7 年前

    随着最近功能的变化 对象 响应您可以获得所有需要的信息,将下载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);
    
        6
  •  22
  •   Laurent    4 年前

    您应该避免在代码中对URL前缀进行harcoding,特别是在有其他选择的情况下 . 我建议用这个选项 predefinedAcl: 'publicRead' 上载文件时使用 Cloud Storage NodeJS 1.6.x 或+:

    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);
    });
    
        7
  •  19
  •   martemorfosis    8 年前

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

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

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

        8
  •  16
  •   Clinton    8 年前

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

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

        9
  •  13
  •   Relm    4 年前

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

    你不需要对googlecloud做任何事情。它和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 = metatata[0].mediaLink
    

    #更新:

    let file = await bucket.upload(fromFilePath, {destination: toFilePath});
    const trimUrl = file[0].metatata.mediaLink
    
        10
  •  10
  •   Renji    7 年前

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

    像他那样发一个签名的网址在很多情况下看起来都很不错 坏的 危险的 根据Firebase的文档,签名的url将在一段时间后过期,因此将其添加到数据库中将导致在特定时间段后出现空url

    可能是误解了那里的文档,并且签名的url没有过期,这会导致一些安全问题。

    其他人可能应该更新上述解决方案。 如果我说错了

        11
  •  9
  •   NiVeK92    8 年前

    抱歉,我不能对你的问题发表评论,因为缺少声誉,所以我会把它包括在这个答案中。

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

    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 
            })
    
        12
  •  4
  •   inorganik    6 年前

    如果使用“publicRead”的预定义访问控制列表值,则可以上载文件并使用非常简单的url结构访问它:

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

    const storageRoot = 'https://storage.googleapis.com/';
    const bucketName = 'myapp.appspot.com/'; // CHANGE TO YOUR BUCKET NAME
    const downloadUrl = storageRoot + bucketName + encodeURIComponent(dest);
    
        13
  •  3
  •   TheFullResolution    6 年前

    我也有同样的问题,但是,我看的是firebase函数示例的代码,而不是自述文件。这个问题的答案也没用。。。

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

    转到项目的 Cloud Console > IAM & admin > IAM ,查找应用程序 引擎默认服务帐户并添加服务帐户令牌 创建者角色。这将允许你的应用程序创建签名

    资料来源: Automatically Generate Thumbnails function README

    应用程序引擎的角色应如下所示:

    Cloud Console

        14
  •  2
  •   Dakine    7 年前

    如果您只需要一个带有简单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);
        }
    });
    
        15
  •  2
  •   ersin-ertan    5 年前

    没有 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);
            });
        });
    });
    
        16
  •  2
  •   Jasdeep Singh    5 年前

    答复人 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
    
        17
  •  1
  •   Allan Poppe    7 年前

    对于使用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) {}
    

    Documentation

    admin.initializeApp(Object.assign(functions.config().firebase, { credential: admin.credential.cert(serviceAccount) })); .

        18
  •  1
  •   Stephen Rauch Afsar Ali    6 年前

    从firebase 6.0.0开始,我可以通过管理员直接访问存储,如下所示:

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

        19
  •  1
  •   Tibor Udvari    6 年前

    这是我想到的最好的。这是多余的,但对我来说唯一合理的解决办法。

    await bucket.upload(localFilePath, {destination: uploadPath, public: true});
    const f = await bucket.file(uploadPath)
    const meta = await f.getMetadata()
    console.log(meta[0].mediaLink)
    
        20
  •  0
  •   Rawan-25    5 年前

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

    How do I upload a base64 encoded image (string) directly to a Google Cloud Storage bucket using Node.js?

    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);
    
        21
  •  0
  •   quicklikerabbit    4 年前

    对于那些试图使用token参数共享文件并希望使用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

        22
  •  -1
  •   Wild Goat    5 年前

    试试这个:

    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);
    .....
    
        23
  •  -1
  •   Chukwuemeka Maduekwe    4 年前

    我在管理员存储文件上看到的

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