Realtime Database-Trigger

Mit Cloud Functions können Sie Ereignisse in der Firebase Realtime Database verarbeiten, ohne den Clientcode aktualisieren zu müssen. Mit Cloud Functions können Sie Realtime Database-Vorgänge mit vollen Administratorberechtigungen ausführen und sicherstellen, dass jede Änderung an Realtime Database einzeln verarbeitet wird. Sie können Firebase Realtime Database-Änderungen über den Daten-Snapshot oder das Admin SDK vornehmen.

Der typische Lebenszyklus einer Firebase Realtime Database-Funktion sieht so aus:

  1. Sie wartet auf Änderungen an einem bestimmten Realtime Database-Pfad.
  2. Sie wird ausgelöst, wenn ein Ereignis eintritt, und führt dessen Aufgaben aus.
  3. Empfängt ein Datenobjekt, das einen Snapshot der unter diesem Pfad gespeicherten Daten enthält.

Sie können eine Funktion als Reaktion auf das Schreiben, Erstellen, Aktualisieren oder Löschen von Datenbankknoten in Firebase Realtime Database auslösen. Wenn Sie festlegen möchten, wann die Funktion ausgelöst wird, geben Sie einen der Event-Handler und den Realtime Database-Pfad an, auf dem auf Ereignisse gewartet wird.

Funktionsstandort festlegen

Die Entfernung zwischen dem Standort einer Realtime Database-Instanz und dem Standort der Funktion kann zu einer erheblichen Netzwerklatenz führen. Außerdem kann eine Nichtübereinstimmung zwischen Regionen zu einem Bereitstellungsfehler führen. Um diese Situationen zu vermeiden, geben Sie den Funktionsstandort so an, dass er mit dem Standort der Datenbankinstanz übereinstimmt.

Realtime Database-Ereignisse verarbeiten

Mit Funktionen können Sie Realtime Database-Ereignisse auf zwei Ebenen verarbeiten. Sie können speziell auf Schreib-, Erstellungs-, Aktualisierungs- oder Löschereignisse oder auf jede Art von Änderung an einer Referenz warten.

Die folgenden Handler sind für die Reaktion auf Realtime Database-Ereignisse verfügbar:

Node.js

  • onValueWritten() Wird ausgelöst, wenn Daten in Realtime Database erstellt, aktualisiert oder gelöscht werden.
  • onValueCreated() Wird nur ausgelöst, wenn Daten in Realtime Database erstellt werden.
  • onValueUpdated() Wird nur ausgelöst, wenn Daten in Realtime Database aktualisiert werden.
  • onValueDeleted() Wird nur ausgelöst, wenn Daten in Realtime Database gelöscht werden.

Python

  • on_value_written() Wird ausgelöst, wenn Daten in Realtime Database erstellt, aktualisiert oder gelöscht werden.
  • on_value_created() Wird nur ausgelöst, wenn Daten in Realtime Database erstellt werden.
  • on_value_updated() Wird nur ausgelöst, wenn Daten in Realtime Database aktualisiert werden.
  • on_value_deleted() Wird nur ausgelöst, wenn Daten in Realtime Database gelöscht werden.

Erforderliche Module importieren

In der Funktionsquelle müssen Sie die SDK-Module importieren, die Sie verwenden möchten. Für dieses Beispiel müssen die Module HTTP und Realtime Database sowie das Modul Firebase Admin SDK zum Schreiben in Realtime Database importiert werden.

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

Instanz und Pfad angeben

Wenn Sie steuern möchten, wann und wo Ihre Funktion ausgelöst werden soll, konfigurieren Sie sie mit einem Pfad und optional einer Realtime Database-Instanz. Wenn Sie keine Instanz angeben, überwacht die Funktion alle Realtime Database-Instanzen in der Funktionsregion. Sie können auch ein Realtime Database-Instanzmuster angeben, um die Bereitstellung auf einer ausgewählten Teilmenge von Instanzen in derselben Region vorzunehmen.

Beispiel:

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

Diese Parameter weisen die Funktion an, Schreibvorgänge in einem bestimmten Pfad innerhalb der Realtime Database-Instanz zu verarbeiten.

Pfadspezifikationen stimmen mit allen Schreibvorgängen überein, die einen Pfad betreffen, einschließlich Schreibvorgängen, die an einer beliebigen Stelle darunter erfolgen. Wenn Sie den Pfad für Ihre Funktion auf /foo/bar festlegen, werden Ereignisse an beiden Standorten abgeglichen:

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

In beiden Fällen interpretiert Firebase, dass das Ereignis um /foo/bar auftritt, und die Ereignisdaten enthalten die alten und neuen Daten um /foo/bar. Wenn die Ereignisdaten groß sein könnten, sollten Sie mehrere Funktionen in tieferen Pfaden anstelle einer einzelnen Funktion in der Nähe des Stammverzeichnisses Ihrer Datenbank verwenden. Für eine optimale Leistung sollten Sie Daten nur auf der tiefstmöglichen Ebene anfordern.

Platzhalter und Erfassung

Sie können {key}, {key=*}, {key=prefix*}, {key=*suffix} für die Erfassung verwenden. *, prefix*, *suffix für Platzhalter mit einem Segment. Hinweis: ** steht für Platzhalter mit mehreren Segmenten, die von Realtime Database nicht unterstützt werden. Weitere Informationen finden Sie unter Informationen zu Pfadmustern.

Pfad-Platzhalter: Sie können eine Pfadkomponente als Platzhalter angeben:

  • Mit Sternchen, *. foo/* entspricht beispielsweise allen untergeordneten Elementen, die sich eine Ebene unter foo/ in der Knotenhierarchie befinden.
  • Verwenden Sie ein Segment, das genau ein Sternchen (*) enthält. Beispiel: foo/app*-us stimmt mit allen untergeordneten Segmenten unter foo/ mit dem Präfix app und dem Suffix -us überein.

Pfade mit Platzhaltern können mit mehreren Ereignissen übereinstimmen, z. B. aus einem einzelnen Schreibvorgang. Eine Einfügung von

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

ergibt zwei Übereinstimmungen mit dem Pfad "/foo/*": einmal "hello": "world" und einmal "firebase": "functions".

Pfaderfassung: Sie können Pfadübereinstimmungen in benannten Variablen erfassen, die in Ihrem Funktionscode verwendet werden (z.B. /user/{uid}, /user/{uid=*-us}).

Die Werte der Erfassungsvariablen sind im Objekt database.DatabaseEvent.params Ihrer Funktion verfügbar.

Instanz-Platzhalter. Sie können eine Instanzkomponente auch mit Platzhaltern angeben. Ein Instanzplatzhalter kann ein Präfix, ein Suffix oder beides haben (z.B. my-app-*-prod).

Referenz zu Platzhaltern und Erfassungen

Bei Cloud Functions (2. Generation) und Realtime Database kann ein Muster verwendet werden, wenn ref und instance angegeben werden. Jede Trigger-Schnittstelle bietet die folgenden Optionen für den Bereich einer Funktion:

ref angeben instance angeben Verhalten
Einzelner (/foo/bar) Keine Angabe Beschränkt den Handler auf alle Instanzen in der Funktionsregion.
Einzelner (/foo/bar) Einzelner (‘my-new-db') Beschränkt den Handler auf die spezifische Instanz in der Funktionsregion.
Einzelner (/foo/bar) Muster (‘inst-prefix*') Beschränkt den Handler auf alle Instanzen, die dem Muster in der Funktionsregion entsprechen.
Muster (/foo/{bar}) Keine Angabe Beschränkt den Handler auf alle Instanzen in der Funktionsregion.
Muster (/foo/{bar}) Einzeln (‘my-new-db') Beschränkt den Handler auf die spezifische Instanz in der Funktionsregion.
Muster (/foo/{bar}) Muster (‘inst-prefix*') Beschränkt den Handler auf alle Instanzen, die dem Muster in der Funktionsregion entsprechen.

Ereignisdaten verarbeiten

Wenn ein Realtime Database-Ereignis ausgelöst wird, wird ein Event-Objekt an Ihre Handler-Funktion übergeben. Dieses Objekt hat das Attribut data, das für Erstellungs- und Löschereignisse einen Snapshot der erstellten oder gelöschten Daten enthält.

In diesem Beispiel ruft die Funktion die Daten für den referenzierten Pfad ab, wandelt den String an dieser Stelle in Großbuchstaben um und schreibt den geänderten String in die Datenbank:

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)

Vorherigen Wert lesen

Bei write- oder update-Ereignissen ist das Attribut data ein Change-Objekt, das zwei Snapshots enthält, die den Datenstatus vor und nach dem auslösenden Ereignis darstellen. Das Objekt Change hat ein Attribut before, mit dem Sie prüfen können, was vor dem Ereignis in Realtime Database gespeichert wurde, und ein Attribut after, das den Status der Daten nach dem Ereignis darstellt.

Mit dem Attribut before können Sie beispielsweise dafür sorgen, dass die Funktion Text nur dann in Großbuchstaben umwandelt, wenn er zum ersten Mal erstellt wird:

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)

Authentifizierungskontext für den Zugriff

Bei Funktionen, die durch RTDB-Eventarc-Ereignisse ausgelöst werden, ist der Authentifizierungskontext in der Ereignisnutzlast enthalten:

  • authtype: Der Typ des Hauptkontos, das das Ereignis ausgelöst hat. Die möglichen Werte sind:
    • app_user: Ein Endnutzer der Anwendung des Entwicklers.
    • admin: Ein Dienstkonto.
    • unauthenticated: Ein nicht authentifizierter Nutzer.
    • unknown: Standardwert, wenn keine Authentifizierungsinformationen verfügbar sind.
  • authid: Die eindeutige Kennung des Hauptkontos.
    • Wenn authtype den Wert app_user hat, ist dies die UID des Nutzers.
    • Wenn authtype den Wert admin hat, ist dies die E-Mail-Adresse des Dienstkontos oder IAM-Nutzers.

Dieser Code wandelt den Text der Nachricht nur dann in Großbuchstaben um, wenn der Nutzer, der die Funktion ausgelöst hat, kein Administrator ist. Außerdem wird geprüft, ob der Nutzer, der die Nachricht ausgelöst hat, der tatsächliche Absender der Nachricht ist.

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)