Подтвердить что ты не робот

Получить URL-адрес загрузки из файла, загруженного с помощью облачных функций для Firebase

После загрузки файла в Firebase Storage с функциями для Firebase я хотел бы получить URL-адрес загрузки файла.

У меня есть это:

...

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

        // Get the download url of file

    });

Объектный файл имеет много параметров. Даже один по имени mediaLink. Однако, если я пытаюсь получить доступ к этой ссылке, я получаю эту ошибку:

Анонимные пользователи не имеют доступа к объекту storage.objects.get...

Может кто-нибудь сказать мне, как получить общедоступный URL-адрес загрузки?

Спасибо

4b9b3361

Ответ 1

Вам нужно будет создать подписанный URL-адрес, используя getSignedURL через модуль NPM @google-cloud/storage.

Пример:

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

Вам нужно будет инициализировать @google-cloud/storage с помощью учетных данных учетной записи службы, так как учетных данных приложения по умолчанию будет недостаточно.

ОБНОВЛЕНИЕ: к Cloud Storage SDK теперь можно получить доступ через Firebase Admin SDK, который действует как обертка вокруг @google-cloud/storage. Единственный способ это сделать, если вы либо:

  1. Запустите SDK со специальной учетной записью службы, обычно через второй экземпляр, отличный от используемого по умолчанию.
  2. Или без учетной записи службы, предоставив учетной записи службы App Engine по умолчанию разрешение "signBlob".

Ответ 2

Вот пример того, как указать токен загрузки при загрузке:

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, чтобы использовать это как общедоступный токен аутентификации.

Большое спасибо @martemorfosis

Ответ 3

Если вы работаете над проектом 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')

Затем вы создаете подписанный URL с

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

Убедитесь, что ваша учетная запись службы Firebase имеет достаточные разрешения для запуска этого

  1. Перейдите в консоль API Google и включите API IAM (https://console.developers.google.com/apis/api/iam.googleapis.com/overview).
  2. В консоли API перейдите в главное меню "IAM & admin" → "IAM".
  3. Нажмите "Изменить" для роли "Учетная запись службы App Engine по умолчанию".
  4. Нажмите "Добавить другую роль" и добавьте роль "Создатель токена учетной записи службы".
  5. Сохраните и подождите минуту, чтобы изменения распространялись

С ванильной конфигурацией Firebase при первом запуске приведенного выше кода вы получите сообщение об ошибке API Identity and Access Management (IAM) ранее не использовался в проекте XXXXXX или он отключен. Если перейти по ссылке в сообщении об ошибке и включив IAM API, вы получите еще одну ошибку: для выполнения этой операции на учетной записи службы my-service-account требуется разрешение iam.serviceAccounts.signBlob. Добавление роли Token Creator устраняет эту вторую проблему с разрешениями.

Ответ 4

Благодаря последним изменениям в ответе объекта функций вы можете получить все, что вам нужно, чтобы "сшить" вместе 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);

Ответ 5

В этом ответе будут представлены варианты получения URL-адреса для загрузки при загрузке файла в Google/Firebase Cloud Storage. Существует три типа URL для загрузки:

  1. подписанные URL-адреса для загрузки, которые являются временными и имеют функции безопасности
  2. URL загрузки токенов, которые являются постоянными и имеют функции безопасности
  3. общедоступные URL-адреса для загрузки, которые являются постоянными и не имеют безопасности

Есть три способа получить URL для загрузки токена. У двух других URL-адресов для загрузки есть только один способ получить их.

Из консоли хранилища Firebase

Вы можете получить URL для загрузки из консоли Firebase Storage:

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

Он получает тот же URL-адрес для загрузки, который вы можете получить из консоли Firebase Storage. Этот метод прост, но требует, чтобы вы знали путь к вашему файлу, который в моем приложении составляет около 300 строк кода, для относительно простой структуры базы данных. Если ваша база данных сложна, это будет кошмар. И вы можете загружать файлы из внешнего интерфейса, но это предоставит ваши учетные данные любому, кто загружает ваше приложение. Таким образом, для большинства проектов вы захотите загрузить свои файлы с вашего внутреннего узла или облачных функций Google, затем получить URL-адрес для загрузки и сохранить его в своей базе данных вместе с другими данными о вашем файле.

getSignedUrl() для временных URL-адресов загрузки

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-адреса являются временными: срок действия по умолчанию составляет один час, а максимальный срок действия - семь дней.

Я расскажу здесь, что getSignedUrl никогда не говорит, что срок действия подписанного вами URL истекает через неделю. Код документации имеет 3-17-2025 в качестве даты истечения срока действия, предлагая вам установить годы истечения в будущем. Мое приложение работало отлично, а через неделю рухнуло. В сообщении об ошибке говорится, что подписи не совпадают, а URL-адрес загрузки истек. Я сделал различные изменения в своем коде, и все работало... пока все не рухнуло неделю спустя. Это продолжалось более месяца разочарования.

Сделайте ваш файл общедоступным

Вы можете установить права доступа к вашему файлу для публичного чтения, как описано в документации. Это можно сделать из браузера Cloud 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));
    });

Результат будет выглядеть в вашем браузере облачного хранилища:

enter image description here

Затем любой желающий может использовать стандартный путь для загрузки вашего файла:

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

Еще один способ сделать файл общедоступным - использовать метод makePublic(). Мне не удалось заставить это работать, сложно правильно определить пути к файлам и корзине.

Интересной альтернативой является использование списков контроля доступа. Вы можете сделать файл доступным только пользователям, которых вы включили в список, или использовать authenticatedRead, чтобы сделать файл доступным для всех, кто вошел в систему из учетной записи Google. Если бы была опция "любой, кто вошел в мое приложение с помощью Firebase Auth", я бы использовал это, так как это ограничило бы доступ только для моих пользователей.

Создайте свой собственный URL для загрузки с помощью firebaseStorageDownloadTokens

Несколько ответов описывают недокументированное свойство объекта Google Storage firebaseStorageDownloadTokens. При этом вы можете указать Storage токен, который хотите использовать. Вы можете сгенерировать токен с помощью узла узла uuid. Четыре строки кода, и вы можете создать свой собственный 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:!

Дуг Стивенсон отметил, что firebaseStorageDownloadTokens не является официальной функцией Google Cloud Storage. Вы не найдете его ни в одной документации Google, и нет никаких обещаний, что это будет в будущей версии @google-cloud. Мне нравится firebaseStorageDownloadTokens, потому что это единственный способ получить то, что я хочу, но у него есть "запах", который его небезопасно использовать.

Почему нет getDownloadURL() от узла?

Как писал @Clinton, Google должен создать метод file.getDownloadURL() в @google-cloud/storage (т.е. в вашем внутреннем узле). Я хочу загрузить файл из Google Cloud Functions и получить URL-адрес для загрузки токена.

Ответ 6

Я предлагаю использовать опцию 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

Один из методов, которые я использую с успехом, - установить значение UUID v4 в ключе с именем firebaseStorageDownloadTokens в метаданных файла после завершения загрузки, а затем собрать URL-адрес загрузки, следуя структуре Firebase, используемой для создания этих URL, например:

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

Я не знаю, насколько "безопасно" использовать этот метод (учитывая, что Firebase может изменить способ генерации URL-адресов загрузки в будущем), но его легко реализовать.

Ответ 8

Для тех, кто задается вопросом, куда должен идти файл службы Firebase Admin SDK serviceAccountKey.json. Просто поместите его в папку функций и разверните, как обычно.

Это все еще озадачивает меня, почему мы не можем просто получить URL-адрес загрузки из метаданных, как в Javascript SDK. Создание URL-адреса, которое в конечном итоге истечет и сохранить его в базе данных, нежелательно.

Ответ 9

Извините, но я не могу опубликовать комментарий к вашему вопросу выше из-за отсутствия репутации, поэтому я включу его в этот ответ.

Сделайте, как указано выше, создав подписанный Url, но вместо использования service-account.json, я думаю, вам нужно использовать 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 
        })

Ответ 10

Я не могу комментировать ответ, который дал Джеймс Дэниелс, но я думаю, что это очень важно прочитать.

Выдача подписанного URL-адреса Как он это сделал, во многих случаях кажется довольно плохим и, возможно, опасным. Согласно документации Firebase, срок действия подписанного URL истекает через некоторое время, поэтому добавление его в базу данных приведет к появлению пустого URL через определенный период времени.

Возможно, что там неправильно истолкована документация, а срок действия подписанного URL не истекает, что может привести к проблемам с безопасностью. Ключ кажется одинаковым для каждого загруженного файла. Это означает, что, получив URL-адрес одного файла, кто-то может легко получить доступ к файлам, к которым он не имеет доступа, просто зная их имена.

Если бы я не понял этого, я бы хотел исправить. В противном случае кто-то должен, вероятно, обновить вышеуказанное решение. Если я могу ошибаться там

Ответ 11

Это то, что я сейчас использую, это просто и работает без нареканий.

Вам не нужно ничего делать с 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

Ответ 12

Это работает, если вам нужен только общий файл с простым 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);
    }
});

Ответ 13

У меня была та же проблема, однако я смотрел на код примера функции firebase вместо README. И ответы в этой теме тоже не помогли...

Вы можете избежать передачи файла конфигурации, выполнив следующие действия:

Перейдите в ваш проект Cloud Console> IAM & admin> IAM, найдите учетную запись службы App Engine по умолчанию и добавьте к этому члену роль создателя маркера служебной учетной записи. Это позволит вашему приложению создавать подписанные общедоступные URL-адреса для изображений.

источник: функция автоматического создания миниатюр README

Ваша роль для движка приложения должна выглядеть следующим образом:

Cloud Console

Ответ 14

Для тех, кто использует Firebase SDK и admin.initializeApp:

1 - Сгенерируйте закрытый ключ и поместите в папку /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) })); ,

Ответ 15

Начиная с Firebase 6.0.0 я мог получить доступ к хранилищу напрямую с помощью администратора:

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

Поэтому мне не нужно было добавлять служебную учетную запись. Затем установка UUID, как указано выше, сработала для получения URL-адреса firebase.

Ответ 16

Если вы используете предопределенное значение списков управления доступом 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);

Ответ 17

Это лучшее, что я придумал. Это избыточно, но единственное разумное решение, которое сработало для меня.

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

Ответ 18

Я ЧИТАЮ... И Я ПРОСТО ИСПОЛЬЗУЮ ПРИМЕР ИЗ GOOGLE ЭТО ПРЯМО СЕЙЧАС ЕГО РАБОТАЕТ ИДЕАЛЬНО - Я ДУМАЮ, ЧТО ВСЕ ЖДУ НЕДЕЛЮ, КАК ОНИ СКАЗАЛ, ЧТО ЭТО ОЖИДАЕТ... ЧТОБЫ ПРОВЕРИТЬ, ЧТО ПРОИСХОДИТ - КОД ПРАВИЛЬНО ЗДЕСЬ: https://github.com/firebase/functions-samples/tree/master/generate-thumbnail

Ответ 19

Если вы получаете сообщение об ошибке:

Облачные функции Google: require (…) не является функцией

попробуйте это:

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

Ответ 20

Без 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);
        });
    });
});