С помощью Cloud Functions вы можете обрабатывать события в Firebase Realtime Database, не обновляя код клиента. Cloud Functions позволяет выполнять операции Realtime Database с полными правами администратора и гарантирует, что каждое изменение в Realtime Database обрабатывается отдельно. Вы можете внести Firebase Realtime Database изменений с помощью снимка данных или Admin SDK.
В типичном жизненном цикле функция Firebase Realtime Database выполняет следующие действия:
- Ожидает изменений в определенном пути Realtime Database.
- Срабатывает при возникновении события и выполняет свои задачи.
- Получает объект данных, который содержит снимок данных, хранящихся по этому пути.
Вы можете активировать функцию в ответ на запись, создание, обновление или удаление узлов базы данных в 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, то это адрес электронной почты сервисного аккаунта или пользователя IAM.
- Если для параметра
Этот код преобразует текст сообщения в верхний регистр, только если пользователь, активировавший функцию, не является администратором. Кроме того, код проверяет, является ли пользователь, активировавший сообщение, его фактическим отправителем.
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)