Aktywatory bazy danych czasu rzeczywistego

Dzięki Cloud Functions możesz obsługiwać zdarzenia w Firebase Realtime Database bez konieczności aktualizowania kodu klienta. Cloud Functions umożliwia wykonywanie operacji Realtime Database z pełnymi uprawnieniami administracyjnymi i zapewnia, że każda zmiana w Realtime Database jest przetwarzana indywidualnie. Możesz wprowadzić zmiany w Firebase Realtime Database za pomocą podsumowania danych lub pakietu Admin SDK.

W typowym cyklu życia funkcja Firebase Realtime Database wykonuje te czynności:

  1. Czeka na zmiany w określonej ścieżce Realtime Database.
  2. Uruchamia się, gdy wystąpi zdarzenie, i wykonuje swoje zadania.
  3. Otrzymuje obiekt danych, który zawiera zrzut danych przechowywanych w tym magazynie danych.

Możesz aktywować funkcję w odpowiedzi na zapisywanie, tworzenie, aktualizowanie lub usuwanie węzłów bazy danych w Firebase Realtime Database. Aby kontrolować, kiedy funkcja jest aktywowana, określ jeden z modułów obsługi zdarzeń i ścieżkę Realtime Database, na której będzie nasłuchiwać zdarzeń.

Ustawianie lokalizacji funkcji

Odległość między lokalizacją instancji Realtime Database a lokalizacją funkcji może powodować znaczne opóźnienie sieci. Niezgodność regionów może też spowodować niepowodzenie wdrożenia. Aby uniknąć takich sytuacji, określ lokalizację funkcji tak, aby odpowiadała lokalizacji instancji bazy danych.

Obsługa zdarzeń Realtime Database

Funkcje umożliwiają obsługę zdarzeń Realtime Database na 2 poziomach szczegółowości. Możesz nasłuchiwać tylko zdarzeń zapisu, utworzenia, aktualizacji lub usunięcia albo dowolnych zmian w odwołaniu.

Dostępne są te moduły obsługi zdarzeń Realtime Database:

Node.js

  • onValueWritten() Wywoływane, gdy dane są tworzone, aktualizowane lub usuwane w Realtime Database.
  • onValueCreated() Wywoływane tylko wtedy, gdy dane są tworzone w Realtime Database.
  • onValueUpdated() – aktywowane tylko wtedy, gdy dane są aktualizowane w Realtime Database.
  • onValueDeleted() – uruchamiane tylko wtedy, gdy dane są usuwane w Realtime Database.

Python

  • on_value_written() Aktywowane, gdy dane są tworzone, aktualizowane lub usuwane w Realtime Database.
  • on_value_created() Wywoływane tylko wtedy, gdy dane są tworzone w Realtime Database.
  • on_value_updated() – aktywowane tylko wtedy, gdy dane są aktualizowane w Realtime Database.
  • on_value_deleted() Wywoływane tylko wtedy, gdy dane są usuwane w Realtime Database.

Importowanie wymaganych modułów

W źródle funkcji musisz zaimportować moduły pakietu SDK, których chcesz używać. W tym przykładzie konieczne jest zaimportowanie modułów HTTP i Realtime Database oraz modułu Firebase Admin SDK do zapisu w 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()

Określ instancję i ścieżkę

Aby kontrolować, kiedy i gdzie funkcja ma się aktywować, skonfiguruj ją, podając ścieżkę i opcjonalnie instancję Realtime Database. Jeśli nie określisz instancji, funkcja będzie nasłuchiwać wszystkich instancji Realtime Database w regionie funkcji. Możesz też określić wzorzec instancji Realtime Database, aby wdrożyć go w wybranym podzbiorze instancji w tym samym regionie.

Przykład:

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

Te parametry kierują funkcję do obsługi zapisów w określonej ścieżce w instancji Realtime Database.

Specyfikacje ścieżki pasują do wszystkich zapisów, które dotyczą ścieżki, w tym zapisów, które występują w dowolnym miejscu poniżej. Jeśli ustawisz ścieżkę funkcji jako /foo/bar, będzie ona pasować do zdarzeń w obu tych lokalizacjach:

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

W obu przypadkach Firebase interpretuje, że zdarzenie występuje o /foo/bar, a dane zdarzenia obejmują stare i nowe dane z /foo/bar. Jeśli dane zdarzenia mogą być duże, rozważ użycie wielu funkcji na głębszych ścieżkach zamiast jednej funkcji w pobliżu katalogu głównego bazy danych. Aby uzyskać najlepszą wydajność, żądaj danych tylko na najgłębszym możliwym poziomie.

Symbol wieloznaczny i przechwytywanie

Do przechwytywania możesz używać {key}, {key=*}, {key=prefix*}, {key=*suffix}. *, prefix*, *suffix w przypadku symbolu wieloznacznego dla pojedynczego segmentu. Uwaga: ** reprezentuje wielosegmentowy symbol wieloznaczny, którego Realtime Database nie obsługuje. Więcej informacji znajdziesz w sekcji Wzorce ścieżek.

Symbole wieloznaczne w ścieżce. Możesz określić komponent ścieżki jako symbol wieloznaczny:

  • Używanie gwiazdki, *. Na przykład foo/* pasuje do wszystkich elementów podrzędnych o 1 poziom niżej w hierarchii węzłów niż foo/.
  • Używasz segmentu zawierającego tylko gwiazdkę: *. Na przykład foo/app*-us pasuje do wszystkich segmentów podrzędnych poniżej foo/ z prefiksem app i sufiksem -us.

Ścieżki z symbolami wieloznacznymi mogą pasować do wielu zdarzeń, np. z jednego zapisu. Wstawienie

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

pasuje do ścieżki "/foo/*" dwukrotnie: raz z "hello": "world" i raz z "firebase": "functions".

Przechwytywanie ścieżki. Możesz przechwytywać dopasowania ścieżek do nazwanych zmiennych, które będą używane w kodzie funkcji (np. /user/{uid}, /user/{uid=*-us}).

Wartości zmiennych przechwytywania są dostępne w obiekcie database.DatabaseEvent.params funkcji.

Używanie symboli wieloznacznych w instancjach. Możesz też określić komponent instancji, używając symboli wieloznacznych. Symbol wieloznaczny instancji może mieć prefiks, sufiks lub oba te elementy (np. my-app-*-prod).

Symbole wieloznaczne i odwołania do przechwyconych fragmentów

W przypadku Cloud Functions (2 generacji) i Realtime Database można użyć wzorca podczas określania ref i instance. Każdy interfejs reguły będzie miał te opcje określania zakresu funkcji:

Określanie ref Określanie instance Zachowanie
Singiel (/foo/bar) Nie określono Obsługuje wszystkie instancje w regionie funkcji.
Singiel (/foo/bar) Singiel (‘my-new-db') Obsługa zakresów w konkretnej instancji w regionie funkcji.
Singiel (/foo/bar) Wzorzec (‘inst-prefix*') Ogranicza zakres modułu obsługi do wszystkich instancji, które pasują do wzorca w regionie funkcji.
Wzorzec (/foo/{bar}) Nie określono Obsługuje wszystkie instancje w regionie funkcji.
Wzorzec (/foo/{bar}) Singiel (‘my-new-db') Obsługa zakresów w konkretnej instancji w regionie funkcji.
Wzorzec (/foo/{bar}) Wzorzec (‘inst-prefix*') Ogranicza zakres modułu obsługi do wszystkich instancji, które pasują do wzorca w regionie funkcji.

Obsługa danych zdarzeń

Gdy wywoływane jest zdarzenie Realtime Database, do funkcji obsługi przekazywany jest obiekt Event. Ten obiekt ma właściwość data, która w przypadku zdarzeń tworzenia i usuwania zawiera zrzut utworzonych lub usuniętych danych.

W tym przykładzie funkcja pobiera dane ze wskazanej ścieżki, konwertuje ciąg znaków w tej lokalizacji na wielkie litery i zapisuje zmodyfikowany ciąg w bazie danych:

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)

Odczytaj poprzednią wartość

W przypadku zdarzeń write lub update właściwość data jest obiektem Change, który zawiera 2 zrzuty reprezentujące stan danych przed zdarzeniem wywołującym i po nim. Obiekt Change ma właściwość before, która pozwala sprawdzić, co zostało zapisane w Realtime Database przed zdarzeniem, oraz właściwość after, która reprezentuje stan danych po wystąpieniu zdarzenia.

Na przykład właściwości before można użyć, aby funkcja zamieniała tekst na wielkie litery tylko podczas jego tworzenia:

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)

Kontekst uwierzytelniania dostępu

W przypadku funkcji aktywowanych przez zdarzenia Eventarc RTDB kontekst uwierzytelniania jest uwzględniany w ładunku zdarzenia:

  • authtype: typ podmiotu zabezpieczeń, który wywołał zdarzenie. Możliwe wartości to:
    • app_user: użytkownik aplikacji dewelopera.
    • admin: konto usługi.
    • unauthenticated: nieuwierzytelniony użytkownik.
    • unknown: domyślna, gdy informacje uwierzytelniające są niedostępne.
  • authid: unikalny identyfikator podmiotu zabezpieczeń.
    • Jeśli authtype to app_user, jest to identyfikator UID użytkownika.
    • Jeśli authtype to admin, jest to konto usługi lub adres e-mail użytkownika uprawnień.

Ten kod przekształca tekst wiadomości na wielkie litery tylko wtedy, gdy użytkownik, który wywołał funkcję, nie jest administratorem. Dodatkowo kod sprawdza, czy użytkownik, który wywołał wiadomość, jest jej rzeczywistym nadawcą.

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)