باستخدام Cloud Functions، يمكنك معالجة الأحداث في الـ Firebase Realtime Database بدون الحاجة إلى تعديل رمز العميل. تتيح لك Cloud Functions تشغيل عمليات Realtime Database بامتيازات إدارية كاملة، وتضمن معالجة كل تغيير في Realtime Database على حدة. يمكنك إجراء تغييرات Firebase Realtime Database عبر لقطة البيانات أو عبر حزمة SDK للمشرف.
في دورة حياة نموذجية، تنفّذ دالة Firebase Realtime Database ما يلي:
- تنتظر إجراء تغييرات على مسار معيّن Realtime Database.
- يتم تشغيلها عند وقوع حدث وتنفّذ مهامها.
- تتلقّى عنصر بيانات يحتوي على لقطة للبيانات المخزّنة في هذا المسار.
يمكنك تشغيل دالة استجابةً لكتابة بيانات أو إنشائها أو تعديلها أو حذفها في عقد قاعدة البيانات في Firebase Realtime Database. للتحكّم في وقت تشغيل الدالة، حدِّد أحد معالِجات الأحداث، و حدِّد مسار Realtime Database الذي ستستمع فيه إلى الأحداث.
ضبط موقع الدالة
يمكن أن تؤدي المسافة بين موقع مثيل Realtime Database وموقع الدالة إلى زيادة كبيرة في وقت استجابة الشبكة. كذلك، يمكن أن يؤدي عدم تطابق المناطق إلى تعذُّر النشر. لتجنُّب هذه الحالات، حدِّد موقع الدالة بحيث يتطابق مع موقع مثيل قاعدة البيانات.
معالجة أحداث Realtime Database
تتيح لك الدوال معالجة أحداث Realtime Database على مستويَين من التفاصيل؛ يمكنك الاستماع إلى أحداث الكتابة أو الإنشاء أو التعديل أو الحذف فقط، أو يمكنك الاستماع إلى أي تغيير من أي نوع في مرجع.
تتوفّر معالِجات الاستجابة لأحداث 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 in default function region us-central1 at path "/user/{uid}" // There must be at least one Realtime Database present in us-central1. 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" // This example assumes us-central1, but to set location: // region: "europe-west1" }, (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-*" // This example assumes us-central1, but to set location: // region: "europe-west1" }, (event) => { // … } );
Python
# All Realtime Database instances in default function region us-central1 at path "/user/{uid}"
# There must be at least one Realtime Database present in us-central1.
@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",
# This example assumes us-central1, but to set location:
# region="europe-west1",
)
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-*",
# This example assumes us-central1, but to set location:
# region="europe-west1",
)
def on_written_function_instance(event: db_fn.Event[db_fn.Change]):
# ...
pass
توجّه هذه المَعلمات الدالة لمعالجة عمليات الكتابة في مسار معيّن ضمن المثيل Realtime Database.
تتطابق مواصفات المسار مع جميع عمليات الكتابة التي تؤثر في مسار، بما في ذلك عمليات الكتابة التي تحدث في أي مكان أدناه. إذا ضبطت مسار الدالة على /foo/bar، سيتطابق ذلك مع الأحداث في كلا الموقعَين التاليَين:
/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/. - باستخدام مقطع يحتوي على علامة النجمة
*فقط. على سبيل المثال، يتطابقfoo/app*-usمع أي مقاطع فرعية أسفلfoo/تبدأ بالبادئةappوتنتهي باللاحقة-us.
يمكن أن تتطابق المسارات التي تحتوي على أحرف بدل مع أحداث متعددة، مثلاً من عملية كتابة واحدة. تتطابق عملية الإدراج
{
"foo": {
"hello": "world",
"firebase": "functions"
}
}
يتطابق مع المسار "/foo/*" مرّتَين: مرّة واحدة مع "hello": "world" ومرّة أخرى مع
"firebase": "functions".
التقاط المسار يمكنك التقاط تطابقات المسار في متغيّرات مُسمّاة لاستخدامها في رمز الدالة (مثل /user/{uid} و/user/{uid=*-us}).
تتوفّر قيم متغيّرات الالتقاط ضمن عنصر `database.DatabaseEvent.params` في الدالة.
استخدام أحرف البدل في المثيل يمكنك أيضًا تحديد مكوّن مثيل باستخدام أحرف البدل. يمكن أن يحتوي حرف البدل في المثيل على بادئة أو لاحقة أو كليهما (مثل my-app-*-prod).
مرجع استخدام أحرف البدل والتقاط البيانات
باستخدام Cloud Functions (الجيل الثاني) و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 يحتوي على لقطتَين تمثّلان حالة البيانات قبل الحدث الذي أدّى إلى تشغيلها وبعده.
يحتوي عنصر 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 في Realtime Database، يتم تضمين سياق المصادقة في حمولة الحدث:
- authtype: نوع الأساسي الذي شغّل الحدث. القيم المحتمَلة هي:
app_user: مستخدم نهائي لتطبيق المطوّر.admin: حساب خدمة.unauthenticated: مستخدم لم تتم مصادقته.unknown: القيمة التلقائية عندما لا تتوفّر معلومات المصادقة.
- authid: المعرّف الفريد للأساسي.
- إذا كانت
authtypeهيapp_user، يكون هذا هو رقم تعريف المستخدم. - إذا كانت
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)