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 w Realtime Database z pełnymi uprawnieniami administracyjnymi i zapewnia, że każda zmiana w Realtime Database jest przetwarzana osobno. Możesz wprowadzać zmiany Firebase Realtime Database za pomocą migawki danych lub za pomocą pakietu Admin SDK.

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

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

Możesz uruchomić funkcję w odpowiedzi na zapisywanie, tworzenie, aktualizowanie lub usuwanie węzłów bazy danych w Firebase Realtime Database. Aby kontrolować, kiedy funkcja ma się uruchamiać, określ jeden z modułów obsługi zdarzeń i określ ścieżkę Realtime Database, w 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 była zgodna z lokalizacją 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, tworzenia, aktualizacji lub usuwania albo nasłuchiwać dowolnych zmian w odniesieniu.

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

Node.js

  • onValueWritten() Uruchamia się, gdy dane są tworzone, aktualizowane lub usuwane w Realtime Database.
  • onValueCreated() Uruchamia się tylko wtedy, gdy dane są tworzone w Realtime Database.
  • onValueUpdated() Uruchamia się tylko wtedy, gdy dane są aktualizowane w Realtime Database.
  • onValueDeleted() Uruchamia się tylko wtedy, gdy dane są usuwane w Realtime Database.

Python

  • on_value_written() Uruchamia się, gdy dane są tworzone, aktualizowane lub usuwane w Realtime Database.
  • on_value_created() Uruchamia się tylko wtedy, gdy dane są tworzone w Realtime Database.
  • on_value_updated() Uruchamia się tylko wtedy, gdy dane są aktualizowane w Realtime Database.
  • on_value_deleted() Uruchamia się 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 wraz z modułem Firebase Admin SDK do zapisywania 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ślanie instancji i ścieżki

Aby kontrolować, kiedy i gdzie funkcja ma się uruchamiać, skonfiguruj ją za pomocą ścieżki i opcjonalnie instancji 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 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

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

Specyfikacje ścieżek pasują do wszystkich zapisów, które dotyczą ścieżki, w tym zapisów, które mają miejsce w dowolnym miejscu poniżej niej. 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 w /foo/bar, a dane zdarzenia zawierają stare i nowe dane w /foo/bar. Jeśli dane zdarzenia mogą być duże, rozważ użycie kilku funkcji w 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.

Symbole wieloznaczne i przechwytywanie

Do przechwytywania możesz używać {key}, {key=*}, {key=prefix*}, {key=*suffix}. *, prefix*, *suffix do symboli wieloznacznych w jednym segmencie. Uwaga: ** reprezentuje symbole wieloznaczne w wielu segmentach, które Realtime Database nie są obsługiwane. Zapoznaj się z artykułem Omówienie wzorców ś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 na 1 poziomie hierarchii węzłów poniżej foo/.
  • Używanie segmentu zawierającego dokładnie gwiazdkę *. Na przykład foo/app*-us pasuje do wszystkich segmentów podrzędnych poniżej foo/ z app prefiksem i -us sufiksem.

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

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

pasuje do ścieżki "/foo/*" 2 razy: raz z "hello": "world" i ponownie 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.

Symbole wieloznaczne w instancji. Możesz też określić komponent instancji za pomocą symboli wieloznacznych. Symbol wieloznaczny instancji może mieć prefiks, sufiks lub oba (np. my-app-*-prod).

Odwołanie do symboli wieloznacznych i przechwytywania

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

Określanie ref Określanie instance Zachowanie
Pojedynczy (/foo/bar) Nieokreślony Ogranicza moduł obsługi do wszystkich instancji w regionie funkcji.
Pojedynczy (/foo/bar) Pojedynczy (‘my-new-db') Ogranicza moduł obsługi do określonej instancji w regionie funkcji.
Pojedynczy (/foo/bar) Wzorzec (‘inst-prefix*') Ogranicza moduł obsługi do wszystkich instancji pasujących do wzorca w regionie funkcji.
Wzorzec (/foo/{bar}) Nieokreślony Ogranicza moduł obsługi do wszystkich instancji w regionie funkcji.
Wzorzec (/foo/{bar}) Pojedynczy (‘my-new-db') Ogranicza moduł obsługi do określonej instancji w regionie funkcji.
Wzorzec (/foo/{bar}) Wzorzec (‘inst-prefix*') Ogranicza moduł obsługi do wszystkich instancji pasujących do wzorca w regionie funkcji.

Obsługa danych zdarzenia

Gdy uruchomi się zdarzenie Realtime Database, przekazuje ono do funkcji obsługi obiekt Event. Ten obiekt ma właściwość data, która w przypadku zdarzeń tworzenia i usuwania zawiera migawkę utworzonych lub usuniętych danych.

W tym przykładzie funkcja pobiera dane dla ścieżki, do której się odwołuje, konwertuje ciąg znaków w tej lokalizacji na wielkie litery i zapisuje zmodyfikowany ciąg znaków 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)

Odczytywanie poprzedniej wartości

W przypadku zdarzeń write lub update właściwość data jest obiektem Change, który zawiera 2 migawki reprezentujące stan danych przed i po zdarzeniu wyzwalającym. Obiekt Change ma właściwość before, która umożliwia sprawdzenie, 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ść before może służyć do sprawdzania, czy funkcja zmienia tekst na wielkie litery tylko wtedy, gdy jest on tworzony po raz pierwszy:

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)

Uzyskiwanie dostępu do kontekstu uwierzytelniania

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

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

Ten kod przekształca tekst wiadomości na wielkie litery tylko wtedy, gdy użytkownik, który uruchomił funkcję, nie jest administratorem. Dodatkowo kod sprawdza też, czy użytkownik, który uruchomił 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)