รับ URL ดาวน์โหลดจากไฟล์ที่อัปโหลดด้วย Cloud Functions สำหรับ Firebase


125

หลังจากอัปโหลดไฟล์ใน Firebase Storage พร้อมฟังก์ชั่นสำหรับ Firebase ฉันต้องการรับ URL ดาวน์โหลดของไฟล์

ฉันมีสิ่งนี้:

...

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

        // Get the download url of file

    });

อ็อบเจ็กต์ไฟล์มีพารามิเตอร์จำนวนมาก แม้แต่ชื่อเดียวmediaLink. อย่างไรก็ตามหากฉันพยายามเข้าถึงลิงค์นี้ฉันได้รับข้อผิดพลาดนี้:

ผู้ใช้ที่ไม่ระบุชื่อไม่มี storage.objects. เข้าถึงวัตถุ ...

ใครช่วยบอกวิธีรับ URL ดาวน์โหลดสาธารณะได้ไหม

ขอบคุณ


ดูโพสต์นี้ซึ่งสร้าง URL ขึ้นใหม่จากข้อมูลที่มีอยู่ในฟังก์ชัน
Kato

คำตอบ:


133

คุณจะต้องสร้าง URL ที่ลงนามโดยใช้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ด้วยข้อมูลรับรองบัญชีบริการของคุณเนื่องจากข้อมูลรับรองเริ่มต้นของแอปพลิเคชันจะไม่เพียงพอ

อัปเดต : ขณะนี้ Cloud Storage SDK สามารถเข้าถึงได้ผ่าน Firebase Admin SDK ซึ่งทำหน้าที่เป็น Wrapperรอบ ๆ @ google-cloud / storage วิธีเดียวที่จะทำได้คือถ้าคุณ:

  1. เริ่มต้น SDK ด้วยบัญชีบริการพิเศษโดยปกติจะใช้อินสแตนซ์ที่ไม่ใช่ค่าเริ่มต้นเป็นวินาที
  2. หรือไม่มีบัญชีบริการโดยให้สิทธิ์ "signBlob" กับบัญชีบริการ App Engine เริ่มต้น

75
มันแปลก ๆ. เราสามารถรับ URL การดาวน์โหลดได้อย่างง่ายดายจากข้อมูลอ้างอิงที่เก็บข้อมูลเมื่อใช้ Firebase Android, iOS และ Web SDK ทำไมตอนเป็นแอดมินถึงไม่ง่าย PS: ฉันจะค้นหา 'service-account.json' ที่จำเป็นในการเริ่มต้น gcs ได้ที่ไหน
Valentin

2
เนื่องจาก admin-sdk ไม่มีส่วนเพิ่มเติม Cloud Storage คุณสามารถรับบัญชีบริการ admin-sdk json ได้ที่นี่console.firebase.google.com/project/_/settings/serviceaccounts/…
James Daniels

18
URL ที่สร้างด้วยวิธีนี้มีความยาวมาก URL ที่สร้างโดยวิธีการเสนอของ @martemorfosis นั้นดีกว่ามาก มีฟังก์ชันใดบ้างที่สร้าง URL นั้น? นั่นคือสิ่งที่ฉันบันทึกไว้ในฐานข้อมูลเพื่อใช้ในอนาคตเมื่อฉันใช้ firebase-sdk ต้องมีเมธอดมิเรอร์ในโดเมน Functions
Bogac

3
เราจะอัปโหลด service-account.json พร้อมกับฟังก์ชันที่ใช้งานได้อย่างไร? ฉันได้ลองวางไว้ในโฟลเดอร์ฟังก์ชั่นและอ้างถึงในช่องไฟล์ใน package.json แต่ไม่ได้รับการปรับใช้ ขอบคุณ.
David Aroesti

2
เราจำเป็นต้องเพิ่มactionและexpires?
Chad Bingham

83

ต่อไปนี้เป็นตัวอย่างวิธีระบุโทเค็นดาวน์โหลดเมื่ออัปโหลด:

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คุณสมบัติตัวเลือก การตั้งค่าเป็นค่าfirebaseStorageDownloadTokensuuid-v4 จะบอกให้ Cloud Storage ใช้เป็นโทเค็นการตรวจสอบสิทธิ์สาธารณะ

ขอบคุณมากสำหรับ @martemorfosis


ฉันจะรับโทเค็น UUID ที่ถูกต้องสำหรับไฟล์ที่อัปโหลดบน Storage แล้วได้อย่างไร การสร้าง UUID แบบสุ่มไม่ได้ช่วย คำแนะนำใด ๆ
DerFaizio

3
พบคำตอบในโพสต์ @martemorfosis UUID สามารถดึงได้จาก object.metadata exports.uploadProfilePic = functions.storage.object (). onChange (event => {const object = event.data; // The Storage object. const uuid = object.metadata.firebaseStorageDownloadTokens; // ...
DerFaizio

ขอบคุณสำหรับตัวอย่างถัง! ฉันกำลังลองชุดที่แตกต่างกันสำหรับวิธีการถังและไฟล์เป็นเวลาเกือบ 1 ชั่วโมง :)
JCarlosR

1
ขอบคุณสำหรับคำตอบ! ในกรณีของฉันฉันกำลังอัปโหลดด้วย bucket.file (fileName) .createWriteStream ซึ่งจะไม่ส่งคืนข้อมูลเมื่ออัปโหลดเสร็จสิ้นด้วยเหตุนี้ฉันจึงใช้ encodeURIComponent (fileName) แทน encodeURIComponent (file.name)
Stanislau Buzunko

2
นี่น่าจะเป็นคำตอบที่ใช่ ผลลัพธ์ใน URL ที่คล้ายกับ URL ที่สร้างโดย Firebase SDKs (@DevMike) และฉันพนันได้เลยว่าพวกเขาทำอะไรอยู่เบื้องหลัง
Samuel E.

66

คำตอบนี้จะสรุปตัวเลือกในการรับ URL ดาวน์โหลดเมื่ออัปโหลดไฟล์ไปยัง Google / Firebase Cloud Storage URL ดาวน์โหลดมีสามประเภท:

  1. URL ดาวน์โหลดที่ลงนามซึ่งเป็นแบบชั่วคราวและมีคุณสมบัติด้านความปลอดภัย
  2. URL ดาวน์โหลดโทเค็นซึ่งคงอยู่และมีคุณสมบัติด้านความปลอดภัย
  3. URL ดาวน์โหลดสาธารณะซึ่งคงอยู่และขาดความปลอดภัย

มีสามวิธีในการรับ URL ดาวน์โหลดโทเค็น URL ดาวน์โหลดอีกสองรายการมีทางเดียวเท่านั้นที่จะได้รับ

จาก Firebase Storage Console

คุณสามารถรับ URL ดาวน์โหลดจากคอนโซล Firebase Storage:

ใส่คำอธิบายภาพที่นี่

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 Cloud Functions จากนั้นรับ URL ดาวน์โหลดและบันทึกลงในฐานข้อมูลของคุณพร้อมกับข้อมูลอื่น ๆ เกี่ยวกับไฟล์ของคุณ

getSignedUrl () สำหรับ URL ดาวน์โหลดชั่วคราว

getSignedUrl ()ใช้งานง่ายจาก Node back end หรือ Google Cloud Functions:

  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 Browser หรือจากเซิร์ฟเวอร์ Node ของคุณ คุณสามารถทำให้หนึ่งไฟล์เป็นสาธารณะหรือไดเร็กทอรีหรือฐานข้อมูล 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));
    });

ผลลัพธ์จะมีลักษณะเช่นนี้ในเบราว์เซอร์ Cloud Storage ของคุณ:

ใส่คำอธิบายภาพที่นี่

จากนั้นทุกคนสามารถใช้เส้นทางมาตรฐานเพื่อดาวน์โหลดไฟล์ของคุณ:

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

วิธีที่จะทำให้ประชาชนไฟล์หนึ่งคือการใช้วิธีการmakePublic () ฉันไม่สามารถทำให้มันใช้งานได้การทำให้ที่เก็บข้อมูลและเส้นทางไฟล์ถูกต้องเป็นเรื่องยาก

เป็นทางเลือกที่น่าสนใจคือการใช้รายการควบคุมการเข้าถึง คุณสามารถทำให้ไฟล์ใช้ได้เฉพาะกับผู้ใช้ที่คุณใส่ไว้ในรายการหรือใช้authenticatedReadเพื่อทำให้ไฟล์นั้นพร้อมใช้งานสำหรับทุกคนที่ลงชื่อเข้าใช้จากบัญชี Google หากมีตัวเลือก "ใครก็ตามที่ลงชื่อเข้าใช้แอปของฉันโดยใช้ Firebase Auth" ฉันจะใช้ตัวเลือกนี้เพราะจะ จำกัด การเข้าถึงเฉพาะผู้ใช้ของฉันเท่านั้น

สร้าง URL ดาวน์โหลดของคุณเองด้วย firebaseStorageDownloadTokens

firebaseStorageDownloadTokensหลายคำตอบที่ไม่มีเอกสารอธิบายคุณสมบัติวัตถุพื้นที่เก็บข้อมูล Google ด้วยวิธีนี้คุณสามารถบอกที่เก็บข้อมูลโทเค็นที่คุณต้องการใช้ คุณสามารถสร้างโทเค็นด้วย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:!

Doug Stevenson ชี้ให้เห็นว่าfirebaseStorageDownloadTokensนี่ไม่ใช่ฟีเจอร์ Google Cloud Storage อย่างเป็นทางการ คุณจะพบว่ามันไม่อยู่ในเอกสารใด ๆ ของ Google @google-cloudและมีสัญญาก็จะอยู่ในรุ่นในอนาคตไม่มี ฉันชอบfirebaseStorageDownloadTokensเพราะมันเป็นวิธีเดียวที่จะได้สิ่งที่ฉันต้องการ แต่มันมี "กลิ่น" ที่ไม่ปลอดภัยที่จะใช้

ทำไมไม่มี getDownloadURL () จาก Node

ตามที่ @Clinton เขียนไว้ Google ควรสร้างfile.getDownloadURL()วิธีการใน@google-cloud/storage(เช่น Node back end) ฉันต้องการอัปโหลดไฟล์จาก Google Cloud Functions และรับ URL ดาวน์โหลดโทเค็น


11
ฉันสร้างปัญหา@google-cloud/storageสำหรับสิ่งนี้อย่าลังเลที่จะ +1;) github.com/googleapis/nodejs-storage/issues/697
Théo Champion

1
ล่าสุด(makePublic)การเชื่อมโยง
galki

1
ดูเหมือนว่าfirebaseStorageDownloadTokensจะไม่ทำงานอีกต่อไป
Mason

1
คำตอบที่ได้รับการยอมรับแสดงให้เห็นว่าเป็นไปไม่ได้ที่จะได้รับ URL การดาวน์โหลดแบบถาวรที่ไม่หมดอายุซึ่งไม่ถูกต้อง รายละเอียดในคำตอบของคุณดีเยี่ยมและควรระบุว่าเป็นคำตอบที่ถูกต้อง ขอบคุณ.
DevMike

2
@thomas ขอบคุณสำหรับบทสรุปที่ยอดเยี่ยม! คุณกล่าวว่ามี 3 วิธีในการรับ URL การดาวน์โหลดโทเค็นแบบต่อเนื่อง แต่คุณแชร์เพียง 2: (a) จาก Firebase Storage Console และ (b) getDownloadURL () จากส่วนหน้า ฉันสงสัยว่าวิธีที่ 3 คืออะไร?
czphilip

23

ด้วยการเปลี่ยนแปลงล่าสุดในฟังก์ชั่นการตอบสนองของวัตถุคุณจะได้รับทุกสิ่งที่คุณต้องการเพื่อ "ต่อ" เข้าด้วยกัน 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);

2
คุณอ้างถึงการตอบสนองของวัตถุจากbucket.file().upload()? ฉันไม่ได้รับคุณสมบัติเมทาดาทาใด ๆ ในข้อมูลการตอบกลับและฉันไม่แน่ใจว่าจะรับสิ่งเหล่านี้ได้firebaseStorageDownloadTokensอย่างไร
Dygerati

[YOUR BUCKET] ก็bucket.nameเช่นกันคุณไม่จำเป็นต้องฮาร์ดโค้ดหรือใช้ตัวแปรท้องถิ่นเพิ่มเติม
Călin Darie

4
ปัญหาในการแก้ปัญหานี้คือ URL ของบริการเป็นฮาร์ดโค้ด หาก Firebase / Google เปลี่ยนแปลงอาจทำให้เสียหายได้ การใช้metadata.mediaLinkคุณสมบัติช่วยป้องกันปัญหาดังกล่าว
Laurent

2
ไม่รองรับกรณีที่จะสร้าง URL เช่นนี้ อาจใช้ได้ผลในวันนี้ แต่อาจพังได้ในอนาคต คุณควรใช้ API ที่ให้มาเพื่อสร้าง URL ดาวน์โหลดที่เหมาะสมเท่านั้น
Doug Stevenson

1
การใช้ URL แบบฮาร์ดโค้ดที่อาจเปลี่ยนแปลงเป็นทางเลือกที่ไม่ดี
Laurent

23

หากคุณกำลังทำงานในโครงการ 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. ไปที่คอนโซล Google API และเปิดใช้งาน IAM API ( https://console.developers.google.com/apis/api/iam.googleapis.com/overview )
  2. ยังคงอยู่ในคอนโซล API ไปที่เมนูหลัก "IAM & admin" -> "IAM"
  3. คลิกแก้ไขสำหรับบทบาท "บัญชีบริการเริ่มต้นของ App Engine"
  4. คลิก "เพิ่มบทบาทอื่น" และเพิ่มบทบาทที่เรียกว่า "ผู้สร้างโทเค็นบัญชีบริการ"
  5. บันทึกและรอสักครู่เพื่อให้การเปลี่ยนแปลงมีผล

ด้วยการกำหนดค่า vanilla Firebase ครั้งแรกที่คุณเรียกใช้โค้ดด้านบนคุณจะได้รับข้อผิดพลาดIdentity and Access Management (IAM) API ไม่ได้ถูกใช้ในโปรเจ็กต์ XXXXXX มาก่อนหรือปิดใช้งาน . ถ้าคุณทำตามลิงก์ในข้อความข้อผิดพลาดและเปิดใช้งาน IAM API คุณจะได้รับข้อผิดพลาดอื่น: อนุญาต iam.serviceAccounts.signBlob จำเป็นต้องมีการดำเนินการนี้กับบริการบัญชีบริการบัญชีของฉัน การเพิ่มบทบาทผู้สร้างโทเค็นจะแก้ไขปัญหาการอนุญาตครั้งที่สองนี้


ฉันกำลังจะออกคำตอบพร้อมรายละเอียดเดียวกันกับที่ฉันคิดออกในที่สุด - หวังว่าฉันจะได้อ่านวิธีแก้ปัญหานี้ก่อนหน้านี้: / สิ่งนี้ใช้ได้ผลสำหรับฉันเมื่อวันที่ 12/12/18 ขอบคุณสำหรับคำแนะนำโดยละเอียดมีประโยชน์มากสำหรับเราผู้เริ่มต้น !!
กาด

2
Signurl ของฉันกำลังจะหมดอายุใน 2 สัปดาห์ แต่ฉันใช้ admin.initializeApp () โดยไม่มีคีย์นี่คือปัญหาหรือไม่? ฉันตั้งค่าบัญชีบริการเริ่มต้นของแอป App Engine เป็น "owner" และ Cloud Functions Service Agent ตอนนี้เพิ่งลบ "owner" และเพิ่ม "Service Account Token Creator"
Amit Bravo

2
URL ที่ลงชื่อจะหมดอายุใน 7 วัน คุณสามารถกำหนดวันหมดอายุให้สั้นลง แต่ไม่นานกว่านั้น
Thomas David Kehoe

จะรีเฟรช url ได้อย่างไรหากหมดอายุ?
Manoj MM

จะรีเฟรช url เพื่อตั้งค่าเป็นเวลานานขึ้นได้อย่างไร
Saifallak

17

วิธีหนึ่งที่ฉันใช้อย่างประสบความสำเร็จคือการตั้งค่า 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 ดาวน์โหลดในอนาคตได้) แต่ใช้งานง่าย


1
คุณมีตัวอย่างที่คุณตั้งค่า uuid หรือไม่?
Drew Beaupre

1
ฉันมีคำถามเดียวกันกับ Drew คุณตั้งค่าข้อมูลเมตาไว้ที่ไหน ฉันพยายามตั้งค่าในขณะที่ฟังก์ชั่น bucket.upload ไม่ทำงาน
Vysakh Sreenivasan

1
Vysakh ฉันโพสต์คำตอบที่สมบูรณ์พร้อมตัวอย่าง หวังว่าจะช่วยคุณได้
Drew Beaupre

คุณสร้างโทเค็นที่ไหน / อย่างไร
CodyBugstein

3
ฉันจะไม่คิดว่าเทคนิคนี้ "ปลอดภัย" เนื่องจาก URL สำหรับดาวน์โหลดควรเป็นแบบทึบซึ่งส่วนประกอบต่างๆจะไม่ถูกแยกย่อยหรือประกอบ
Doug Stevenson

16

สำหรับผู้ที่สงสัยว่าไฟล์ Firebase Admin SDK serviceAccountKey.json ควรไปที่ใด เพียงวางไว้ในโฟลเดอร์ฟังก์ชันและปรับใช้ตามปกติ

มันยังทำให้ฉันงงว่าทำไมเราไม่ได้แค่ดาวน์โหลด url จาก metadata เหมือนที่เราทำใน Javascript SDK การสร้าง url ที่จะหมดอายุในที่สุดและบันทึกลงในฐานข้อมูลนั้นไม่เป็นที่พึงปรารถนา


16

คุณควรหลีกเลี่ยง harcoding คำนำหน้า URL ในรหัสของคุณ ฉันขอแนะนำให้ใช้ตัวเลือกนี้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);
});

2
ที่จริงดูเหมือนจะได้ผล ข้อเสียเพียงอย่างเดียวที่ฉันเห็นคือถ้าคุณกดรูปภาพในแถบ URL ของเบราว์เซอร์มันจะดาวน์โหลดรูปภาพแทนที่จะดูแบบอินไลน์
Michael Giovanni Pumo

file.getMetadata () ทำเคล็ดลับให้ฉันหลังจากใช้เมธอด save () ในการอ้างอิงไฟล์ ใช้ใน NodeJS กับ firebase-admin sdk
Pascal Lamers

ไม่ได้ผลฉันได้รับผู้โทรที่ไม่ระบุชื่อไม่มีที่เก็บวัตถุเข้าถึง your_app / image.jpg
Manoj MM

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

9

ฉันไม่สามารถแสดงความคิดเห็นเกี่ยวกับคำตอบที่ James Daniels ให้ได้ แต่ฉันคิดว่านี่สำคัญมากในการอ่าน

การให้ URL ที่มีลายเซ็นเหมือนกับที่เขาทำในหลาย ๆ กรณีค่อนข้างแย่และอาจเป็นอันตรายได้ ตามเอกสารของ Firebase URL ที่ลงชื่อจะหมดอายุหลังจากเวลาผ่านไปสักครู่ดังนั้นการเพิ่มลงในฐานข้อมูลของคุณจะทำให้ URL ว่างเปล่าหลังจากกรอบเวลาที่กำหนด

อาจเป็นเพราะเข้าใจผิดในเอกสารที่นั่นและ url ที่เซ็นชื่อไม่หมดอายุซึ่งอาจมีปัญหาด้านความปลอดภัยตามมา คีย์ดูเหมือนจะเหมือนกันสำหรับทุกไฟล์ที่อัปโหลด ซึ่งหมายความว่าเมื่อคุณได้รับ url ของไฟล์หนึ่งแล้วใครบางคนสามารถเข้าถึงไฟล์ที่เขาไม่ได้รับอนุญาตให้เข้าถึงได้อย่างง่ายดายเพียงแค่รู้ชื่อของพวกเขา

ถ้าฉันเข้าใจผิดฉันก็จะได้รับการแก้ไข คนอื่นน่าจะอัปเดตโซลูชันที่มีชื่อด้านบน ถ้าฉันอาจจะผิดที่นั่น


8

นี่คือสิ่งที่ฉันใช้อยู่ตอนนี้เรียบง่ายและทำงานได้อย่างไม่มีที่ติ

คุณไม่จำเป็นต้องทำอะไรกับ 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

#update: ไม่จำเป็นต้องโทรสองครั้งในวิธีการอัปโหลดเพื่อรับข้อมูลเมตา:

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

1
คุณจะใช้มันกับไฟล์ที่ไม่ได้เข้ารหัส base64 อย่างไร?
Tibor Udvari

1
ไม่ใช่ mediaLinkenter แต่เป็นเพียง mediaLink
l2aelba

1
ฉันไม่พบ mediaLink i.stack.imgur.com/B4Fw5.png
sarah

@ ซาร่าห์ฉันเขียนสิ่งนี้โดยใช้ typescript ไม่แน่ใจว่ามีการเปลี่ยนโมดูลหรือไม่
Oliver Dixon

4

หากคุณใช้ค่ารายการควบคุมการเข้าถึงที่กำหนดไว้ล่วงหน้าเป็น '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);

3

ฉันมีปัญหาเดียวกันอย่างไรก็ตามฉันกำลังดูโค้ดของตัวอย่างฟังก์ชัน firebase แทน README และคำตอบในหัวข้อนี้ก็ไม่ได้ช่วยอะไรเช่นกัน ...

คุณสามารถหลีกเลี่ยงการส่งไฟล์ config ได้โดยทำดังต่อไปนี้:

ไปที่Cloud Consoleของโปรเจ็กต์ของคุณ> IAM และผู้ดูแลระบบ> IAMค้นหาบัญชีบริการเริ่มต้นของ App Engine และเพิ่มบทบาทผู้สร้างโทเค็นบัญชีบริการให้กับสมาชิกนั้น วิธีนี้จะช่วยให้แอปของคุณสร้าง URL สาธารณะที่มีลายเซ็นให้กับรูปภาพได้

แหล่งที่มา: สร้างฟังก์ชันรูปขนาดย่อโดยอัตโนมัติ README

บทบาทของคุณสำหรับ App Engine ควรมีลักษณะดังนี้:

Cloud Console


2

วิธีนี้ใช้งานได้หากคุณต้องการเพียงแค่ไฟล์สาธารณะที่มี 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);
    }
});

1

สำหรับผู้ที่ใช้ 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) }));เดียวที่มีฟังก์ชั่นที่ทุกท่านควรจะตกลงกับ


สำหรับฉันมันคือ '../serviceaccountkey.json' แต่ขอบคุณสำหรับการใช้ ../
robert king

1

ตั้งแต่ firebase 6.0.0 ฉันสามารถเข้าถึงที่เก็บข้อมูลได้โดยตรงกับผู้ดูแลระบบดังนี้:

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

ฉันจึงไม่จำเป็นต้องเพิ่มบัญชีบริการ จากนั้นการตั้งค่า UUID ตามที่อ้างถึงด้านบนจะใช้สำหรับรับ url firebase


1

นี่คือสิ่งที่ดีที่สุดที่ฉันคิดขึ้นมา มันซ้ำซ้อน แต่เป็นวิธีเดียวที่เหมาะสมสำหรับฉัน

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

1

โดยไม่ต้อง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);
        });
    });
});


0

หากคุณได้รับข้อผิดพลาด:

Google Cloud Functions: ต้องใช้ (…) ไม่ใช่ฟังก์ชัน

ลองสิ่งนี้:

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

0

ฉันโพสต์ ans ของฉันแล้ว ... ใน URL ด้านล่างซึ่งคุณสามารถรับรหัสเต็มพร้อมโซลูชัน

ฉันจะอัปโหลดรูปภาพที่เข้ารหัส base64 (สตริง) โดยตรงไปยังที่เก็บข้อมูล Google Cloud โดยใช้ 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);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.