Firebase 实时数据库触发器

借助 Cloud Run functions,您可以处理与该函数属于同一 Google Cloud 项目的 Firebase Realtime Database 中的事件。Cloud Run functions 让您能以完整的管理权限运行数据库操作,并可确保对数据库所做的每项更改都会单独进行处理。您可以通过 Firebase Admin SDK 更改 Firebase Realtime Database。

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

  1. 等待对特定数据库位置执行的更改。

  2. 在事件发生时触发并执行相应的任务。

  3. 接收相关数据对象,该对象包含了存储在指定文档中的数据的快照。

事件类型

函数可让您以两种不同的侦听范围处理数据库事件;您可以只侦听创建、更新或删除事件,也可以侦听某个路径上任何类型的任何更改。Cloud Run functions 支持实时数据库的以下事件类型:

事件类型 触发器
providers/google.firebase.database/eventTypes/ref.write 当实时数据库中发生任何变更事件(创建、更新或删除数据)时触发。
providers/google.firebase.database/eventTypes/ref.create(默认) 当实时数据库中创建了新数据时触发。
providers/google.firebase.database/eventTypes/ref.update 当实时数据库中更新了数据时触发。
providers/google.firebase.database/eventTypes/ref.delete 当实时数据库中删除了数据时触发。

指定数据库路径和实例

如需控制函数的触发时间和位置,您需要指定路径,并视情况指定数据库实例。

路径

所指定的路径会匹配与之相关的所有写入操作(包括在此路径下任何位置发生的写入操作)。如果您将函数的路径设置为 /foo/bar,以下两个位置发生的事件都会与其匹配:

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

不管是哪种情况,Firebase 都会解读为事件是在 /foo/bar 下发生的,且事件数据包括 /foo/bar 下的旧数据和新数据。如果事件数据可能很大,请考虑在更深的路径下使用多个函数,而不是在数据库根目录附近使用单个函数。为了获得最佳性能,请仅在尽可能深的路径层级中发出数据请求。

您可以用大括号括住路径的某个部分,将其指定为通配符;foo/{bar} 会匹配 /foo 的任何子路径。这些通配符路径组成部分的值可从函数的 event.params 对象中获得。在此示例中,该值通过 event.params.bar 提供。

包含通配符的路径可以匹配单个写入操作引发的多个事件。例如以下数据:

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

该数据的插入操作会匹配路径 /foo/{bar} 两次:第一次匹配 "hello": "world",第二次匹配 "firebase": "functions"

实例

使用 Google Cloud 控制台时,必须指定数据库实例。

使用 Google Cloud CLI 时,必须将实例指定为 --trigger-resource 字符串的一部分。

例如,以下命令会在 --trigger-resource 字符串中使用以下实例:

--trigger-resource projects/_/instances/DATABASE_INSTANCE/refs/PATH

事件结构

处理实时数据库事件时,data 对象包含以 JSON 对象格式提供的两个属性:

  • data:在触发函数的事件之前截取的数据快照。

  • delta:在触发函数的事件之后截取的数据快照。

代码示例

Node.js

/**
 * Background Function triggered by a change to a Firebase RTDB reference.
 *
 * @param {!Object} event The Cloud Functions event.
 * @param {!Object} context The Cloud Functions event context.
 */
exports.helloRTDB = (event, context) => {
  const triggerResource = context.resource;

  console.log(`Function triggered by change to: ${triggerResource}`);
  console.log(`Admin?: ${!!context.auth.admin}`);
  console.log('Delta:');
  console.log(JSON.stringify(event.delta, null, 2));
};

Python

import json

def hello_rtdb(data, context):
    """Triggered by a change to a Firebase RTDB reference.
    Args:
        data (dict): The event payload.
        context (google.cloud.functions.Context): Metadata for the event.
    """
    trigger_resource = context.resource

    print("Function triggered by change to: %s" % trigger_resource)
    print("Admin?: %s" % data.get("admin", False))
    print("Delta:")
    print(json.dumps(data["delta"]))

Go


// Package p contains a Cloud Function triggered by a Firebase Realtime Database
// event.
package p

import (
	"context"
	"fmt"
	"log"

	"cloud.google.com/go/functions/metadata"
)

// RTDBEvent is the payload of a RTDB event.
type RTDBEvent struct {
	Data  interface{} `json:"data"`
	Delta interface{} `json:"delta"`
}

// HelloRTDB handles changes to a Firebase RTDB.
func HelloRTDB(ctx context.Context, e RTDBEvent) error {
	meta, err := metadata.FromContext(ctx)
	if err != nil {
		return fmt.Errorf("metadata.FromContext: %w", err)
	}
	log.Printf("Function triggered by change to: %v", meta.Resource)
	log.Printf("%+v", e)
	return nil
}

Java

import com.google.cloud.functions.Context;
import com.google.cloud.functions.RawBackgroundFunction;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.util.logging.Logger;

public class FirebaseRtdb implements RawBackgroundFunction {
  private static final Logger logger = Logger.getLogger(FirebaseRtdb.class.getName());

  // Use GSON (https://github.com/google/gson) to parse JSON content.
  private static final Gson gson = new Gson();

  @Override
  public void accept(String json, Context context) {
    logger.info("Function triggered by change to: " + context.resource());

    JsonObject body = gson.fromJson(json, JsonObject.class);

    boolean isAdmin = false;
    if (body != null && body.has("auth")) {
      JsonObject authObj = body.getAsJsonObject("auth");
      isAdmin = authObj.has("admin") && authObj.get("admin").getAsBoolean();
    }

    logger.info("Admin?: " + isAdmin);

    if (body != null && body.has("delta")) {
      logger.info("Delta:");
      logger.info(body.get("delta").toString());
    }
  }
}

C#

using CloudNative.CloudEvents;
using Google.Cloud.Functions.Framework;
using Google.Events.Protobuf.Firebase.Database.V1;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;

namespace FirebaseRtdb;

public class Function : ICloudEventFunction<ReferenceEventData>
{
    private readonly ILogger _logger;

    public Function(ILogger<Function> logger) =>
        _logger = logger;

    public Task HandleAsync(CloudEvent cloudEvent, ReferenceEventData data, CancellationToken cancellationToken)
    {
        _logger.LogInformation("Function triggered by change to {subject}", cloudEvent.Subject);
        _logger.LogInformation("Delta: {delta}", data.Delta);

        // In this example, we don't need to perform any asynchronous operations, so the
        // method doesn't need to be declared async.
        return Task.CompletedTask;
    }
}

Ruby

require "functions_framework"

# Triggered by a change to a Firebase RTDB document.
FunctionsFramework.cloud_event "hello_rtdb" do |event|
  # Event-triggered Ruby functions receive a CloudEvents::Event::V1 object.
  # See https://cloudevents.github.io/sdk-ruby/latest/CloudEvents/Event/V1.html
  # The Firebase event payload can be obtained from the `data` field.
  payload = event.data

  logger.info "Function triggered by change to: #{event.source}"
  logger.info "Admin?: #{payload.fetch 'admin', false}"
  logger.info "Delta: #{payload['delta']}"
end

PHP


use Google\CloudFunctions\CloudEvent;

function firebaseRTDB(CloudEvent $cloudevent)
{
    $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb');

    fwrite($log, 'Event: ' . $cloudevent->getId() . PHP_EOL);

    $data = $cloudevent->getData();
    $resource = $data['resource'] ?? '<null>';

    fwrite($log, 'Function triggered by change to: ' . $resource . PHP_EOL);

    $isAdmin = isset($data['auth']['admin']) && $data['auth']['admin'] == true;

    fwrite($log, 'Admin?: ' . var_export($isAdmin, true) . PHP_EOL);
    fwrite($log, 'Delta: ' . json_encode($data['delta'] ?? '') . PHP_EOL);
}

部署函数

以下 gcloud 命令会部署一个将由路径 /messages/{pushId}/original 上的 create 事件触发的函数:

gcloud functions deploy FUNCTION_NAME \
  --no-gen2 \
  --entry-point ENTRY_POINT \
  --trigger-event providers/google.firebase.database/eventTypes/ref.create \
  --trigger-resource projects/_/instances/DATABASE_INSTANCE/refs/messages/{pushId}/original \
  --runtime RUNTIME
参数 说明
FUNCTION_NAME 您要部署的 Cloud Run functions 函数的注册名称。 这可以是源代码中函数的名称,也可以是任意字符串。如果 FUNCTION_NAME 是任意字符串,则必须添加 --entry-point 标志。
--entry-point ENTRY_POINT 源代码中函数或类的名称。可选,除非您未使用 FUNCTION_NAME 指定源代码中要在部署期间执行的函数。在这种情况下,您必须使用 --entry-point 提供可执行函数的名称。
--trigger-event NAME 函数希望接收的事件类型的名称。 在本示例中,该名称将为以下四项之一:write、create、update 或 delete。
--trigger-resource NAME 函数将侦听的完全限定数据库路径。此路径应符合以下格式: projects/_/instances/DATABASE_INSTANCE/refs/PATH
--runtime RUNTIME 您使用的运行时的名称。如需完整列表,请参阅 gcloud 参考文档