想象一下,你有一个“消息”集合,其中包含客户端可以添加和删除的消息。还要想象一个不同集合中的文档,路径为“messages stats/data”,字段为“count”,用于维护消息中文档的准确计数。如果客户端应用程序执行这样的事务以添加文档:
async function addDocumentTransaction() {
try {
const ref = firestore.collection("messages").doc()
const statsRef = firestore.collection("messages-stats").doc("data")
await firestore.runTransaction(transaction => {
transaction.set(ref, {
foo: "bar"
})
transaction.update(statsRef, {
count: firebase.firestore.FieldValue.increment(1),
messageId: ref.id
})
return Promise.resolve()
})
console.log(`Added message ${ref.id}`)
}
catch (error) {
console.error(error)
}
}
或者像这样的批次:
async function addDocumentBatch() {
try {
const batch = firestore.batch()
const ref = firestore.collection("messages").doc()
const statsRef = firestore.collection("messages-stats").doc("data")
batch.set(ref, {
foo: "bar"
})
batch.update(statsRef, {
count: firebase.firestore.FieldValue.increment(1),
messageId: ref.id
})
await batch.commit()
console.log(`Added message ${ref.id}`)
}
catch (error) {
console.error(error)
}
}
像这样使用事务删除文档:
async function deleteDocumentTransaction(id) {
try {
const ref = firestore.collection("messages").doc(id)
const statsRef = firestore.collection("messages-stats").doc("data")
await firestore.runTransaction(transaction => {
transaction.delete(ref)
transaction.update(statsRef, {
count: firebase.firestore.FieldValue.increment(-1),
messageId: ref.id
})
return Promise.resolve()
})
console.log(`Deleted message ${ref.id}`)
}
catch (error) {
console.error(error)
}
}
或者像这样处理一批:
async function deleteDocumentBatch(id) {
try {
const batch = firestore.batch()
const ref = firestore.collection("messages").doc(id)
const statsRef = firestore.collection("messages-stats").doc("data")
batch.delete(ref)
batch.update(statsRef, {
count: firebase.firestore.FieldValue.increment(-1),
messageId: ref.id
})
await batch.commit()
console.log(`Deleted message ${ref.id}`)
}
catch (error) {
console.error(error)
}
}
然后,您可以使用安全规则要求添加或删除的文档只能与具有计数字段的文档同时更改。最低限度:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /messages/{id} {
allow read;
allow create: if
getAfter(/databases/$(database)/documents/messages-stats/data).data.count ==
get(/databases/$(database)/documents/messages-stats/data).data.count + 1;
allow delete: if
getAfter(/databases/$(database)/documents/messages-stats/data).data.count ==
get(/databases/$(database)/documents/messages-stats/data).data.count - 1;
}
match /messages-stats/data {
allow read;
allow update: if (
request.resource.data.count == resource.data.count + 1 &&
existsAfter(/databases/$(database)/documents/messages/$(request.resource.data.messageId)) &&
! exists(/databases/$(database)/documents/messages/$(request.resource.data.messageId))
) || (
request.resource.data.count == resource.data.count - 1 &&
! existsAfter(/databases/$(database)/documents/messages/$(request.resource.data.messageId)) &&
exists(/databases/$(database)/documents/messages/$(request.resource.data.messageId))
);
}
}
}
请注意,客户必须:
-
递增或递减计数
/messages-stats/data
在添加或删除文档时。
-
必须在名为的字段中提供在“数据”文档中添加或删除的文档的id
messageId
.
-
递增计数要求中标识的新文档
messageId
在批处理/事务提交之前不得存在,在事务提交之后必须存在。
-
减少计数要求中标识的旧文档
messageId
必须在批处理/事务提交之前存在,在事务提交之后不存在。
请注意
existsAfter()
在事务处理后检查指定文档的状态
将
完成,同时
exists()
之前检查过。这两个函数之间的区别对这些规则的工作方式很重要。
还要注意,在重载下,这将无法很好地扩展。如果文档的添加和删除速度超过每秒10个,则数据文档的每个文档写入速率将超过,事务将失败。
一旦你有了这个,现在你实际上可以编写安全规则来限制集合的大小,如下所示:
match /messages/{id} {
allow create: if
get(/databases/$(database)/documents/messages-stats/data).data.count < 5;
}