Представьте, что у вас есть коллекция "сообщений", которая содержит сообщения, которые клиенты могут добавлять и удалять. Также представьте документ в другой коллекции с путем «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
при добавлении или удалении документа. - Должен предоставить идентификатор документа, добавляемого или удаляемого в документе «data» в поле с именем
messageId
. - Для увеличения счетчика необходимо, чтобы новый документ, указанный в
messageId
, не существовал до партии / транзакции фиксирует и существует после транзакции. - Для уменьшения счетчика требуется, чтобы старый документ, указанный в
messageId
, существовал до фиксации пакета / транзакции и не существовал после транзакции.
Обратите внимание, что existAfter () проверяет состояние названного документа после завершения транзакции , в то время как exist () проверяет его раньше. Разница между этими двумя функциями важна для того, как работают эти правила.
Также обратите внимание, что это не будет хорошо масштабироваться при большой нагрузке. Если документы добавляются и удаляются быстрее, чем 10 в секунду, для документа данных будет превышена скорость записи для каждого документа, и транзакция завершится неудачей.
После того, как это будет сделано, теперь вы можете фактически напишите правила безопасности, чтобы ограничить размер коллекции следующим образом:
match /messages/{id} {
allow create: if
get(/databases/$(database)/documents/messages-stats/data).data.count < 5;
}