使用 Cloud Functions 扩展 Firestore
借助 Cloud Functions,您可以部署 Node.js 代码来处理因 Firestore 数据库更改而触发的事件。这样您就可以轻松向您的应用中添加服务器端功能,而无需运行您自己的服务器。
如需用例的示例,请参阅 Cloud Functions 有哪些用途?或 GitHub 代码库中的 Functions 示例。
Firestore 函数触发器
Cloud Functions for Firebase SDK 会导出 functions.firestore
对象,以便您创建与特定 Firestore 事件关联的处理程序。
事件类型 | 触发器 |
---|---|
onCreate |
首次写入某个文档时触发。 |
onUpdate |
当某文档已存在并且其任何值发生了更改时触发。 |
onDelete |
当包含数据的文档被删除时触发。 |
onWrite |
在触发 onCreate 、onUpdate 或 onDelete 时触发。 |
如果您还没有启用了 Cloud Functions for Firebase 的项目,请参阅《使用入门:编写和部署您的第一批函数》,配置并设置 Cloud Functions for Firebase 项目。
编写 Firestore 触发的函数
定义函数触发器
要定义 Firestore 触发器,请指定文档路径和事件类型:
Node.js
const functions = require('firebase-functions');
exports.myFunction = functions.firestore
.document('my-collection/{docId}')
.onWrite((change, context) => { /* ... */ });
指定单个文档
如果您希望针对特定文档的任何更改都触发一个事件,可以使用以下函数。
Node.js
// Listen for any change on document `marie` in collection `users` exports.myFunctionName = functions.firestore .document('users/marie').onWrite((change, context) => { // ... Your code here });
使用通配符指定一组文档
如果您要将触发器附加到一组文档(例如特定集合中的任何文档)中,请使用 {wildcard}
替代文档 ID:
Node.js
// Listen for changes in all documents in the 'users' collection exports.useWildcard = functions.firestore .document('users/{userId}') .onWrite((change, context) => { // If we set `/users/marie` to {name: "Marie"} then // context.params.userId == "marie" // ... and ... // change.after.data() == {name: "Marie"} });
在此示例中,如果 users
中的任何文档的任何字段发生更改,都会匹配一个名为 userId
的通配符。
如果 users
中的某个文档有多个子集合,并且这些子集合中一个文档内的某字段发生了更改,则不会触发 userId
通配符。
通配符匹配项会从文档路径中提取出来并存储到 context.params
中。您可以定义任意多个通配符,以替代明确指定的集合或文档 ID,例如:
Node.js
// Listen for changes in all documents in the 'users' collection and all subcollections exports.useMultipleWildcards = functions.firestore .document('users/{userId}/{messageCollectionId}/{messageId}') .onWrite((change, context) => { // If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then // context.params.userId == "marie"; // context.params.messageCollectionId == "incoming_messages"; // context.params.messageId == "134"; // ... and ... // change.after.data() == {body: "Hello"} });
事件触发器
在有新文档创建时触发函数
如果您希望在某个集合中有新文档创建时触发函数,则可以使用带通配符的 onCreate()
处理程序来实现。下面的示例函数会在每次系统中添加了新的用户个人资料时调用 createUser
:
Node.js
exports.createUser = functions.firestore .document('users/{userId}') .onCreate((snap, context) => { // Get an object representing the document // e.g. {'name': 'Marie', 'age': 66} const newValue = snap.data(); // access a particular field as you would any JS property const name = newValue.name; // perform desired operations ... });
在有文档被更新时触发函数
如果您希望在文档被更新时触发函数,也可以使用带通配符的 onUpdate()
函数来实现。下面的示例函数会在用户更改其个人资料时调用 updateUser
:
Node.js
exports.updateUser = functions.firestore .document('users/{userId}') .onUpdate((change, context) => { // Get an object representing the document // e.g. {'name': 'Marie', 'age': 66} const newValue = change.after.data(); // ...or the previous value before this update const previousValue = change.before.data(); // access a particular field as you would any JS property const name = newValue.name; // perform desired operations ... });
在有文档被删除时触发函数
如果您希望在文档被删除时触发函数,也可以使用带通配符的 onDelete()
函数来实现。下面的示例函数会在用户删除其个人资料时调用 deleteUser
:
Node.js
exports.deleteUser = functions.firestore .document('users/{userID}') .onDelete((snap, context) => { // Get an object representing the document prior to deletion // e.g. {'name': 'Marie', 'age': 66} const deletedValue = snap.data(); // perform desired operations ... });
对文档进行任何更改时触发函数
如果您不在意触发的事件类型,则可以使用带通配符的 onWrite()
函数来监听 Firestore 文档中的所有更改。创建、更新或删除用户时,此示例函数会调用 modifyUser
:
Node.js
exports.modifyUser = functions.firestore .document('users/{userID}') .onWrite((change, context) => { // Get an object with the current document value. // If the document does not exist, it has been deleted. const document = change.after.exists ? change.after.data() : null; // Get an object with the previous document value (for update or delete) const oldDocument = change.before.data(); // perform desired operations ... });
读取和写入数据
函数被触发后,会提供与相应事件相关的数据的快照。您可以使用此快照对触发了该事件的文档执行读取或写入操作,也可以使用 Firebase Admin SDK 访问数据库的其他部分。
事件数据
读取数据
函数触发后,您可能希望获取更新后文档的数据,或者获取更新之前的数据。您可以使用 change.before.data()
来获取之前的数据,其中包含更新前的文档快照。同样,change.after.data()
包含更新后的文档快照状态。
Node.js
exports.updateUser2 = functions.firestore .document('users/{userId}') .onUpdate((change, context) => { // Get an object representing the current document const newValue = change.after.data(); // ...or the previous value before this update const previousValue = change.before.data(); });
您可以像在其他任何对象中一样访问属性。或者,您也可以使用 get
函数访问特定字段:
Node.js
// Fetch data using standard accessors const age = snap.data().age; const name = snap.data()['name']; // Fetch data using built in accessor const experience = snap.get('experience');
写入数据
每个函数调用都与 Firestore 数据库中的特定文档相关联。您可以在为函数返回的快照的 ref
属性中以 DocumentReference
的形式访问该文档。
此 DocumentReference
来自 Firestore Node.js SDK,并且包含 update()
、set()
和 remove()
等方法,因此您可以轻松修改触发了函数的文档。
Node.js
// Listen for updates to any `user` document. exports.countNameChanges = functions.firestore .document('users/{userId}') .onUpdate((change, context) => { // Retrieve the current and previous value const data = change.after.data(); const previousData = change.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 change.after.ref.set({ name_change_count: count + 1 }, {merge: true}); });
触发事件之外的数据
Cloud Functions 函数在受信任的环境中执行,这意味着这些函数被授权为项目的服务账号。您可以使用 Firebase Admin SDK 执行读取和写入操作:
Node.js
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.writeToFirestore = functions.firestore
.document('some/doc')
.onWrite((change, context) => {
db.doc('some/otherdoc').set({ ... });
});
限制
请注意适用于 Cloud Run functions 的 Firestore 触发器的以下限制:
- Cloud Run functions (第 1 代) 前提条件是 Firestore 原生模式的现有“(默认)”数据库。它不支持 Firestore 命名数据库或 Datastore 模式。在这种情况下,请使用 Cloud Run functions (第 2 代) 来配置事件。
- 无法保证顺序。快速更改可能会以意想不到的顺序触发函数调用。
- 事件至少会被传送一次,但单个事件可能会导致多次调用函数。应该避免依赖“正好一次”机制,并编写幂等函数。
- Datastore 模式 Firestore 需要 Cloud Run functions(第 2 代)。Cloud Run functions(第 1 代)不支持 Datastore 模式。
- 一个触发器与单一数据库相关联。您无法创建与多个数据库匹配的触发器。
- 删除数据库不会自动删除该数据库的任何触发器。触发器会停止传送事件,但会继续存在,直到您删除触发器。
- 如果匹配的事件超过请求大小上限,该事件可能不会传送到 Cloud Run functions (第 1 代)。
- 因请求大小而未传送的事件会记录在平台日志中,并计入项目的日志使用量。
- 您可以在 Logs Explorer 中找到这些日志,其严重性为
error
且内容为“由于大小超出第 1 代的限制,因此事件无法传送到 Cloud Functions 函数”消息。您可以在functionName
字段下方找到函数名称。如果receiveTimestamp
字段仍在从现在起的一小时内,您可以利用该时间戳之前和之后的快照来读取相关文档,从而推断实际事件内容。 - 为避免这种情况发生,您可以:
- 迁移和升级到 Cloud Run functions (第 2 代)
- 缩小文档
- 删除相关的 Cloud Run functions 函数
- 您可以使用排除功能关闭日志记录功能本身,但请注意,违规事件仍然不会传送。