借助 Cloud Functions,您可以部署代码来处理因 Cloud Firestore 数据库更改而触发的事件。这样您就可以轻松向您的应用中添加服务器端功能,而无需运行您自己的服务器。
Cloud Functions(第 2 代)
得益于 Cloud Run 和 Eventarc 提供的支持,Cloud Functions for Firebase(第 2 代)能为您提供更强大的基础设施,让您对性能和可伸缩性进行更深层次的控制,并更好地控制函数运行时。如需详细了解第 2 代产品,请参阅 Cloud Functions for Firebase(第 2 代)。如需详细了解第 1 代产品,请参阅使用 Cloud Functions 扩展 Cloud Firestore。
Cloud Firestore 函数触发器
Cloud Functions for Firebase SDK 会导出以下 Cloud Firestore 事件触发器,以便您创建与特定 Cloud Firestore 事件关联的处理程序:
Node.js
事件类型 | 触发器 |
---|---|
onDocumentCreated |
首次写入某个文档时触发。 |
onDocumentUpdated |
当某文档已存在并且其任何值发生了更改时触发。 |
onDocumentDeleted |
在有文档被删除时触发。 |
onDocumentWritten |
在触发 onDocumentCreated 、onDocumentUpdated 或 onDocumentDeleted 时触发。 |
onDocumentCreatedWithAuthContext |
带有额外身份验证信息的 onDocumentCreated |
onDocumentWrittenWithAuthContext |
带有额外身份验证信息的 onDocumentWritten |
onDocumentDeletedWithAuthContext |
带有额外身份验证信息的 onDocumentDeleted |
onDocumentUpdatedWithAuthContext |
带有额外身份验证信息的 onDocumentUpdated |
Python(预览版)
事件类型 | 触发器 |
---|---|
on_document_created |
首次写入某个文档时触发。 |
on_document_updated |
当某文档已存在并且其任何值发生了更改时触发。 |
on_document_deleted |
在有文档被删除时触发。 |
on_document_written |
在触发 on_document_created 、on_document_updated 或 on_document_deleted 时触发。 |
on_document_created_with_auth_context |
带有额外身份验证信息的 on_document_created |
on_document_updated_with_auth_context |
带有额外身份验证信息的 on_document_updated |
on_document_deleted_with_auth_context |
带有额外身份验证信息的 on_document_deleted |
on_document_written_with_auth_context |
带有额外身份验证信息的 on_document_written |
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 ...
});
如需包含额外的身份验证信息,请使用 onDocumentCreatedWithAuthContext
。
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 ...
});
如需包含额外的身份验证信息,请使用 onDocumentUpdatedWithAuthContext
。
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 ...
});
如需包含额外的身份验证信息,请使用 onDocumentDeletedWithAuthContext
。
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 ...
});
如需包含额外的身份验证信息,请使用 onDocumentWrittenWithAuthContext
。
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})
访问用户身份验证信息
如果您使用以下某一种类型的事件,则可以访问触发事件的主账号的用户身份验证信息。此信息是对基本事件中返回的信息的补充。
Node.js
onDocumentCreatedWithAuthContext
onDocumentWrittenWithAuthContext
onDocumentDeletedWithAuthContext
onDocumentUpdatedWithAuthContext
Python(预览版)
on_document_created_with_auth_context
on_document_updated_with_auth_context
on_document_deleted_with_auth_context
on_document_written_with_auth_context
如需了解身份验证上下文中可用的数据,请参阅身份验证上下文。以下示例演示了如何检索身份验证信息:
Node.js
import { onDocumentWrittenWithAuthContext } from "firebase-functions/v2/firestore"
exports.syncUser = onDocumentWrittenWithAuthContext("users/{userId}", (event) => {
const snapshot = event.data.after;
if (!snapshot) {
console.log("No data associated with the event");
return;
}
const data = snapshot.data();
// retrieve auth context from event
const { authType, authId } = event;
let verified = false;
if (authType === "system") {
// system-generated users are automatically verified
verified = true;
} else if (authType === "unknown" || authType === "unauthenticated") {
// admin users from a specific domain are verified
if (authId.endsWith("@example.com")) {
verified = true;
}
}
return data.after.ref.set({
created_by: authId,
verified,
}, {merge: true});
});
Python(预览版)
@on_document_updated_with_auth_context(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
# Get the auth context from the event
user_auth_type = event.auth_type
user_auth_id = event.auth_id
触发事件之外的数据
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 触发器,请注意以下限制:
- Cloud Functions(第 1 代)前提条件是 Firestore 原生模式的现有“(默认)”数据库。它不支持 Cloud Firestore 命名数据库或 Datastore 模式。在这种情况下,请使用 Cloud Functions(第 2 代)来配置事件。
- 无法保证顺序。快速更改可能会以意想不到的顺序触发函数调用。
- 事件至少会被传送一次,但单个事件可能会导致多次调用函数。应该避免依赖“正好一次”机制,并编写幂等函数。
- Datastore 模式下的 Cloud Firestore 需要 Cloud Functions(第 2 代)。Cloud Functions(第 1 代)不支持 Datastore 模式。
- 一个触发器与单一数据库相关联。您无法创建与多个数据库匹配的触发器。
- 删除数据库不会自动删除该数据库的任何触发器。触发器会停止传送事件,但会继续存在,直到您删除触发器。
- 如果匹配的事件超过请求大小上限,该事件可能不会传送到 Cloud Functions(第 1 代)。
- 因请求大小而未传送的事件会记录在平台日志中,并计入项目的日志使用量。
- 您可以在 Logs Explorer 中找到这些日志,其严重性为
error
且内容为“由于大小超出第 1 代的限制,因此事件无法传送到 Cloud Functions 函数”消息。您可以在functionName
字段下方找到函数名称。如果receiveTimestamp
字段仍在从现在起的一小时内,您可以利用该时间戳之前和之后的快照来读取相关文档,从而推断实际事件内容。 - 为避免这种情况发生,您可以:
- 迁移和升级到 Cloud Functions(第 2 代)
- 缩小文档
- 删除相关的 Cloud Functions
- 您可以使用排除功能关闭日志记录功能本身,但请注意,违规事件仍然不会传送。