مشغلات قاعدة البيانات في الوقت الفعلي

باستخدام Cloud Functions، يمكنك معالجة الأحداث في Firebase Realtime Database بدون الحاجة إلى تعديل رمز العميل. يتيح لك Cloud Functions تشغيل عمليات Realtime Database بامتيازات إدارية كاملة، ويضمن معالجة كل تغيير في Realtime Database بشكل فردي. يمكنك إجراء تغييرات على Firebase Realtime Database من خلال لقطة البيانات أو من خلال مدير SDK.

في مراحل النشاط العادية، تؤدي دالة Firebase Realtime Database ما يلي:

  1. ينتظر التغييرات في مسار Realtime Database معيّن.
  2. يتم تشغيله عند وقوع حدث وينفّذ مهامه.
  3. تتلقّى دالة الاستدعاء عنصر بيانات يحتوي على لقطة من البيانات المخزّنة في هذا المسار.

يمكنك تشغيل دالة استجابةً لكتابة عُقد قاعدة البيانات أو إنشائها أو تعديلها أو حذفها في 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 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، فستتم مطابقة الأحداث في كلا الموقعين التاليين:

 /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 في RTDB، يتم تضمين سياق المصادقة في حمولة الحدث:

  • ‫authtype: يشير إلى نوع المستخدم الرئيسي الذي بدأ الحدث. القيم المحتمَلة هي:
    • ‫app_user: مستخدم نهائي لتطبيق المطوّر.
    • ‫admin: حساب خدمة
    • ‫unauthenticated: مستخدم لم تتم مصادقته
    • ‫unknown: الإعداد التلقائي عندما لا تتوفّر معلومات المصادقة.
  • authid: المعرّف الفريد للمستخدم الأساسي
    • إذا كانت قيمة authtype هي app_user، يكون هذا هو المعرّف الفريد للمستخدم.
    • إذا كانت قيمة authtype هي admin، يشير ذلك إلى حساب الخدمة أو عنوان البريد الإلكتروني لمستخدم "إدارة الهوية وإمكانية الوصول".

يحوّل هذا الرمز نص الرسالة إلى أحرف لاتينية كبيرة فقط إذا لم يكن المستخدم الذي شغّل الدالة مشرفًا. بالإضافة إلى ذلك، يتحقّق الرمز أيضًا مما إذا كان المستخدم الذي أرسل الرسالة هو المرسِل الفعلي للرسالة.

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)