ทริกเกอร์ Realtime Database

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

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

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

คุณสามารถทริกเกอร์ฟังก์ชันเพื่อตอบสนองต่อการเขียน การสร้าง การอัปเดต หรือการลบโหนดฐานข้อมูลใน Firebase Realtime Database ได้ หากต้องการควบคุมเวลาที่ฟังก์ชันจะทริกเกอร์ ให้ระบุตัวแฮนเดิลเหตุการณ์อย่างใดอย่างหนึ่ง และระบุเส้นทาง Realtime Database ที่จะคอยฟังเหตุการณ์

ตั้งค่าตำแหน่งของฟังก์ชัน

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

จัดการเหตุการณ์ Realtime Database รายการ

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

ตัวแฮนเดิลต่อไปนี้พร้อมใช้งานสำหรับการตอบสนองต่อเหตุการณ์ Realtime Database

Node.js

  • onValueWritten() ทริกเกอร์เมื่อมีการสร้าง อัปเดต หรือลบข้อมูลใน Realtime Database
  • onValueCreated() ทริกเกอร์เฉพาะเมื่อมีการสร้างข้อมูลใน Realtime Database
  • onValueUpdated() ทริกเกอร์เฉพาะเมื่อมีการอัปเดตข้อมูลใน Realtime Database
  • onValueDeleted() ทริกเกอร์เฉพาะเมื่อมีการลบข้อมูลใน Realtime Database

Python

  • on_value_written() ทริกเกอร์เมื่อมีการสร้าง อัปเดต หรือลบข้อมูลใน Realtime Database
  • on_value_created() ทริกเกอร์เฉพาะเมื่อมีการสร้างข้อมูลใน Realtime Database
  • on_value_updated() ทริกเกอร์เฉพาะเมื่อมีการอัปเดตข้อมูลใน Realtime Database
  • on_value_deleted() ทริกเกอร์เฉพาะเมื่อมีการลบข้อมูลใน Realtime Database

นำเข้าโมดูลที่จำเป็น

ในแหล่งที่มาของฟังก์ชัน คุณต้องนําเข้าโมดูล SDK ที่ต้องการใช้ สำหรับตัวอย่างนี้ จำเป็นต้องนำเข้าโมดูล HTTP และ Realtime Database พร้อมกับโมดูล Firebase Admin SDK สำหรับการเขียนไปยัง Realtime Database

Node.js

// The Cloud Functions for Firebase SDK to setup triggers and logging.
const {onRequest} = require("firebase-functions/https");
const {onValueCreated} = require("firebase-functions/database");
const {logger} = require("firebase-functions");

// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require("firebase-admin");
admin.initializeApp();

Python

# The Cloud Functions for Firebase SDK to create Cloud Functions and set up triggers.
from firebase_functions import db_fn, https_fn

# The Firebase Admin SDK to access the Firebase Realtime Database.
from firebase_admin import initialize_app, db

app = initialize_app()

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

หากต้องการควบคุมเวลาและตำแหน่งที่ฟังก์ชันควรทริกเกอร์ ให้กำหนดค่าฟังก์ชันด้วยเส้นทางและอินสแตนซ์ Realtime Database (ไม่บังคับ) หากคุณไม่ระบุอินสแตนซ์ ฟังก์ชันจะคอยฟังอินสแตนซ์ Realtime Database ทั้งหมดในภูมิภาคของฟังก์ชัน นอกจากนี้ คุณยังระบุรูปแบบอินสแตนซ์ Realtime Database เพื่อทำให้ใช้งานได้กับชุดย่อยของอินสแตนซ์ที่เลือกในภูมิภาคเดียวกันได้ด้วย

เช่น

Node.js

// All Realtime Database instances at path "/user/{uid}"
// There must be at least one Realtime Database present.
const onWrittenFunctionDefault = onValueWritten("/user/{uid}", (event) => {
  // …
});

// Instance named "my-app-db-2", at path "/user/{uid}".
// The "my-app-db-2" instance must exist in this region.
const OnWrittenFunctionInstance = onValueWritten(
  {
    ref: "/user/{uid}",
    instance: "my-app-db-2"
  },
  (event) => {
    // …
  }
);

// Instance with "my-app-db-" prefix, at path "/user/{uid}", where uid ends with @gmail.com.
// There must be at least one Realtime Database with "my-app-db-*" prefix in this region.
const onWrittenFunctionInstance = onValueWritten(
  {
    ref: "/user/{uid=*@gmail.com}",
    instance: "my-app-db-*"
  },
  (event) => {
    // …
  }
);

Python

# All Realtime Database instances at path "/user/{uid}"
# There must be at least one Realtime Database present.
@db_fn.on_value_written(r"/user/{uid}")
def onwrittenfunctiondefault(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

# Instance named "my-app-db-2", at path "/user/{uid}".
# The "my-app-db-2" instance must exist in this region.
@db_fn.on_value_written(
    reference=r"/user/{uid}",
    instance="my-app-db-2",
)
def on_written_function_instance(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

# Instance with "my-app-db-" prefix, at path "/user/{uid}", where uid ends with @gmail.com.
# There must be at least one Realtime Database with "my-app-db-*" prefix in this region.
@db_fn.on_value_written(
    reference=r"/user/{uid=*@gmail.com}",
    instance="my-app-db-*",
)
def on_written_function_instance(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

พารามิเตอร์เหล่านี้จะกำหนดให้ฟังก์ชันจัดการการเขียนที่เส้นทางหนึ่งๆ ภายในอินสแตนซ์ Realtime Database

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

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

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

ไวลด์การ์ดและการจับภาพ

คุณสามารถใช้ {key}, {key=*}, {key=prefix*}, {key=*suffix} เพื่อจับภาพได้ *, prefix*, *suffix สำหรับไวลด์การ์ดกลุ่มเดียว หมายเหตุ: ** แสดงถึงไวลด์การ์ดแบบหลายกลุ่ม ซึ่ง Realtime Database ไม่รองรับ ดูทำความเข้าใจรูปแบบเส้นทาง

ไวลด์การ์ดเส้นทาง คุณสามารถระบุคอมโพเนนต์เส้นทางเป็นไวลด์การ์ดได้ ดังนี้

  • ใช้เครื่องหมายดอกจัน * เช่น foo/* จะจับคู่กับโหนดย่อยที่อยู่ต่ำกว่า foo/ 1 ระดับในลำดับชั้นของโหนด
  • การใช้กลุ่มที่มีดอกจัน * เท่านั้น ตัวอย่างเช่น foo/app*-us จะจับคู่กับกลุ่มย่อยใดก็ได้ที่อยู่ใต้ foo/ โดยมีคำนำหน้า app และคำต่อท้าย -us

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

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

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

การจับภาพเส้นทาง คุณสามารถบันทึกการจับคู่เส้นทางลงในตัวแปรที่มีชื่อเพื่อใช้ในโค้ดฟังก์ชัน (เช่น /user/{uid}, /user/{uid=*-us})

ค่าของตัวแปรการบันทึกจะอยู่ในออบเจ็กต์ database.DatabaseEvent.params ของฟังก์ชัน

การใช้ไวลด์การ์ดอินสแตนซ์ นอกจากนี้ คุณยังระบุคอมโพเนนต์อินสแตนซ์โดยใช้ไวลด์การ์ดได้ด้วย ไวลด์การ์ดอินสแตนซ์สามารถมีคำนำหน้า คำต่อท้าย หรือทั้ง 2 อย่าง (เช่น my-app-*-prod)

ไวลด์การ์ดและการอ้างอิงการจับภาพ

เมื่อใช้ Cloud Functions (รุ่นที่ 2) และ Realtime Database คุณจะใช้รูปแบบได้เมื่อระบุ ref และ instance อินเทอร์เฟซทริกเกอร์แต่ละรายการจะมีตัวเลือกต่อไปนี้สำหรับการกำหนดขอบเขตฟังก์ชัน

การระบุ ref การระบุ instance พฤติกรรม
ซิงเกิล (/foo/bar) ไม่ระบุ กำหนดขอบเขตเครื่องจัดการให้กับอินสแตนซ์ทั้งหมดในภูมิภาคของฟังก์ชัน
ซิงเกิล (/foo/bar) ซิงเกิล (‘my-new-db') กำหนดขอบเขตของเครื่องจัดการไปยังอินสแตนซ์ที่เฉพาะเจาะจงในภูมิภาคของฟังก์ชัน
ซิงเกิล (/foo/bar) รูปแบบ (‘inst-prefix*') กำหนดขอบเขตของเครื่องจัดการให้กับอินสแตนซ์ทั้งหมดที่ตรงกับรูปแบบในภูมิภาคของฟังก์ชัน
รูปแบบ (/foo/{bar}) ไม่ระบุ กำหนดขอบเขตเครื่องจัดการให้กับอินสแตนซ์ทั้งหมดในภูมิภาคของฟังก์ชัน
รูปแบบ (/foo/{bar}) ซิงเกิล (‘my-new-db') กำหนดขอบเขตของเครื่องจัดการไปยังอินสแตนซ์ที่เฉพาะเจาะจงในภูมิภาคของฟังก์ชัน
รูปแบบ (/foo/{bar}) รูปแบบ (‘inst-prefix*') กำหนดขอบเขตของเครื่องจัดการให้กับอินสแตนซ์ทั้งหมดที่ตรงกับรูปแบบในภูมิภาคของฟังก์ชัน

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

เมื่อเหตุการณ์ Realtime Database ทริกเกอร์ ระบบจะส่งออบเจ็กต์ Event ไปยังฟังก์ชันตัวแฮนเดิล ออบเจ็กต์นี้มีพร็อพเพอร์ตี้ data ซึ่งสำหรับเหตุการณ์การสร้างและการลบ จะมีภาพรวมของข้อมูลที่สร้างหรือลบ

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

Node.js

// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
// for all databases in 'us-central1'
exports.makeuppercase = onValueCreated(
    "/messages/{pushId}/original",
    (event) => {
    // Grab the current value of what was written to the Realtime Database.
      const original = event.data.val();
      logger.log("Uppercasing", event.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing
      // asynchronous tasks inside a function, such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the
      // Realtime Database returns a Promise.
      return event.data.ref.parent.child("uppercase").set(uppercase);
    },
);

Python

@db_fn.on_value_created(reference="/messages/{pushId}/original")
def makeuppercase(event: db_fn.Event[Any]) -> None:
    """Listens for new messages added to /messages/{pushId}/original and
    creates an uppercase version of the message to /messages/{pushId}/uppercase
    """

    # Grab the value that was written to the Realtime Database.
    original = event.data
    if not isinstance(original, str):
        print(f"Not a string: {event.reference}")
        return

    # Use the Admin SDK to set an "uppercase" sibling.
    print(f"Uppercasing {event.params['pushId']}: {original}")
    upper = original.upper()
    parent = db.reference(event.reference).parent
    if parent is None:
        print("Message can't be root node.")
        return
    parent.child("uppercase").set(upper)

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

สำหรับเหตุการณ์ write หรือ update พร็อพเพอร์ตี้ data จะเป็นออบเจ็กต์ Change ที่มีสแนปชอต 2 รายการซึ่งแสดงสถานะข้อมูลก่อนและหลังเหตุการณ์ที่ทริกเกอร์ ออบเจ็กต์ Change มีพร็อพเพอร์ตี้ before ที่ช่วยให้คุณตรวจสอบสิ่งที่บันทึกไว้ใน Realtime Database ก่อนเหตุการณ์ และพร็อพเพอร์ตี้ after ที่แสดงสถานะของข้อมูลหลังจากเหตุการณ์เกิดขึ้น

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

Node.js

  exports makeUppercase = onValueWritten("/messages/{pushId}/original", (event) => {
        // Only edit data when it is first created.
        if (event.data.before.exists()) {
          return null;
        }
        // Exit when the data is deleted.
        if (!event.data.after.exists()) {
          return null;
        }
        // Grab the current value of what was written to the Realtime Database.
        const original = event.data.after.val();
        console.log('Uppercasing', event.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 event.data.after.ref.parent.child('uppercase').set(uppercase);
      });

Python

@db_fn.on_value_written(reference="/messages/{pushId}/original")
def makeuppercase2(event: db_fn.Event[db_fn.Change]) -> None:
    """Listens for new messages added to /messages/{pushId}/original and
    creates an uppercase version of the message to /messages/{pushId}/uppercase
    """

    # Only edit data when it is first created.
    if event.data.before is not None:
        return

    # Exit when the data is deleted.
    if event.data.after is None:
        return

    # Grab the value that was written to the Realtime Database.
    original = event.data.after
    if not hasattr(original, "upper"):
        print(f"Not a string: {event.reference}")
        return

    # Use the Admin SDK to set an "uppercase" sibling.
    print(f"Uppercasing {event.params['pushId']}: {original}")
    upper = original.upper()
    parent = db.reference(event.reference).parent
    if parent is None:
        print("Message can't be root node.")
        return
    parent.child("uppercase").set(upper)

เข้าถึงบริบทการตรวจสอบสิทธิ์

สำหรับฟังก์ชันที่ทริกเกอร์โดยเหตุการณ์ Eventarc ของ RTDB บริบทการตรวจสอบสิทธิ์จะรวมอยู่ในเพย์โหลดของเหตุการณ์ ดังนี้

  • authtype: ประเภทของ Principal ที่ทริกเกอร์เหตุการณ์ โดยค่าที่เลือกได้มีดังนี้
    • app_user: ผู้ใช้ปลายทางของแอปพลิเคชันของนักพัฒนาแอป
    • admin: บัญชีบริการ
    • unauthenticated: ผู้ใช้ที่ไม่ผ่านการตรวจสอบสิทธิ์
    • unknown: ค่าเริ่มต้นเมื่อไม่มีข้อมูลการตรวจสอบสิทธิ์
  • authid: ตัวระบุที่ไม่ซ้ำกันของ Principal
    • หาก authtype คือ app_user ค่านี้จะเป็น UID ของผู้ใช้
    • หาก authtype เป็น admin นี่คืออีเมลผู้ใช้ IAM หรือบัญชีบริการ

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

Node.js

// The Cloud Functions for Firebase SDK to setup triggers and logging.
const {onValueWritten} = require("firebase-functions/v2/database");
const {logger} = require("firebase-functions");
const admin = require("firebase-admin");

admin.initializeApp();

exports.dbtrigger = onValueWritten("/messages/{pushId}/original", async (event) => {
  // 1. Check whether authtype is admin. If it is, skip this operation.
  if (event.authType === "admin") {
    logger.log("Modification by admin detected. Skipping uppercase conversion.");
    return null;
  }

  // 2. Retrieve the userID of the sender (assumed sibling node 'senderId')
  const snapshot = await event.data.after.ref.parent.child("senderId").get();
  const senderId = snapshot.val();

  // 3. Check if userID of sender of message = event.authid
  if (senderId !== event.authId) {
    logger.error(`Unauthorized write: senderId (${senderId}) does not match authId (${event.authId})`);
    return null;
  }

  // Grab the value that was written to the Realtime Database.
  const original = event.data.after.val();
  logger.log("Uppercasing", event.params.pushId, original);
  const uppercase = original.toUpperCase();

  // Return the promise to set the "uppercase" sibling node.
  return event.data.after.ref.parent.child("uppercase").set(uppercase);
});

Python

from firebase_functions import db_fn
from firebase_admin import initialize_app, db

initialize_app()

@db_fn.on_value_written(reference="/messages/{pushId}/original")
def makeuppercase(event: db_fn.Event[db_fn.Change]) -> None:
    # 1. Check whether authtype is admin. If it is, skip this operation.
    if event.auth_type == "admin":
        print("Admin user detected. Skipping.")
        return

    # 2. Retrieve the userID of the sender (assumed sibling node: 'senderId')
    parent_ref = db.reference(event.reference).parent
    sender_id = parent_ref.child("senderId").get()

    # 3. Check if userID of sender = event.auth_id
    if sender_id != event.auth_id:
        print(f"Unauthorized: sender_id {sender_id} != auth_id {event.auth_id}")
        return

    # Exit when the data is deleted.
    if event.data.after is None:
        return

    # Grab the value and uppercase it
    original = event.data.after
    if not isinstance(original, str):
        return

    print(f"Uppercasing {event.params['pushId']}: {original}")
    upper = original.upper()
    parent_ref.child("uppercase").set(upper)