Firestore无法查询集合的大小,因此,如果客户端应用需要知道该值,则需要对另一个集合中的文档进行某种维护以保持此计数。但是,它要求客户端正确执行事务,以便在添加和删除文档时使此计数保持最新。恶意或损坏的客户端可能会单独修改集合或计数,并造成计数不准确的情况。
可以通过后端强制客户端强制执行此操作,也可以使用Cloud Functions触发器自动维护计数(延迟一段时间后生效)。但是,我不想引入后端,而宁愿使用安全规则。我该怎么办?
假设您有一个集合“邮件”,其中包含客户可以添加和删除的邮件。还要想象一个不同集合中的文档,该文档的路径为“ 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)
}
}
然后,您可以使用安全规则来要求添加或删除的文档只能与带有count字段的文档同时更改。最少:
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
中的计数增加或减少。messageId
的字段中的“数据”文档中提供要添加或删除的文档的ID。messageId
中标识的新文档在批处理/事务提交之前一定不存在,而在事务处理之后必须存在。messageId
中标识的旧文档必须在批处理/事务提交之前存在,并且在事务处理之后必须不存在。请注意,existsAfter()在事务将[would]完成之后检查命名文档的状态,而exists()在此之前检查它。这两个功能之间的差异对于这些规则的工作方式非常重要。
还请注意,这在重负载下无法很好地扩展。如果添加和删除文档的速度超过每秒10个,则数据文档将超出每个文档的写入速率,并且事务将失败。