ทริกเกอร์ Realtime Database


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