Realtime Database 触发器


借助 Cloud Functions,您无需更新客户端代码即可处理 Firebase Realtime Database 中的事件。Cloud Functions 让您能以完整的管理员权限执行 Realtime Database 操作,并可确保对 Realtime Database 所做的每项更改都得到单独处理。您可以通过数据快照或 Admin SDK 更改 Firebase Realtime Database

在一个典型的生命周期中,Firebase Realtime Database 函数会执行以下操作:

  1. 等待对特定 Realtime Database 路径执行的更改。
  2. 在事件发生时触发并执行相应的任务。
  3. 接收相关数据对象,该对象包含了存储在该路径下的数据的快照。

您可以触发一个函数来响应 Firebase Realtime Database 中的数据库节点的写入、创建、更新或删除操作。如需控制函数何时触发,请指定一个事件处理程序,并指定要监听事件的 Realtime Database 路径。

Realtime Database 实例与函数位置之间的距离可能会造成严重的网络延迟。此外,区域之间的不匹配也可能会导致部署失败。为避免这些情况,请指定函数位置,使其与数据库实例位置相匹配。

处理 Realtime Database 事件

函数可让您以两种不同的监听范围处理 Realtime Database 事件;您可以只监听写入、创建、更新或删除事件,也可以监听某个引用对象上任何类型的任何更改。

响应 Realtime Database 事件可使用以下处理程序:

  • onValueWritten():在 Realtime Database 中创建、更新或删除数据时触发。
  • onValueCreated():仅在 Realtime Database 中创建数据时触发。
  • onValueUpdated():仅在 Realtime Database 中更新数据时触发。
  • onValueDeleted():仅在 Realtime Database 中删除数据时触发。
  • 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

// The Cloud Functions for Firebase SDK to setup triggers and logging.
const {onRequest} = require("firebase-functions/v2/https");
const {onValueCreated} = require("firebase-functions/v2/database");
const {logger} = require("firebase-functions");

// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require("firebase-admin");
admin.initializeApp();
# 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 实例模式,将函数部署到在同一区域中选定的部分实例。

例如:

// 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) => {
    // …
  }
);
# 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

这些参数用于指示您的函数处理在 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(第 2 代)和 Realtime Database 时,您可以在指定 refinstance 时使用模式。每个触发器接口都有以下选项,您可以用它们来限定函数范围:

指定 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 属性,对于创建事件和删除事件,该属性包含创建或删除的数据的快照。

在以下示例中,函数会检索所引用路径的数据,将该位置中的字符串转换为大写,并将修改后的字符串写入数据库:

// 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);
    },
);
@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)

读取先前的值

对于 writeupdate 事件,data 属性是一个 Change 对象,其中包含代表触发事件前后的数据状态的两个快照。Change 对象有一个 before 属性(可用于检查在事件发生之前保存到 Realtime Database 中的内容)和一个 after 属性(表示事件发生之后的数据状态)。

例如,before 属性可用于确保函数仅在第一次创建记录时将文本改为大写:

  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);
      });
@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)