ทริกเกอร์ Realtime Database


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

ในวงจรทั่วไป ฟังก์ชันฐานข้อมูลเรียลไทม์ของ Firebase จะทำสิ่งต่อไปนี้

  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

ในทั้ง 2 กรณี 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');
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 ที่ช่วยให้คุณตรวจสอบสิ่งที่บันทึกลงในฐานข้อมูลเรียลไทม์ก่อนเหตุการณ์ได้ พร็อพเพอร์ตี้ 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);
    });