ทริกเกอร์ Realtime Database (รุ่นที่ 1)

Cloud Functions ช่วยให้คุณจัดการเหตุการณ์ในFirebase Realtime Databaseได้โดยไม่ต้องอัปเดตโค้ดฝั่งไคลเอ็นต์ Cloud Functions ช่วยให้คุณดำเนินการ Realtime Database ด้วยสิทธิ์ของผู้ดูแลระบบอย่างเต็มรูปแบบ และช่วยให้มั่นใจว่าการเปลี่ยนแปลงแต่ละรายการใน Realtime Database จะได้รับการประมวลผลแยกกัน คุณสามารถเปลี่ยนแปลง Firebase Realtime Database รายการผ่านDataSnapshotหรือผ่าน Admin SDK

ในวงจรปกติ ฟังก์ชัน Firebase Realtime Database จะทำสิ่งต่อไปนี้

  1. รอการเปลี่ยนแปลงในตำแหน่ง Realtime Database ที่เฉพาะเจาะจง
  2. ทริกเกอร์เมื่อเกิดเหตุการณ์และทำงาน (ดูฉันจะทำอะไรได้บ้างด้วย Cloud Functions เพื่อดูตัวอย่างกรณีการใช้งาน)
  3. รับออบเจ็กต์ข้อมูลที่มีภาพรวมของข้อมูลที่จัดเก็บไว้ในเอกสารที่ระบุ

ทริกเกอร์ฟังก์ชัน Realtime Database

สร้างฟังก์ชันใหม่สำหรับเหตุการณ์ Realtime Database ด้วย functions.database หากต้องการควบคุมเวลาที่ฟังก์ชันจะทริกเกอร์ ให้ระบุตัวแฮนเดิลเหตุการณ์อย่างใดอย่างหนึ่ง และระบุเส้นทาง Realtime Database ที่จะคอยฟังเหตุการณ์

ตั้งค่าเครื่องจัดการเหตุการณ์

ฟังก์ชันช่วยให้คุณจัดการเหตุการณ์ Realtime Database ได้ในความเฉพาะเจาะจง 2 ระดับ คุณสามารถฟังเฉพาะเหตุการณ์การสร้าง การอัปเดต หรือการลบ หรือจะฟังการเปลี่ยนแปลงประเภทใดก็ได้ในเส้นทาง Cloud Functions รองรับตัวแฮนเดิลเหตุการณ์เหล่านี้สำหรับ Realtime Database

  • onWrite() ซึ่งจะทริกเกอร์เมื่อมีการสร้าง อัปเดต หรือลบข้อมูลใน Realtime Database
  • onCreate() ซึ่งจะทริกเกอร์เมื่อมีการสร้างข้อมูลใหม่ใน Realtime Database
  • onUpdate() ซึ่งจะทริกเกอร์เมื่อมีการอัปเดตข้อมูลใน Realtime Database
  • onDelete() ซึ่งจะทริกเกอร์เมื่อมีการลบข้อมูลออกจาก Realtime Database

ระบุอินสแตนซ์และเส้นทาง

หากต้องการควบคุมเวลาและตำแหน่งที่ฟังก์ชันควรทริกเกอร์ ให้เรียกใช้ ref(path) เพื่อระบุเส้นทาง และระบุอินสแตนซ์ Realtime Database ด้วย instance('INSTANCE_NAME') (ไม่บังคับ) หากไม่ระบุอินสแตนซ์ ฟังก์ชันจะทำให้ใช้งานได้ในอินสแตนซ์ Realtime Database เริ่มต้นสำหรับโปรเจ็กต์ Firebase เช่น

  • อินสแตนซ์ Realtime Database เริ่มต้น: functions.database.ref('/foo/bar')
  • อินสแตนซ์ชื่อ "my-app-db-2": functions.database.instance('my-app-db-2').ref('/foo/bar')

วิธีการเหล่านี้จะกำหนดให้ฟังก์ชันจัดการการเขียนที่เส้นทางหนึ่งๆ ภายในอินสแตนซ์ Realtime Database ข้อกำหนดเฉพาะของเส้นทางจะตรงกับทุกการเขียนที่แตะเส้นทาง รวมถึงการเขียนที่เกิดขึ้นที่ใดก็ได้ด้านล่างเส้นทาง หากคุณตั้งค่าเส้นทางสำหรับฟังก์ชันเป็น /foo/bar ระบบจะจับคู่เหตุการณ์ที่ตำแหน่งทั้ง 2 แห่งนี้

 /foo/bar
 /foo/bar/baz/really/deep/path

ไม่ว่าในกรณีใด Firebase จะตีความว่าเหตุการณ์เกิดขึ้นที่ /foo/bar และข้อมูลเหตุการณ์จะรวมข้อมูลเก่าและใหม่ที่ /foo/bar หากข้อมูลเหตุการณ์อาจมีขนาดใหญ่ ให้พิจารณาใช้หลายฟังก์ชันในเส้นทางที่ลึกขึ้นแทนที่จะใช้ฟังก์ชันเดียวใกล้กับรูทของฐานข้อมูล เพื่อประสิทธิภาพที่ดีที่สุด ให้ขอข้อมูลในระดับที่ลึกที่สุดเท่าที่จะเป็นไปได้เท่านั้น

คุณสามารถระบุคอมโพเนนต์เส้นทางเป็นไวลด์การ์ดได้โดยใส่วงเล็บปีกกาไว้รอบๆ เช่น ref('foo/{bar}') จะจับคู่กับรายการย่อยของ /foo ค่าของคอมโพเนนต์เส้นทางไวลด์การ์ดเหล่านี้จะอยู่ในออบเจ็กต์ EventContext.params ของฟังก์ชัน ในตัวอย่างนี้ ค่าจะแสดงเป็น context.params.bar

เส้นทางที่มีไวลด์การ์ดสามารถจับคู่เหตุการณ์หลายรายการจากการเขียนครั้งเดียวได้ การแทรก

{
  "foo": {
    "hello": "world",
    "firebase": "functions"
  }
}

จับคู่เส้นทาง "/foo/{bar}" 2 ครั้ง โดยครั้งแรกจับคู่กับ "hello": "world" และอีกครั้งจับคู่กับ "firebase": "functions"

จัดการข้อมูลเหตุการณ์

เมื่อจัดการเหตุการณ์ Realtime Database ออบเจ็กต์ข้อมูลที่แสดงผลจะเป็น DataSnapshot สำหรับเหตุการณ์ onWrite หรือ onUpdate พารามิเตอร์แรกคือออบเจ็กต์ Change ที่มีสแนปชอต 2 รายการ ซึ่งแสดงสถานะข้อมูลก่อน และหลังเหตุการณ์ที่ทริกเกอร์ สำหรับเหตุการณ์ onCreate และ onDelete ออบเจ็กต์ข้อมูลที่แสดงผลจะเป็นภาพรวมของข้อมูลที่สร้างหรือลบ

ในตัวอย่างนี้ ฟังก์ชันจะดึงสแนปชอตสำหรับเส้นทางที่ระบุ แปลงสตริงที่ตำแหน่งนั้นเป็นตัวพิมพ์ใหญ่ และเขียนสตริงที่แก้ไขแล้วลงในฐานข้อมูล

// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snapshot, context) => {
      // Grab the current value of what was written to the Realtime Database.
      const original = snapshot.val();
      functions.logger.log('Uppercasing', context.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing asynchronous tasks inside a Functions such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
      return snapshot.ref.parent.child('uppercase').set(uppercase);
    });

เข้าถึงข้อมูลการตรวจสอบสิทธิ์ผู้ใช้

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

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

exports.simpleDbFunction = functions.database.ref('/path')
    .onCreate((snap, context) => {
      if (context.authType === 'ADMIN') {
        // do something
      } else if (context.authType === 'USER') {
        console.log(snap.val(), 'written by', context.auth.uid);
      }
    });

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

exports.impersonateMakeUpperCase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snap, context) => {
      const appOptions = JSON.parse(process.env.FIREBASE_CONFIG);
      appOptions.databaseAuthVariableOverride = context.auth;
      const app = admin.initializeApp(appOptions, 'app');
      const uppercase = snap.val().toUpperCase();
      const ref = snap.ref.parent.child('uppercase');

      const deleteApp = () => app.delete().catch(() => null);

      return app.database().ref(ref).set(uppercase).then(res => {
        // Deleting the app is necessary for preventing concurrency leaks
        return deleteApp().then(() => res);
      }).catch(err => {
        return deleteApp().then(() => Promise.reject(err));
      });
    });

อ่านค่าก่อนหน้า

ออบเจ็กต์ Change มีพร็อพเพอร์ตี้ before ที่ช่วยให้คุณตรวจสอบสิ่งที่บันทึกไว้ใน Realtime Database ก่อนเหตุการณ์ได้ พร็อพเพอร์ตี้ before จะแสดงผล DataSnapshot โดยที่เมธอดทั้งหมด (เช่น val() และ exists()) จะอ้างอิงค่าก่อนหน้า คุณอ่านค่าใหม่ได้อีกครั้งโดยใช้ DataSnapshot เดิมหรืออ่านพร็อพเพอร์ตี้ after พร็อพเพอร์ตี้นี้ใน Change ใดๆ เป็น DataSnapshot อีกรายการหนึ่งที่แสดงสถานะของข้อมูลหลังจากที่เหตุการณ์เกิดขึ้น

เช่น คุณสามารถใช้พร็อพเพอร์ตี้ before เพื่อให้แน่ใจว่าฟังก์ชันจะเปลี่ยนข้อความเป็นตัวพิมพ์ใหญ่เฉพาะเมื่อสร้างขึ้นครั้งแรกเท่านั้น

exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onWrite((change, context) => {
      // Only edit data when it is first created.
      if (change.before.exists()) {
        return null;
      }
      // Exit when the data is deleted.
      if (!change.after.exists()) {
        return null;
      }
      // Grab the current value of what was written to the Realtime Database.
      const original = change.after.val();
      console.log('Uppercasing', context.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing asynchronous tasks inside a Functions such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
      return change.after.ref.parent.child('uppercase').set(uppercase);
    });