Check out what’s new from Firebase at Google I/O 2022. Learn more

Триггеры базы данных

С помощью облачных функций вы можете обрабатывать события в базе данных Firebase Realtime без необходимости обновления клиентского кода. Облачные функции позволяют выполнять операции с базой данных в реальном времени с полными правами администратора и гарантируют, что каждое изменение в базе данных в реальном времени обрабатывается отдельно. Вы можете вносить изменения в базу данных Firebase Realtime с помощью DataSnapshot или с помощью Admin SDK .

В типичном жизненном цикле функция базы данных Firebase Realtime выполняет следующие действия:

  1. Ожидает изменений в определенном местоположении базы данных реального времени.
  2. Запускается, когда происходит событие, и выполняет свои задачи (примеры использования см. в разделе Что можно делать с облачными функциями? ).
  3. Получает объект данных, содержащий моментальный снимок данных, хранящихся в указанном документе.

Активировать функцию базы данных в реальном времени

Создайте новые функции для событий базы данных реального времени с помощью functions.database . Чтобы управлять запуском функции, укажите один из обработчиков событий и укажите путь к базе данных реального времени, где он будет прослушивать события.

Установить обработчик события

Функции позволяют обрабатывать события базы данных реального времени на двух уровнях специфичности; вы можете прослушивать только события создания, обновления или удаления, или вы можете прослушивать любое изменение пути любого рода. Cloud Functions поддерживает следующие обработчики событий для базы данных реального времени:

  • onWrite() , который срабатывает при создании, обновлении или удалении данных в базе данных реального времени.
  • onCreate() , который срабатывает при создании новых данных в базе данных реального времени.
  • onUpdate() , который срабатывает при обновлении данных в базе данных реального времени.
  • onDelete() , который срабатывает при удалении данных из базы данных реального времени.

Укажите экземпляр и путь

Чтобы контролировать, когда и где должна срабатывать ваша функция, вызовите ref(path) для указания пути и, при необходимости, укажите экземпляр базы данных реального времени с помощью instance('INSTANCE_NAME') . Если вы не укажете экземпляр, функция будет развернута в экземпляр базы данных реального времени по умолчанию для проекта Firebase. Например:

  • Экземпляр базы данных реального времени по умолчанию: functions.database.ref('/foo/bar')
  • Экземпляр с именем «my-app-db-2»: functions.database.instance('my-app-db-2').ref('/foo/bar')

Эти методы направляют вашу функцию для обработки записей по определенному пути в экземпляре базы данных реального времени. Спецификации пути соответствуют всем записям, которые касаются пути, включая записи, которые происходят в любом месте ниже этого пути. Если вы установите путь для своей функции как /foo/bar , он будет соответствовать событиям в обоих этих местах:

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

В любом случае Firebase интерпретирует событие как событие /foo/bar , а данные события включают старые и новые данные в /foo/bar . Если данные события могут быть большими, рассмотрите возможность использования нескольких функций на более глубоких путях вместо одной функции рядом с корнем вашей базы данных. Для наилучшей производительности запрашивайте данные только на самом глубоком возможном уровне.

Вы можете указать компонент пути как подстановочный знак, заключив его в фигурные скобки; ref('foo/{bar}') соответствует любому потомку /foo . Значения этих компонентов пути с подстановочными знаками доступны в объекте EventContext.params вашей функции. В этом примере значение доступно как context.params.bar .

Пути с подстановочными знаками могут соответствовать нескольким событиям из одной записи. Вставка из

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

соответствует пути "/foo/{bar}" дважды: один раз с "hello": "world" и снова с "firebase": "functions" .

Обрабатывать данные о событиях

При обработке события базы данных реального времени возвращаемый объект данных представляет собой DataSnapshot . Для onWrite или onUpdate первым параметром является объект Change , содержащий два моментальных снимка, представляющих состояние данных до и после инициирующего события. Для onCreate и onDelete возвращаемый объект данных представляет собой снимок созданных или удаленных данных.

В этом примере функция извлекает моментальный снимок для указанного пути как snap , преобразует строку в этом месте в верхний регистр и записывает эту измененную строку в базу данных:

// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snapshot, context) => {
      // Grab the current value of what was written to the Realtime Database.
      const original = snapshot.val();
      functions.logger.log('Uppercasing', context.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 snapshot.ref.parent.child('uppercase').set(uppercase);
    });

Доступ к информации об аутентификации пользователя

Из EventContext.auth и EventContext.authType вы можете получить доступ к информации о пользователе, включая разрешения, для пользователя, который активировал функцию. Это может быть полезно для обеспечения соблюдения правил безопасности, позволяя вашей функции выполнять различные операции в зависимости от уровня разрешений пользователя:

const functions = require('firebase-functions');
const admin = require('firebase-admin');

exports.simpleDbFunction = functions.database.ref('/path')
    .onCreate((snap, context) => {
      if (context.authType === 'ADMIN') {
        // do something
      } else if (context.authType === 'USER') {
        console.log(snap.val(), 'written by', context.auth.uid);
      }
    });

Кроме того, вы можете использовать информацию об аутентификации пользователя, чтобы «олицетворять» пользователя и выполнять операции записи от имени пользователя. Обязательно удалите экземпляр приложения, как показано ниже, чтобы предотвратить проблемы параллелизма:

exports.impersonateMakeUpperCase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snap, context) => {
      const appOptions = JSON.parse(process.env.FIREBASE_CONFIG);
      appOptions.databaseAuthVariableOverride = context.auth;
      const app = admin.initializeApp(appOptions, 'app');
      const uppercase = snap.val().toUpperCase();
      const ref = snap.ref.parent.child('uppercase');

      const deleteApp = () => app.delete().catch(() => null);

      return app.database().ref(ref).set(uppercase).then(res => {
        // Deleting the app is necessary for preventing concurrency leaks
        return deleteApp().then(() => res);
      }).catch(err => {
        return deleteApp().then(() => Promise.reject(err));
      });
    });

Чтение предыдущего значения

Объект Change имеет свойство before , которое позволяет проверить, что было сохранено в базе данных реального времени до события. Свойство before возвращает DataSnapshot , в котором все методы (например, val() и exists() ) ссылаются на предыдущее значение. Вы можете снова прочитать новое значение, используя исходный DataSnapshot или прочитав свойство after . Это свойство любого Change является еще одним DataSnapshot данных, представляющим состояние данных после того, как произошло событие.

Например, свойство before можно использовать для того, чтобы при первом создании функция выводила текст только в верхний регистр:

exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onWrite((change, context) => {
      // Only edit data when it is first created.
      if (change.before.exists()) {
        return null;
      }
      // Exit when the data is deleted.
      if (!change.after.exists()) {
        return null;
      }
      // Grab the current value of what was written to the Realtime Database.
      const original = change.after.val();
      console.log('Uppercasing', context.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 change.after.ref.parent.child('uppercase').set(uppercase);
    });