使用 Cloud Functions (第 2 代) 进行扩展

借助 Cloud Functions,您可以部署代码来处理因 Firestore 数据库更改而触发的事件。这样,您无需运行自己的服务器即可添加服务器端功能。

使用 Cloud Functions (第 2 代) 扩展 Firestore

Cloud Functions (第 2 代)支持以下 Firestore 事件触发器,以便您创建与 Firestore 事件关联的处理程序:

事件类型 触发器
google.cloud.firestore.document.v1.created 首次写入某个文档时触发。
google.cloud.firestore.document.v1.updated 当某文档已存在并且其任何值发生了更改时触发。
google.cloud.firestore.document.v1.deleted 在有文档被删除时触发。
google.cloud.firestore.document.v1.written 在触发 createdupdateddeleted 时触发。
google.cloud.firestore.document.v1.created.withAuthContext created 相同,但会添加身份验证信息。
google.cloud.firestore.document.v1.updated.withAuthContext updated 相同,但会添加身份验证信息。
google.cloud.firestore.document.v1.deleted.withAuthContext deleted 相同,但会添加身份验证信息。
google.cloud.firestore.document.v1.written.withAuthContext written 相同,但会添加身份验证信息。

Firestore 事件只会在文档发生更改时触发。数据未更改的 Firestore 文档更新(无操作写入)不会生成更新或写入事件。无法将事件添加到特定字段。

在事件中包含身份验证上下文

如需添加有关事件的其他身份验证信息,请使用扩展名为 withAuthContext 的事件触发器。此扩展程序会添加有关触发事件的主帐号的其他信息。除了在基本事件中返回的信息之外,它还会添加 authtypeauthid 属性。如需详细了解属性值,请参阅 authcontext 参考文档。

编写 Firestore 触发的函数

如需编写响应 Firestore 事件的函数,请准备在部署期间指定以下内容:

  • 触发器事件类型
  • 用于选择与函数关联的文档的触发器事件过滤条件
  • 要运行的函数代码

触发器事件过滤条件

指定事件过滤条件时,您可以指定文档完全匹配或路径模式。使用路径模式可匹配带有通配符 *** 的多个文档。

例如,您可以回复对以下文档的更改:

users/marie

使用通配符 *** 来响应与模式匹配的文档中的更改。通配符 * 与单个片段匹配,多片段通配符 ** 与模式中的零个或多个片段匹配。

对于单个片段匹配 (*),您还可以使用命名的捕获组。例如 users/{userId}

例如:

模式 说明
/users/*/users/{userId} 匹配 /users 集合中的所有文档。与子集合(例如 /users/marie/messages/33e2IxYBD9enzS50SJ68)中的文档不匹配
/users/** 匹配 /users 集合中的所有文档以及 /users/marie/messages/33e2IxYBD9enzS50SJ68 等子集合中的文档

如需详细了解路径模式,请参阅 Eventarc 路径模式

即使您使用的是通配符,触发器也必须始终指向某个文档。例如,users/{userId=*}/{messageCollectionId=*} 是无效的,因为 {messageCollectionId=*} 是一个集合。不过,users/{userId=*}/{messageCollectionId}/{messageId=*}有效的,因为 {messageId=*} 将始终指向某个文档。

函数示例

以下示例演示了如何接收 Firestore 事件。 如需处理事件中涉及的文档数据,请查看 valueold_value 字段。

  • value:一个包含操作后文档快照的 Document 对象。对于删除事件,不会填充此字段。
  • old_value:一个包含操作前文档快照的 Document 对象。系统仅会针对更新和删除事件填充此字段。

Go

如需向 Firestore 进行身份验证,请设置应用默认凭据。如需了解详情,请参阅为本地开发环境设置身份验证


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

如需向 Firestore 进行身份验证,请设置应用默认凭据。如需了解详情,请参阅为本地开发环境设置身份验证

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

Node.js

如需向 Firestore 进行身份验证,请设置应用默认凭据。如需了解详情,请参阅为本地开发环境设置身份验证

使用 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

如需向 Firestore 进行身份验证,请设置应用默认凭据。如需了解详情,请参阅为本地开发环境设置身份验证

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)

C#

如需向 Firestore 进行身份验证,请设置应用默认凭据。如需了解详情,请参阅为本地开发环境设置身份验证

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

以下示例将添加到受影响文档的 original 字段的字符串转换为大写,并将新值写入同一文档:

Go

如需向 Firestore 进行身份验证,请设置应用默认凭据。如需了解详情,请参阅为本地开发环境设置身份验证


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

如需向 Firestore 进行身份验证,请设置应用默认凭据。如需了解详情,请参阅为本地开发环境设置身份验证

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

Node.js

如需向 Firestore 进行身份验证,请设置应用默认凭据。如需了解详情,请参阅为本地开发环境设置身份验证

使用 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

如需向 Firestore 进行身份验证,请设置应用默认凭据。如需了解详情,请参阅为本地开发环境设置身份验证

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.")

C#

如需向 Firestore 进行身份验证,请设置应用默认凭据。如需了解详情,请参阅为本地开发环境设置身份验证

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

在源代码中添加 proto 依赖项

您必须在函数源目录中添加 Firestore data.proto 文件。此文件会导入以下 proto,您还必须将这些 proto 添加到源目录中:

请为依赖项使用相同的目录结构。例如,将 struct.proto 放置在 google/protobuf 中。

这些文件是解码事件数据所必需的。如果您的函数来源不包含这些文件,则会在运行时返回错误。

事件属性

每个事件都包含数据属性,数据属性包含事件的相关信息,例如事件触发时间。Firestore 会添加与事件中涉及的数据库和文档有关的其他数据。您可以按如下方式访问这些属性:

Java
logger.info("Function triggered by event on: " + event.getSource());
logger.info("Event type: " + event.getType());
logger.info("Event time " + event.getTime());
logger.info("Event project: " + event.getExtension("project"));
logger.info("Event location: " + event.getExtension("location"));
logger.info("Database name: " + event.getExtension("database"));
logger.info("Database document: " + event.getExtension("document"));
// For withAuthContext events
logger.info("Auth information: " + event.getExtension("authid"));
logger.info("Auth information: " + event.getExtension("authtype"));
Node.js
console.log(`Function triggered by event on: ${cloudEvent.source}`);
console.log(`Event type: ${cloudEvent.type}`);
console.log(`Event time: ${cloudEvent.time}`);
console.log(`Event project: ${cloudEvent.project}`);
console.log(`Event location: ${cloudEvent.location}`);
console.log(`Database name: ${cloudEvent.database}`);
console.log(`Document name: ${cloudEvent.document}`);
// For withAuthContext events
console.log(`Auth information: ${cloudEvent.authid}`);
console.log(`Auth information: ${cloudEvent.authtype}`);
Python
print(f"Function triggered by change to: {cloud_event['source']}")
print(f"Event type: {cloud_event['type']}")
print(f"Event time: {cloud_event['time']}")
print(f"Event project: {cloud_event['project']}")
print(f"Location: {cloud_event['location']}")
print(f"Database name: {cloud_event['database']}")
print(f"Document: {cloud_event['document']}")
// For withAuthContext events
print(f"Auth information: {cloud_event['authid']}")
print(f"Auth information: {cloud_event['authtype']}")

部署函数

部署 Cloud Functions 的用户必须具有 Cloud Functions Developer IAM 角色或具有提供相同权限的其他角色。另请参阅其他部署配置

您可以使用 gcloud CLI 或 Google Cloud 控制台部署函数。下面的示例演示了如何使用 gcloud CLI 进行部署。如需详细了解如何使用 Google Cloud 控制台进行部署,请参阅部署 Cloud Functions 函数

gcloud

  1. 在 Google Cloud 控制台中,激活 Cloud Shell。

    激活 Cloud Shell

    Cloud Shell 会话随即会在 Google Cloud 控制台的底部启动,并显示命令行提示符。Cloud Shell 是一个已安装 Google Cloud CLI 且已为当前项目设置值的 Shell 环境。该会话可能需要几秒钟时间来完成初始化。

  2. 使用 gcloud functions deploy 命令部署函数:

    gcloud functions deploy YOUR_FUNCTION_NAME \
    --gen2 \
    --region=FUNCTION_LOCATION \
    --trigger-location=TRIGGER_LOCATION \
    --runtime=YOUR_RUNTIME \
    --source=YOUR_SOURCE_LOCATION \
    --entry-point=YOUR_CODE_ENTRYPOINT \
    --trigger-event-filters="type=EVENT_FILTER_TYPE" \
    --trigger-event-filters="database=DATABASE" \
    --trigger-event-filters-path-pattern="document=DOCUMENT" \
    

    第一个参数 YOUR_FUNCTION_NAME 是已部署函数的名称。函数名称必须以字母开头,后面最多可跟 62 个字母、数字、连字符或下划线,但必须以字母或数字结尾。

    • --gen2 标志用于指定要部署到 Cloud Functions (第 2 代)。省略此标志会导致部署到 Cloud Functions(第 1 代)。

    • --region 标志指定要部署函数的区域。

      为了最大限度地提高邻近度,请设置为您的 Firestore 数据库附近的一个区域。如果您的 Firestore 数据库位于多区域位置,对于位于 nam5 的数据库,设置为 us-central1;对于位于 eur3 的数据库,设置为 europe-west4。对于区域级 Firestore 位置,请设置为同一区域。

    • --trigger-location 标志指定触发器的位置。您必须将此标志设置为 Firestore 数据库的位置。

    • --runtime 标志指定函数使用的语言运行时。Cloud Functions 支持多种运行时;如需了解详情,请参阅运行时

    • --source 标志用于指定函数源代码的位置。如需了解详情,请参阅以下内容:

    • --entry-point 标志指定源代码中函数的入口点。这是在您的函数运行时执行的代码。此标志的值必须是源代码中存在的函数名称或完全限定类名称。如需了解详情,请参阅函数入口点

    • EVENT_FILTER_TYPE:Firestore 支持以下事件类型。

      • google.cloud.firestore.document.v1.created:首次写入某个文档时发送事件。
      • google.cloud.firestore.document.v1.updated:当某个文档已存在并且其值发生了更改时发送事件。
      • google.cloud.firestore.document.v1.deleted:当某个文档被删除时,系统会发送该事件。
      • google.cloud.firestore.document.v1.written:创建、更新或删除文档时发送事件。
      • google.cloud.firestore.document.v1.created.withAuthContext:当首次向某个文档写入数据时发送事件,并且该事件包含额外的身份验证信息
      • google.cloud.firestore.document.v1.updated.withAuthContext:当某个文档已存在并且其值发生了更改时发送事件。包含额外的身份验证信息
      • google.cloud.firestore.document.v1.deleted.withAuthContext:当某个文档被删除时,系统会发送该事件。包含额外的身份验证信息
      • google.cloud.firestore.document.v1.written.withAuthContext:当创建、更新或删除文档时发送事件。包含额外的身份验证信息
    • DATABASE:Firestore 数据库。对于默认数据库名称,请使用 (default)

    • DOCUMENT:在创建、更新或删除数据时触发事件的数据库路径。运算符可以是下列之一:

      • 等于;例如 --trigger-event-filters=document='users/marie'
      • 路径模式;例如 --trigger-event-filters-path-pattern=document='users/*'。如需了解详情,请参阅了解路径模式

    您可以视需要在部署函数时指定其他配置网络安全选项。

    如需查看有关部署命令及其标志的完整参考信息,请参阅 gcloud functions deploy 文档。

部署示例

以下示例演示了如何使用 Google Cloud CLI 进行部署。

us-west2 区域中为数据库部署一个函数:

gcloud functions deploy gcfv2-trigger-firestore-node \
--gen2 \
--region=us-west2 \
--trigger-location=us-west2 \
--runtime=nodejs18 \
--source=gs://CLOUD_STORAGE_BUCKET/firestoreEventFunction.zip \
--entry-point=makeUpperCase \
--trigger-event-filters=type=google.cloud.firestore.document.v1.written \
--trigger-event-filters=database='(default)' \
--trigger-event-filters-path-pattern=document='messages/{pushId}'

nam5 多区域位置为数据库部署函数:

gcloud functions deploy gcfv2-trigger-firestore-python \
--gen2 \
--region=us-central1 \
--trigger-location=nam5 \
--runtime=python311 \
--source=gs://CLOUD_STORAGE_BUCKET/firestoreEventFunction.zip \
--entry-point=make_upper_case \
--trigger-event-filters=type=google.cloud.firestore.document.v1.written.withAuthContext \
--trigger-event-filters=database='(default)' \
--trigger-event-filters-path-pattern=document='messages/{pushId}'

限制

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

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

Eventarc 和 Firestore 位置

Eventarc 不支持多区域位置的 Firestore 事件触发器,但您仍然可以为多区域位置的 Firestore 数据库创建触发器。Eventarc 会将 Firestore 多区域位置映射到以下 Eventarc 区域:

Firestore 多区域 Eventarc 区域
nam5 us-central1
eur3 europe-west4

Cloud Functions (第 2 代) 和第 1 代函数之间的区别

Cloud Functions (第 2 代)为所有运行时使用 Eventarc 事件。以前,Cloud Functions(第 1 代)仅在部分运行时中使用 Eventarc 事件。Eventarc 事件与 Cloud Functions(第 1 代)存在以下差异。

  • 除了 Cloud Functions 之外,适用于 Eventarc 的 Firestore 触发器支持其他目标位置。您可以将 CloudEvents 路由到多个目的地,包括但不限于 Cloud Run、GKE 和 Workflows。

  • 适用于 Eventarc 的 Firestore 触发器会在数据库写入操作开始时检索触发器定义,并使用该定义来确定 Firestore 是否应发出事件。写入操作不会考虑运行期间可能发生的任何触发器定义更改。

    Cloud Functions(第 1 代)会在评估数据库写入期间检索触发器定义,并且评估期间对触发器的更改可能会影响 Firestore 是否发出事件。

如需了解详情,请参阅 Cloud Functions 版本比较