С помощью 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)