Cloud Functions ช่วยให้คุณจัดการเหตุการณ์ในFirebase Realtime Databaseได้โดยไม่ต้องอัปเดตโค้ดฝั่งไคลเอ็นต์ Cloud Functions ช่วยให้คุณดำเนินการ Realtime Database ด้วยสิทธิ์ของผู้ดูแลระบบอย่างเต็มรูปแบบ และช่วยให้มั่นใจว่าการเปลี่ยนแปลงแต่ละรายการใน Realtime Database จะได้รับการประมวลผลแยกกัน คุณสามารถทำการเปลี่ยนแปลง Firebase Realtime Database รายการผ่านสแนปชอตข้อมูลหรือผ่าน Admin SDK
ในวงจรปกติ ฟังก์ชัน Firebase Realtime Database จะทำสิ่งต่อไปนี้
- รอการเปลี่ยนแปลงในเส้นทาง Realtime Database ที่เฉพาะเจาะจง
- ทริกเกอร์เมื่อเกิดเหตุการณ์และทำงานของเหตุการณ์นั้น
- รับออบเจ็กต์ข้อมูลที่มีภาพรวมของข้อมูลที่จัดเก็บไว้ในเส้นทางนั้น
คุณสามารถทริกเกอร์ฟังก์ชันเพื่อตอบสนองต่อการเขียน การสร้าง การอัปเดต หรือการลบโหนดฐานข้อมูลใน Firebase Realtime Database ได้ หากต้องการควบคุมเวลาที่ฟังก์ชันจะทริกเกอร์ ให้ระบุตัวแฮนเดิลเหตุการณ์อย่างใดอย่างหนึ่ง และระบุเส้นทาง Realtime Database ที่จะคอยฟังเหตุการณ์
ตั้งค่าตำแหน่งของฟังก์ชัน
ระยะห่างระหว่างตำแหน่งของอินสแตนซ์ Realtime Database และตำแหน่งของฟังก์ชันอาจทำให้เกิดเวลาในการตอบสนองของเครือข่ายอย่างมาก นอกจากนี้ ภูมิภาคที่ไม่ตรงกันอาจส่งผลให้การติดตั้งใช้งานล้มเหลว หากต้องการหลีกเลี่ยงสถานการณ์เหล่านี้ ให้ระบุตำแหน่งฟังก์ชันให้ตรงกับตำแหน่งอินสแตนซ์ฐานข้อมูล
จัดการเหตุการณ์ Realtime Database รายการ
ฟังก์ชันช่วยให้คุณจัดการเหตุการณ์ Realtime Database ได้ที่ความเฉพาะเจาะจง 2 ระดับ คุณสามารถรอฟังเฉพาะเหตุการณ์การเขียน การสร้าง การอัปเดต หรือการลบ หรือจะรอฟังการเปลี่ยนแปลงประเภทใดก็ได้ในการอ้างอิง
ตัวแฮนเดิลต่อไปนี้พร้อมใช้งานสำหรับการตอบสนองต่อเหตุการณ์ Realtime Database
Node.js
onValueWritten()ทริกเกอร์เมื่อมีการสร้าง อัปเดต หรือลบข้อมูลใน Realtime DatabaseonValueCreated()ทริกเกอร์เฉพาะเมื่อมีการสร้างข้อมูลใน Realtime DatabaseonValueUpdated()ทริกเกอร์เฉพาะเมื่อมีการอัปเดตข้อมูลใน Realtime DatabaseonValueDeleted()ทริกเกอร์เฉพาะเมื่อมีการลบข้อมูลใน Realtime Database
Python
on_value_written()ทริกเกอร์เมื่อมีการสร้าง อัปเดต หรือลบข้อมูลใน Realtime Databaseon_value_created()ทริกเกอร์เฉพาะเมื่อมีการสร้างข้อมูลใน Realtime Databaseon_value_updated()ทริกเกอร์เฉพาะเมื่อมีการอัปเดตข้อมูลใน Realtime Databaseon_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)