Cloud Firestore 触发器


借助 Cloud Functions,您无需更新客户端代码即可处理 Cloud Firestore 中的事件。 您可以通过文档快照接口或通过 Admin SDK 进行 Cloud Firestore 更改。

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

  1. 等待特定文档发生更改。
  2. 在事件发生时触发并执行相应的任务。
  3. 接收相关数据对象,该对象包含了存储在指定文档中的数据的快照。对于写入或更新事件,该数据对象包含两个快照,分别代表触发事件前后的数据状态。

Firestore 实例所在位置与函数所在位置之间的距离可能会造成严重的网络延迟。为了优化性能,请考虑指定函数位置(如适用)。

Cloud Firestore 函数触发器

Cloud Functions for Firebase SDK 会导出以下 Cloud Firestore 事件触发器,以便您创建与特定 Cloud Firestore 事件关联的处理程序:

Node.js

事件类型 触发器
onDocumentCreated 首次写入某个文档时触发。
onDocumentUpdated 当某文档已存在并且其任何值发生了更改时触发。
onDocumentDeleted 在有文档被删除时触发。
onDocumentWritten 在触发了 onDocumentCreatedonDocumentUpdatedonDocumentDeleted 时触发。

Python(预览版)

事件类型 触发器
on_document_created 首次写入某个文档时触发。
on_document_updated 当某文档已存在并且其任何值发生了更改时触发。
on_document_deleted 在有文档被删除时触发。
on_document_written 在触发了 on_document_createdon_document_updatedon_document_deleted 时触发。

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

如果您还没有启用了 Cloud Functions for Firebase 的项目,请参阅 Cloud Functions for Firebase(第 2 代)使用入门,配置并设置 Cloud Functions for Firebase 项目。

编写 Cloud Firestore 触发的函数

定义函数触发器

如需定义 Cloud Firestore 触发器,请指定文档路径和事件类型:

Node.js

import {
  onDocumentWritten,
  onDocumentCreated,
  onDocumentUpdated,
  onDocumentDeleted,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("my-collection/{docId}", (event) => {
   /* ... */ 
});

Python(预览版)

from firebase_functions.firestore_fn import (
  on_document_created,
  on_document_deleted,
  on_document_updated,
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_created(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot]) -> None:

文档路径可以引用特定文档通配符模式

指定单个文档

如果您希望针对特定文档的任何更改都触发一个事件,可以使用以下函数。

Node.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("users/marie", (event) => {
  // Your code here
});

Python(预览版)

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/marie")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:

使用通配符指定一组文档

如果您要将触发器附加到一组文档(例如特定集合中的任何文档)中,请使用 {wildcard} 替代文档 ID:

Node.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("users/{userId}", (event) => {
  // If we set `/users/marie` to {name: "Marie"} then
  // event.params.userId == "marie"
  // ... and ...
  // event.data.after.data() == {name: "Marie"}
});

Python(预览版)

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # If we set `/users/marie` to {name: "Marie"} then
  event.params["userId"] == "marie"  # True
  # ... and ...
  event.data.after.to_dict() == {"name": "Marie"}  # True

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

如果 users 中的某个文档有多个子集合,并且这些子集合中一个文档内的某字段发生了更改,则不会触发 userId 通配符。

通配符匹配项会从文档路径中提取出来并存储到 event.params 中。您可以定义任意多个通配符,以替代明确指定的集合或文档 ID,例如:

Node.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("users/{userId}/{messageCollectionId}/{messageId}", (event) => {
    // If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
    // event.params.userId == "marie";
    // event.params.messageCollectionId == "incoming_messages";
    // event.params.messageId == "134";
    // ... and ...
    // event.data.after.data() == {body: "Hello"}
});

Python(预览版)

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/{userId}/{messageCollectionId}/{messageId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
  event.params["userId"] == "marie"  # True
  event.params["messageCollectionId"] == "incoming_messages"  # True
  event.params["messageId"] == "134"  # True
  # ... and ...
  event.data.after.to_dict() == {"body": "Hello"}

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

事件触发器

在有新文档创建时触发函数

只要集合中有新文档创建,您就可以触发函数。每当有新的用户个人资料添加时,都会触发此示例函数:

Node.js

import {
  onDocumentCreated,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.createuser = onDocumentCreated("users/{userId}", (event) => {
    // Get an object representing the document
    // e.g. {'name': 'Marie', 'age': 66}
    const snapshot = event.data;
    if (!snapshot) {
        console.log("No data associated with the event");
        return;
    }
    const data = snapshot.data();

    // access a particular field as you would any JS property
    const name = data.name;

    // perform more operations ...
});

Python(预览版)

from firebase_functions.firestore_fn import (
  on_document_created,
  Event,
  DocumentSnapshot,
)

@on_document_created(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot]) -> None:
  # Get a dictionary representing the document
  # e.g. {'name': 'Marie', 'age': 66}
  new_value = event.data.to_dict()

  # Access a particular field as you would any dictionary
  name = new_value["name"]

  # Perform more operations ...

在有文档被更新时触发函数

您还可以在更新文档时触发函数。如果用户更改其个人资料,就会触发此示例函数:

Node.js

import {
  onDocumentUpdated,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.updateuser = onDocumentUpdated("users/{userId}", (event) => {
    // Get an object representing the document
    // e.g. {'name': 'Marie', 'age': 66}
    const newValue = event.data.after.data();

    // access a particular field as you would any JS property
    const name = newValue.name;

    // perform more operations ...
});

Python(预览版)

from firebase_functions.firestore_fn import (
  on_document_updated,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # Get a dictionary representing the document
  # e.g. {'name': 'Marie', 'age': 66}
  new_value = event.data.after.to_dict()

  # Access a particular field as you would any dictionary
  name = new_value["name"]

  # Perform more operations ...

在有文档被删除时触发函数

您还可以在删除文档时触发函数。用户删除其个人资料时,就会触发此示例函数:

Node.js

import {
  onDocumentDeleted,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.deleteuser = onDocumentDeleted("users/{userId}", (event) => {
    // Get an object representing the document
    // e.g. {'name': 'Marie', 'age': 66}
    const snap =  event.data;
    const data =  snap.data();

    // perform more operations ...
});

Python(预览版)

from firebase_functions.firestore_fn import (
  on_document_deleted,
  Event,
  DocumentSnapshot,
)

@on_document_deleted(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot|None]) -> None:
  # Perform more operations ...

在文档有任何更改发生时触发函数

如果您不在意触发的事件类型,则可以使用“写入文档”事件触发器来监听 Cloud Firestore 文档发生的所有更改。如果创建、更新或删除用户,就会触发此示例函数:

Node.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.modifyuser = onDocumentWritten("users/{userId}", (event) => {
    // Get an object with the current document values.
    // If the document does not exist, it was deleted
    const document =  event.data.after.data();

    // Get an object with the previous document values
    const previousValues =  event.data.before.data();

    // perform more operations ...
});

Python(预览版)

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot | None]]) -> None:
  # Get an object with the current document values.
  # If the document does not exist, it was deleted.
  document = (event.data.after.to_dict()
              if event.data.after is not None else None)

  # Get an object with the previous document values.
  # If the document does not exist, it was newly created.
  previous_values = (event.data.before.to_dict()
                     if event.data.before is not None else None)

  # Perform more operations ...

读取和写入数据

函数被触发后,会提供与相应事件相关的数据的快照。您可以使用此快照对触发了该事件的文档执行读取或写入操作,也可以使用 Firebase Admin SDK 访问数据库的其他部分。

事件数据

读取数据

函数触发后,您可能希望获取更新后文档的数据,或者获取更新之前的数据。您可以使用 event.data.before 来获取之前的数据,其中包含更新前的文档快照。同样,event.data.after 包含更新后的文档快照状态。

Node.js

exports.updateuser2 = onDocumentUpdated("users/{userId}", (event) => {
    // Get an object with the current document values.
    // If the document does not exist, it was deleted
    const newValues =  event.data.after.data();

    // Get an object with the previous document values
    const previousValues =  event.data.before.data();
});

Python(预览版)

@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # Get an object with the current document values.
  new_value = event.data.after.to_dict()

  # Get an object with the previous document values.
  prev_value = event.data.before.to_dict()

您可以像在其他任何对象中一样访问属性。或者,您也可以使用 get 函数访问特定字段:

Node.js

// Fetch data using standard accessors
const age = event.data.after.data().age;
const name = event.data.after.data()['name'];

// Fetch data using built in accessor
const experience = event.data.after.data.get('experience');

Python(预览版)

# Get the value of a single document field.
age = event.data.after.get("age")

# Convert the document to a dictionary.
age = event.data.after.to_dict()["age"]

写入数据

每个函数调用都与 Cloud Firestore 数据库中的特定文档相关联。您可以在返回给函数的快照中访问该文档。

文档引用包含 update()set()remove() 等方法,以便您修改触发函数的文档。

Node.js

import { onDocumentUpdated } from "firebase-functions/v2/firestore";

exports.countnamechanges = onDocumentUpdated('users/{userId}', (event) => {
  // Retrieve the current and previous value
  const data = event.data.after.data();
  const previousData = event.data.before.data();

  // We'll only update if the name has changed.
  // This is crucial to prevent infinite loops.
  if (data.name == previousData.name) {
    return null;
  }

  // Retrieve the current count of name changes
  let count = data.name_change_count;
  if (!count) {
    count = 0;
  }

  // Then return a promise of a set operation to update the count
  return data.after.ref.set({
    name_change_count: count + 1
  }, {merge: true});

});

Python(预览版)

@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
 # Get the current and previous document values.
 new_value = event.data.after
 prev_value = event.data.before

 # We'll only update if the name has changed.
 # This is crucial to prevent infinite loops.
 if new_value.get("name") == prev_value.get("name"):
     return

 # Retrieve the current count of name changes
 count = new_value.to_dict().get("name_change_count", 0)

 # Update the count
 new_value.reference.update({"name_change_count": count + 1})
 ```

触发事件之外的数据

Cloud Functions 函数在受信任的环境中执行,这意味着这些函数被授权为项目的服务账号,您可以使用 Firebase Admin SDK 进行读取和写入:

Node.js

const { initializeApp } = require('firebase-admin/app');
const { getFirestore, Timestamp, FieldValue } = require('firebase-admin/firestore');

initializeApp();
const db = getFirestore();

exports.writetofirestore = onDocumentWritten("some/doc", (event) => {
    db.doc('some/otherdoc').set({ ... });
  });

  exports.writetofirestore = onDocumentWritten('users/{userId}', (event) => {
    db.doc('some/otherdoc').set({
      // Update otherdoc
    });
  });

Python(预览版)

from firebase_admin import firestore, initialize_app
import google.cloud.firestore

initialize_app()

@on_document_written(document="some/doc")
def myfunction(event: Event[Change[DocumentSnapshot | None]]) -> None:
  firestore_client: google.cloud.firestore.Client = firestore.client()
  firestore_client.document("another/doc").set({
      # ...
  })

限制

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

  • 无法保证顺序。快速更改可能会以意想不到的顺序触发函数调用。
  • 事件至少会被传送一次,但单个事件可能会导致多次调用函数。应该避免依赖“正好一次”机制,并编写幂等函数
  • Datastore 模式 Cloud Firestore 需要 Cloud Functions(第 2 代)。Cloud Functions(第 1 代)不支持 Datastore 模式。