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

باستخدام 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)

الوصول إلى سياق المصادقة

بالنسبة إلى الدوال التي يتم تشغيلها من خلال أحداث RTDB eventarc، يتم تضمين سياق المصادقة في حمولة الحدث:

  • 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)