Firestore 触发器(第 2 代)

您可以配置 Cloud Functions 函数,使其由 Firestore 数据库中的事件触发。触发后,您的函数可以通过 Firestore API 和客户端库读取和更新 Firestore 数据库,以便对这些事件做出响应。

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

  1. 等待特定文档发生更改。

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

  3. 接收包含受影响文档快照的数据对象。对于 writeupdate 事件,该数据对象包含代表触发事件前后的文档状态的快照。

事件类型

Firestore 支持 createupdatedeletewrite 事件。write 事件包含对某个文档所做的所有修改。

事件类型 触发器
google.cloud.firestore.document.v1.created(默认) 首次写入某个文档时触发。
google.cloud.firestore.document.v1.updated 当某文档已存在并且其任何值发生了更改时触发。
google.cloud.firestore.document.v1.deleted 当文档的数据被删除时触发。
google.cloud.firestore.document.v1.written 在创建、更新或删除某个文档时触发。

在触发器中,通配符是使用大括号编写的,如下所示: "projects/YOUR_PROJECT_ID/databases/(default)/documents/collection/{document_wildcard}"

指定文档路径

如需触发您的函数,请指定要侦听的文档路径。文档路径必须与函数位于同一 Google Cloud 项目中。

以下是一些有效和无效的文档路径示例:

  • users/marie:有效的触发器。监控单个文档 (/users/marie)。

  • users/{username}:有效的触发器。监控所有用户文档。通配符用于监控集合中的所有文档。

  • users/{username}/addresses无效的触发器。该路径指向的是子集合 addresses,而不是某个文档。

  • users/{username}/addresses/home:有效的触发器。监控所有用户的家庭住址文档。

  • users/{username}/addresses/{addressId}:有效的触发器。监控所有地址文档。

  • users/{user=**}:有效的触发器。监控所有用户文档以及每个用户文档下子集合中的任何文档,例如 /users/userID/address/home/users/userID/phone/work

通配符和参数

如果您不知道要监控的特定文档,请使用 {wildcard} 代替文档 ID:

  • users/{username} 侦听所有用户文档的更改。

在此示例中,如果 users 中的任何文档的任何字段发生更改,都会与一个名为 {username} 的通配符相匹配。

如果 users 中的某个文档有多个子集合,并且这些子集合中一个文档内的某字段发生了更改,那么不会触发 {username} 通配符。 如果您的目标是同时也响应子集合中的事件,请使用多段通配符 {username=**}

通配符匹配项会从文档路径中提取出来。 您可以定义任意多个通配符,以替代明确指定的集合或文档 ID。最多可以使用一个多段通配符,例如 {username=**}

事件结构

当发生如下所示的事件时,此触发器会调用您的函数:

{
    "oldValue": { // Update and Delete operations only
        A Document object containing a pre-operation document snapshot
    },
    "updateMask": { // Update operations only
        A DocumentMask object that lists changed fields.
    },
    "value": {
        // A Document object containing a post-operation document snapshot
    }
}

每个 Document 对象都包含一个或多个 Value 对象。如需查看类型参考信息,请参阅 Value 文档。如果您使用类型化语言(例如 Go)编写函数,此方法特别有用。

设置您的 Firestore 数据库

您需要一个 Firestore 数据库来测试本文档中的示例。您必须先设置好 Cloud Firestore 数据库,然后再部署函数。如果您还没有 Firestore 数据库,请按如下方式创建一个:

  1. 进入 Firestore 数据页面

  2. 点击选择原生模式

  3. 选择数据库位于的区域(位置)。一旦选择便无法更改。

  4. 点击创建数据库

Firestore 数据模型由包含文档的集合组成。每个文档包含一组键值对。

当您对指定集合内的文档进行更改时,会触发您在本教程中创建的函数。

示例 1:Hello Firestore 函数

以下示例 Cloud Functions 函数会输出触发 Firestore 事件的字段:

Node.js

使用 protobufjs 解码事件数据。在源代码中添加 google.events.cloud.firestore.v1 data.proto

/**
 * Cloud Event Function triggered by a change to a Firestore document.
 */
const functions = require('@google-cloud/functions-framework');
const protobuf = require('protobufjs');

functions.cloudEvent('helloFirestore', async cloudEvent => {
  console.log(`Function triggered by event on: ${cloudEvent.source}`);
  console.log(`Event type: ${cloudEvent.type}`);

  console.log('Loading protos...');
  const root = await protobuf.load('data.proto');
  const DocumentEventData = root.lookupType(
    'google.events.cloud.firestore.v1.DocumentEventData'
  );

  console.log('Decoding data...');
  const firestoreReceived = DocumentEventData.decode(cloudEvent.data);

  console.log('\nOld value:');
  console.log(JSON.stringify(firestoreReceived.oldValue, null, 2));

  console.log('\nNew value:');
  console.log(JSON.stringify(firestoreReceived.value, null, 2));
});

Python

from cloudevents.http import CloudEvent
import functions_framework
from google.events.cloud import firestore


@functions_framework.cloud_event
def hello_firestore(cloud_event: CloudEvent) -> None:
    """Triggers by a change to a Firestore document.

    Args:
        cloud_event: cloud event with information on the firestore event trigger
    """
    firestore_payload = firestore.DocumentEventData()
    firestore_payload._pb.ParseFromString(cloud_event.data)

    print(f"Function triggered by change to: {cloud_event['source']}")

    print("\nOld value:")
    print(firestore_payload.old_value)

    print("\nNew value:")
    print(firestore_payload.value)

Go


// Package hellofirestore contains a Cloud Event Function triggered by a Cloud Firestore event.
package hellofirestore

import (
	"context"
	"fmt"

	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
	"github.com/cloudevents/sdk-go/v2/event"
	"github.com/googleapis/google-cloudevents-go/cloud/firestoredata"
	"google.golang.org/protobuf/proto"
)

func init() {
	functions.CloudEvent("helloFirestore", HelloFirestore)
}

// HelloFirestore is triggered by a change to a Firestore document.
func HelloFirestore(ctx context.Context, event event.Event) error {
	var data firestoredata.DocumentEventData
	if err := proto.Unmarshal(event.Data(), &data); err != nil {
		return fmt.Errorf("proto.Unmarshal: %w", err)
	}

	fmt.Printf("Function triggered by change to: %v\n", event.Source())
	fmt.Printf("Old value: %+v\n", data.GetOldValue())
	fmt.Printf("New value: %+v\n", data.GetValue())
	return nil
}

Java

import com.google.cloud.functions.CloudEventsFunction;
import com.google.events.cloud.firestore.v1.DocumentEventData;
import com.google.protobuf.InvalidProtocolBufferException;
import io.cloudevents.CloudEvent;
import java.util.logging.Logger;

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

  @Override
  public void accept(CloudEvent event) throws InvalidProtocolBufferException {
    DocumentEventData firestorEventData = DocumentEventData.parseFrom(event.getData().toBytes());

    logger.info("Function triggered by event on: " + event.getSource());
    logger.info("Event type: " + event.getType());

    logger.info("Old value:");
    logger.info(firestorEventData.getOldValue().toString());

    logger.info("New value:");
    logger.info(firestorEventData.getValue().toString());
  }
}

C#

using CloudNative.CloudEvents;
using Google.Cloud.Functions.Framework;
using Google.Events.Protobuf.Cloud.Firestore.V1;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace FirebaseFirestore;

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

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

    public Task HandleAsync(CloudEvent cloudEvent, DocumentEventData data, CancellationToken cancellationToken)
    {
        _logger.LogInformation("Function triggered by event on {subject}", cloudEvent.Subject);
        _logger.LogInformation("Event type: {type}", cloudEvent.Type);
        MaybeLogDocument("Old value", data.OldValue);
        MaybeLogDocument("New value", data.Value);

        // 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;
    }

    /// <summary>
    /// Logs the names and values of the fields in a document in a very simplistic way.
    /// </summary>
    private void MaybeLogDocument(string message, Document document)
    {
        if (document is null)
        {
            return;
        }

        // ConvertFields converts the Firestore representation into a .NET-friendly
        // representation.
        IReadOnlyDictionary<string, object> fields = document.ConvertFields();
        var fieldNamesAndTypes = fields
            .OrderBy(pair => pair.Key)
            .Select(pair => $"{pair.Key}: {pair.Value}");
        _logger.LogInformation(message + ": {fields}", string.Join(", ", fieldNamesAndTypes));
    }
}

部署 Hello Firestore 函数

  1. 设置 Firestore 数据库(如果您尚未这样做)。

  2. 如需使用 Firestore 触发器部署 Hello Firestore 函数,请在包含示例代码(如果是 Java,则为 pom.xml 文件)的目录中运行以下命令:

    gcloud functions deploy FUNCTION_NAME \
    --gen2 \
    --runtime=RUNTIME \
    --region=REGION \
    --trigger-location=TRIGGER REGION \
    --source=. \
    --entry-point=ENTRY_POINT \
    --trigger-event-filters=type=google.cloud.firestore.document.v1.written \
    --trigger-event-filters=database='(default)' \
    --trigger-event-filters-path-pattern=document='users/{username}'
    

    替换以下内容:

    • FUNCTION_NAME:已部署函数的名称。
    • RUNTIME:您的函数使用的语言运行时。
    • REGION:要在其中部署函数的区域。
    • TRIGGER_REGION:触发器的位置,必须与 Firestore 数据库的区域相同。
    • ENTRY_POINT:源代码中函数的入口点。这是在您的函数运行时执行的代码。

    按原样使用其他字段:

    • --trigger-event-filters=type=google.cloud.firestore.document.v1.written 指定根据 google.cloud.firestore.document.v1.written 事件类型创建、更新或删除文档时触发函数。
    • --trigger-event-filters=database='(default)' 指定 Firebase 数据库。对于默认数据库名称,请使用 (default)
    • --trigger-event-filters-path-pattern=document='users/{username}' 提供受监控文档的路径模式,应监控这些文档是否发生相关改变。此路径模式声明 users 集合中的所有文档都应被监控。如需了解详情,请参阅了解路径模式

测试 Hello Firestore 函数

如需测试 Hello Firestore 函数,请在 Firestore 数据库中设置一个名为 users 的集合:

  1. “Firestore 数据”页面上,点击开始使用集合

  2. 指定 users 作为集合 ID。

  3. 如需开始添加集合的第一个文档,请在为其添加首个文档下接受自动生成的文档 ID

  4. 为文档至少添加一个字段,并指定名称和值。在此示例中,名称为“username”,值为“rowan”:

    展示如何创建 Firestore 集合的屏幕截图

  5. 完成之后,点击保存

    此操作会创建一个新文档,从而触发您的函数。

  6. 如需确认您的函数已触发,请在 Google Cloud 控制台 Cloud Functions 概览页面中点击该函数的链接名称,以打开函数详情页面。

  7. 打开日志标签页,然后查找此字符串:

Function triggered by change to: //firestore.googleapis.com/projects/your-project-id/databases/(default)'

示例 2:“转换为大写”函数

以下示例会检索用户添加的值,将该位置的字符串转换为大写,然后将该值替换为该大写字符串:

Node.js

使用 protobufjs 解码事件数据。在源代码中添加 google.events.cloud.firestore.v1 data.proto

const functions = require('@google-cloud/functions-framework');
const Firestore = require('@google-cloud/firestore');
const protobuf = require('protobufjs');

const firestore = new Firestore({
  projectId: process.env.GOOGLE_CLOUD_PROJECT,
});

// Converts strings added to /messages/{pushId}/original to uppercase
functions.cloudEvent('makeUpperCase', async cloudEvent => {
  console.log('Loading protos...');
  const root = await protobuf.load('data.proto');
  const DocumentEventData = root.lookupType(
    'google.events.cloud.firestore.v1.DocumentEventData'
  );

  console.log('Decoding data...');
  const firestoreReceived = DocumentEventData.decode(cloudEvent.data);

  const resource = firestoreReceived.value.name;
  const affectedDoc = firestore.doc(resource.split('/documents/')[1]);

  const curValue = firestoreReceived.value.fields.original.stringValue;
  const newValue = curValue.toUpperCase();

  if (curValue === newValue) {
    // Value is already upper-case
    // Don't perform a(nother) write to avoid infinite loops
    console.log('Value is already upper-case.');
    return;
  }

  console.log(`Replacing value: ${curValue} --> ${newValue}`);
  affectedDoc.set({
    original: newValue,
  });
});

Python

from cloudevents.http import CloudEvent
import functions_framework
from google.cloud import firestore
from google.events.cloud import firestore as firestoredata

client = firestore.Client()


# Converts strings added to /messages/{pushId}/original to uppercase
@functions_framework.cloud_event
def make_upper_case(cloud_event: CloudEvent) -> None:
    firestore_payload = firestoredata.DocumentEventData()
    firestore_payload._pb.ParseFromString(cloud_event.data)

    path_parts = firestore_payload.value.name.split("/")
    separator_idx = path_parts.index("documents")
    collection_path = path_parts[separator_idx + 1]
    document_path = "/".join(path_parts[(separator_idx + 2) :])

    print(f"Collection path: {collection_path}")
    print(f"Document path: {document_path}")

    affected_doc = client.collection(collection_path).document(document_path)

    cur_value = firestore_payload.value.fields["original"].string_value
    new_value = cur_value.upper()

    if cur_value != new_value:
        print(f"Replacing value: {cur_value} --> {new_value}")
        affected_doc.set({"original": new_value})
    else:
        # Value is already upper-case
        # Don't perform a second write (which can trigger an infinite loop)
        print("Value is already upper-case.")

Go


// Package upper contains a Firestore Cloud Function.
package upper

import (
	"context"
	"errors"
	"fmt"
	"log"
	"os"
	"strings"

	"cloud.google.com/go/firestore"
	firebase "firebase.google.com/go/v4"
	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
	"github.com/cloudevents/sdk-go/v2/event"
	"github.com/googleapis/google-cloudevents-go/cloud/firestoredata"
	"google.golang.org/protobuf/proto"
)

// set the GOOGLE_CLOUD_PROJECT environment variable when deploying.
var projectID = os.Getenv("GOOGLE_CLOUD_PROJECT")

// client is a Firestore client, reused between function invocations.
var client *firestore.Client

func init() {
	// Use the application default credentials.
	conf := &firebase.Config{ProjectID: projectID}

	// Use context.Background() because the app/client should persist across
	// invocations.
	ctx := context.Background()

	app, err := firebase.NewApp(ctx, conf)
	if err != nil {
		log.Fatalf("firebase.NewApp: %v", err)
	}

	client, err = app.Firestore(ctx)
	if err != nil {
		log.Fatalf("app.Firestore: %v", err)
	}

	// Register cloud event function
	functions.CloudEvent("MakeUpperCase", MakeUpperCase)
}

// MakeUpperCase is triggered by a change to a Firestore document. It updates
// the `original` value of the document to upper case.
func MakeUpperCase(ctx context.Context, e event.Event) error {
	var data firestoredata.DocumentEventData
	if err := proto.Unmarshal(e.Data(), &data); err != nil {
		return fmt.Errorf("proto.Unmarshal: %w", err)
	}

	if data.GetValue() == nil {
		return errors.New("Invalid message: 'Value' not present")
	}

	fullPath := strings.Split(data.GetValue().GetName(), "/documents/")[1]
	pathParts := strings.Split(fullPath, "/")
	collection := pathParts[0]
	doc := strings.Join(pathParts[1:], "/")

	var originalStringValue string
	if v, ok := data.GetValue().GetFields()["original"]; ok {
		originalStringValue = v.GetStringValue()
	} else {
		return errors.New("Document did not contain field \"original\"")
	}

	newValue := strings.ToUpper(originalStringValue)
	if originalStringValue == newValue {
		log.Printf("%q is already upper case: skipping", originalStringValue)
		return nil
	}
	log.Printf("Replacing value: %q -> %q", originalStringValue, newValue)

	newDocumentEntry := map[string]string{"original": newValue}
	_, err := client.Collection(collection).Doc(doc).Set(ctx, newDocumentEntry)
	if err != nil {
		return fmt.Errorf("Set: %w", err)
	}
	return nil
}

Java

import com.google.cloud.firestore.Firestore;
import com.google.cloud.firestore.FirestoreOptions;
import com.google.cloud.firestore.SetOptions;
import com.google.cloud.functions.CloudEventsFunction;
import com.google.events.cloud.firestore.v1.DocumentEventData;
import com.google.events.cloud.firestore.v1.Value;
import com.google.protobuf.InvalidProtocolBufferException;
import io.cloudevents.CloudEvent;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;

public class FirebaseFirestoreReactive implements CloudEventsFunction {
  private static final Logger logger = Logger.getLogger(FirebaseFirestoreReactive.class.getName());
  private final Firestore firestore;

  private static final String FIELD_KEY = "original";
  private static final String APPLICATION_PROTOBUF = "application/protobuf";

  public FirebaseFirestoreReactive() {
    this(FirestoreOptions.getDefaultInstance().getService());
  }

  public FirebaseFirestoreReactive(Firestore firestore) {
    this.firestore = firestore;
  }

  @Override
  public void accept(CloudEvent event)
      throws InvalidProtocolBufferException, InterruptedException, ExecutionException {
    if (event.getData() == null) {
      logger.warning("No data found in event!");
      return;
    }

    if (!event.getDataContentType().equals(APPLICATION_PROTOBUF)) {
      logger.warning(String.format("Found unexpected content type %s, expected %s",
          event.getDataContentType(),
          APPLICATION_PROTOBUF));
      return;
    }

    DocumentEventData firestoreEventData = DocumentEventData
        .parseFrom(event.getData().toBytes());

    // Get the fields from the post-operation document snapshot
    // https://firebase.google.com/docs/firestore/reference/rest/v1/projects.databases.documents#Document
    Map<String, Value> fields = firestoreEventData.getValue().getFieldsMap();
    if (!fields.containsKey(FIELD_KEY)) {
      logger.warning("Document does not contain original field");
      return;
    }
    String currValue = fields.get(FIELD_KEY).getStringValue();
    String newValue = currValue.toUpperCase();

    if (currValue.equals(newValue)) {
      logger.info("Value is already upper-case");
      return;
    }

    // Retrieve the document name from the resource path:
    // projects/{project_id}/databases/{database_id}/documents/{document_path}
    String affectedDoc = firestoreEventData.getValue()
        .getName()
        .split("/documents/")[1]
        .replace("\"", "");

    logger.info(String.format("Replacing values: %s --> %s", currValue, newValue));

    // Wait for the async call to complete
    this.firestore
        .document(affectedDoc)
        .set(Map.of(FIELD_KEY, newValue), SetOptions.merge())
        .get();
  }
}

C#

using CloudNative.CloudEvents;
using Google.Cloud.Firestore;
using Google.Cloud.Functions.Framework;
using Google.Cloud.Functions.Hosting;
using Google.Events.Protobuf.Cloud.Firestore.V1;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace FirestoreReactive;

public class Startup : FunctionsStartup
{
    public override void ConfigureServices(WebHostBuilderContext context, IServiceCollection services) =>
        services.AddSingleton(FirestoreDb.Create());
}

// Register the startup class to provide the Firestore dependency.
[FunctionsStartup(typeof(Startup))]
public class Function : ICloudEventFunction<DocumentEventData>
{
    private readonly ILogger _logger;
    private readonly FirestoreDb _firestoreDb;

    public Function(ILogger<Function> logger, FirestoreDb firestoreDb) =>
        (_logger, _firestoreDb) = (logger, firestoreDb);

    public async Task HandleAsync(CloudEvent cloudEvent, DocumentEventData data, CancellationToken cancellationToken)
    {
        // Get the recently-written value. This expression will result in a null value
        // if any of the following is true:
        // - The event doesn't contain a "new" document
        // - The value doesn't contain a field called "original"
        // - The "original" field isn't a string
        string currentValue = data.Value?.ConvertFields().GetValueOrDefault("original") as string;
        if (currentValue is null)
        {
            _logger.LogWarning($"Event did not contain a suitable document");
            return;
        }

        string newValue = currentValue.ToUpperInvariant();
        if (newValue == currentValue)
        {
            _logger.LogInformation("Value is already upper-cased; no replacement necessary");
            return;
        }

        // The CloudEvent subject is "documents/x/y/...".
        // The Firestore SDK FirestoreDb.Document method expects a reference relative to
        // "documents" (so just the "x/y/..." part). This may be simplified over time.
        if (cloudEvent.Subject is null || !cloudEvent.Subject.StartsWith("documents/"))
        {
            _logger.LogWarning("CloudEvent subject is not a document reference.");
            return;
        }
        string documentPath = cloudEvent.Subject.Substring("documents/".Length);

        _logger.LogInformation("Replacing '{current}' with '{new}' in '{path}'", currentValue, newValue, documentPath);
        await _firestoreDb.Document(documentPath).UpdateAsync("original", newValue, cancellationToken: cancellationToken);
    }
}

部署“转换为大写”函数

  1. 设置 Firestore 数据库(如果您尚未这样做)。

  2. 使用以下命令部署由文档 companies/{CompanyId} 上的写入事件触发的函数:

    gcloud functions deploy FUNCTION_NAME \
    --gen2 \
    --runtime=RUNTIME \
    --trigger-location=TRIGGER REGION \
    --region=REGION \
    --source=. \
    --entry-point=ENTRY_POINT \
    --trigger-event-filters=type=google.cloud.firestore.document.v1.written \
    --trigger-event-filters=database='(default)' \
    --trigger-event-filters-path-pattern=document='messages/{pushId}'
    

    替换以下内容:

    • FUNCTION_NAME:已部署函数的名称。
    • RUNTIME:您的函数使用的语言运行时。
    • REGION:要在其中部署函数的区域。
    • TRIGGER_REGION:触发器的位置,必须与 Firestore 数据库的区域相同。
    • ENTRY_POINT:源代码中函数的入口点。这是在您的函数运行时执行的代码。

    按原样使用其他字段:

    • --trigger-event-filters=type=google.cloud.firestore.document.v1.written 指定根据 google.cloud.firestore.document.v1.written 事件类型创建、更新或删除文档时触发函数。
    • --trigger-event-filters=database='(default)' 指定 Firestore 数据库。对于默认数据库名称,请使用 (default)
    • --trigger-event-filters-path-pattern=document='messages/{pushId}' 提供受监控文档的路径模式,应监控这些文档是否发生相关改变。此路径模式声明 messages 集合中的所有文档都应被监控。如需了解详情,请参阅了解路径模式

测试“转换为大写”函数

如需测试刚刚部署的“转换为大写”函数,请在 Firestore 数据库中设置一个名为 messages 的集合:

  1. 进入 Firestore 数据页面

  2. 点击开始使用集合

  3. 指定 messages 作为集合 ID。

  4. 如需开始添加集合的第一个文档,请在为其添加首个文档下接受自动生成的文档 ID

  5. 如需触发已部署的函数,请添加一个文档,其字段名称为“original”且字段值为小写字母单词,例如:

    展示如何创建 Firestore 集合的屏幕截图

  6. 保存文档时,您可以看到值字段中的小写字词转换为大写字词。

    如果您随后修改字段值以包含小写字母,则会再次触发该函数,并将所有小写字母转换为大写字母。

限制

请注意适用于 Cloud Functions 的 Firestore 触发器的以下限制:

  • Cloud Functions (第 1 代) 前提条件是 Firestore 原生模式的现有“(默认)”数据库。它不支持 Firestore 命名数据库或 Datastore 模式。在这种情况下,请使用 Cloud Functions (第 2 代) 来配置事件。
  • 无法保证顺序。快速更改可能会以意想不到的顺序触发函数调用。
  • 事件至少会被传送一次,但单个事件可能会导致多次调用函数。应该避免依赖“正好一次”机制,并编写幂等函数
  • Datastore 模式 Firestore 需要 Cloud Functions(第 2 代)。Cloud Functions(第 1 代)不支持 Datastore 模式。
  • 一个触发器与单一数据库相关联。您无法创建与多个数据库匹配的触发器。
  • 删除数据库不会自动删除该数据库的任何触发器。触发器会停止传送事件,但会继续存在,直到您删除触发器
  • 如果匹配的事件超过请求大小上限,该事件可能不会传送到 Cloud Functions(第 1 代)。
    • 因请求大小而未传送的事件会记录在平台日志中,并计入项目的日志使用量。
    • 您可以在 Logs Explorer 中找到这些日志,其严重性为 error 且内容为“由于大小超出第 1 代的限制,因此事件无法传送到 Cloud Functions 函数”消息。您可以在 functionName 字段下方找到函数名称。如果 receiveTimestamp 字段仍在从现在起的一小时内,您可以利用该时间戳之前和之后的快照来读取相关文档,从而推断实际事件内容。
    • 为避免这种情况发生,您可以:
      • 迁移和升级到 Cloud Functions (第 2 代)
      • 缩小文档
      • 删除相关的 Cloud Functions 函数
    • 您可以使用排除功能关闭日志记录功能本身,但请注意,违规事件仍然不会传送。