借助 Cloud Functions,您可以处理 Firebase Realtime Database 中的事件,而无需更新客户端代码。Cloud Functions 让您能以完整的管理员权限执行 Realtime Database 操作,并可确保对 Realtime Database 所做的每项更改都得到单独处理。您可以通过 DataSnapshot
或 Admin SDK 执行 Firebase Realtime Database 更改。
在典型生命周期中,Firebase Realtime Database 函数会执行以下操作:
- 等待对特定 Realtime Database 位置执行的更改。
- 在事件发生时触发并执行相应的任务(如需查看更多使用场景示例,请参阅 Cloud Functions 有哪些用途?)。
- 接收相关数据对象,该对象包含了存储在指定文档中的数据的快照。
触发 Realtime Database 函数
您可以使用 functions.database
为 Realtime Database 事件创建新函数。如需控制函数何时触发,请指定一个事件处理程序,并指定要监听事件的 Realtime Database 路径。
设置事件处理程序
函数可让您以两种不同的监听范围处理 Realtime Database 事件;您可以只监听创建、更新或删除事件,也可以监听某个路径上任何类型的任何更改。 对于 Realtime Database,Cloud Functions 支持以下事件处理程序:
onWrite()
:当 Realtime Database 中发生数据创建、更新或删除事件时触发。onCreate()
:当 Realtime Database 中发生新数据创建事件时触发。onUpdate()
:当 Realtime Database 中发生数据更新事件时触发。onDelete()
:当 Realtime Database 中发生数据删除事件时触发。
指定实例和路径
如需控制函数的触发时间和位置,请调用 ref(path)
以指定路径,并视需要使用 instance('INSTANCE_NAME')
指定 Realtime Database 实例。如果您没有指定实例,函数将部署到 Firebase 项目的默认 Realtime Database 实例。例如:
- 默认 Realtime Database 实例:
functions.database.ref('/foo/bar')
- 名为“my-app-db-2”的实例:
functions.database.instance('my-app-db-2').ref('/foo/bar')
这些方法用于指示您的函数处理在 Realtime Database 实例中特定路径下的写入操作。根据路径规范,涉及该路径的所有写入操作均匹配(包括在此路径下任何位置发生的写入操作)。如果您将函数的路径设置为 /foo/bar
,则以下两个位置发生的事件均匹配:
/foo/bar
/foo/bar/baz/really/deep/path
不管是哪种情况,Firebase 都会解读为事件是在 /foo/bar
下发生的,且事件数据包括 /foo/bar
下的旧数据和新数据。如果事件数据可能很大,请考虑在更深的路径下使用多个函数,而不是在靠近数据库根目录处使用单个函数。为了获得最佳性能,请仅在尽可能深的路径层级中发出数据请求。
您可以用大括号括住路径的某个部分,将其指定为通配符;ref('foo/{bar}')
会匹配 /foo
的任何子路径。这些通配符路径组成部分的值可从函数的 EventContext.params
对象中获得。在此示例中,该值通过 context.params.bar
提供。
包含通配符的路径可以匹配单个写入操作引发的多个事件。 下列代码的一次插入
{
"foo": {
"hello": "world",
"firebase": "functions"
}
}
会匹配路径 "/foo/{bar}"
两次:第一次是 "hello": "world"
,第二次是 "firebase": "functions"
。
处理事件数据
处理 Realtime Database 事件时,返回的数据对象是一个 DataSnapshot
。对于 onWrite
或 onUpdate
事件,第一个参数是一个 Change
对象,其中包含代表触发事件前后的数据状态的两个快照。对于 onCreate
和 onDelete
事件,返回的数据对象是已创建或已删除的数据的快照。
在以下示例中,函数会检索指定路径的快照,将该位置中的字符串转换为大写,并将修改后的字符串写入数据库:
// Listens for new messages added to /messages/:pushId/original and creates an // uppercase version of the message to /messages/:pushId/uppercase exports.makeUppercase = functions.database.ref('/messages/{pushId}/original') .onCreate((snapshot, context) => { // Grab the current value of what was written to the Realtime Database. const original = snapshot.val(); functions.logger.log('Uppercasing', context.params.pushId, original); const uppercase = original.toUpperCase(); // You must return a Promise when performing asynchronous tasks inside a Functions such as // writing to the Firebase Realtime Database. // Setting an "uppercase" sibling in the Realtime Database returns a Promise. return snapshot.ref.parent.child('uppercase').set(uppercase); });
访问用户身份验证信息
在 EventContext.auth
和 EventContext.authType
中,您可以访问触发函数的用户的信息(包括权限)。这对于实施安全规则很有用,可以让您的函数根据用户的权限级别完成不同的操作:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
exports.simpleDbFunction = functions.database.ref('/path')
.onCreate((snap, context) => {
if (context.authType === 'ADMIN') {
// do something
} else if (context.authType === 'USER') {
console.log(snap.val(), 'written by', context.auth.uid);
}
});
此外,您可以利用用户身份验证信息来“模拟”用户并代表用户执行写入操作。请务必按如下所示删除该应用实例,以防出现并发问题:
exports.impersonateMakeUpperCase = functions.database.ref('/messages/{pushId}/original')
.onCreate((snap, context) => {
const appOptions = JSON.parse(process.env.FIREBASE_CONFIG);
appOptions.databaseAuthVariableOverride = context.auth;
const app = admin.initializeApp(appOptions, 'app');
const uppercase = snap.val().toUpperCase();
const ref = snap.ref.parent.child('uppercase');
const deleteApp = () => app.delete().catch(() => null);
return app.database().ref(ref).set(uppercase).then(res => {
// Deleting the app is necessary for preventing concurrency leaks
return deleteApp().then(() => res);
}).catch(err => {
return deleteApp().then(() => Promise.reject(err));
});
});
读取先前的值
Change
对象有一个 before
属性,可用于检查在事件发生之前保存在 Realtime Database 中的内容。before
属性会返回一个 DataSnapshot
,其中所有方法(例如 val()
和 exists()
)都会引用先前的值。您可以通过以下两种方法再次读取新的值:使用原始 DataSnapshot
或读取 after
属性。任何 Change
对象的此属性都是另一个 DataSnapshot
,代表事件发生后的数据状态。
例如,before
属性可用于确保函数仅在第一次创建记录时将文本改为大写:
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
.onWrite((change, context) => {
// Only edit data when it is first created.
if (change.before.exists()) {
return null;
}
// Exit when the data is deleted.
if (!change.after.exists()) {
return null;
}
// Grab the current value of what was written to the Realtime Database.
const original = change.after.val();
console.log('Uppercasing', context.params.pushId, original);
const uppercase = original.toUpperCase();
// You must return a Promise when performing asynchronous tasks inside a Functions such as
// writing to the Firebase Realtime Database.
// Setting an "uppercase" sibling in the Realtime Database returns a Promise.
return change.after.ref.parent.child('uppercase').set(uppercase);
});