您如何强制 Firestore 客户端应用维护集合的正确文档计数? [英] How do you force a Firestore client app to maintain a correct document count for a collection?

查看:17
本文介绍了您如何强制 Firestore 客户端应用维护集合的正确文档计数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Firestore 无法查询集合的大小,因此如果客户端应用程序需要知道此值,则需要对另一个集合中的文档进行一些维护以保存此计数.但是,它要求客户端正确执行事务,以便在添加和删除文档时保持此计数为最新.恶意或损坏的客户端可以独立修改集合或计数,并造成计数不准确的情况.

Firestore doesn't have a way to query for the size of a collection, so if a client app needs to know this value, it requires some maintenance of a document in another collection to hold this count. However, it requires that the client to correctly perform a transaction in order to keep this count up to date while adding and removing documents. A malicious or broken client could modify either the collection or the count independently, and create a situation where the count isn't accurate.

可以通过后端强制客户端强制执行此操作,或使用 Cloud Functions 触发器自动维护计数(这将在一段时间后生效).但是,我不想引入后端,我宁愿使用安全规则.我该怎么做?

It's possible to force clients through a backend to enforce this, or use a Cloud Functions trigger to automatically maintain the count (which will take effect after some delay). However, I don't want to introduce a backend, and I'd rather use security rules. How can I do this?

推荐答案

假设您有一个消息"集合,其中包含客户端可以添加和删除的消息.还可以想象不同集合中的文档,其路径为messages-stats/data",其中包含一个名为count"的字段,用于维护消息中文档的准确计数.如果客户端应用程序执行这样的事务来添加文档:

Imagine you have a collection "messages" that holds messages that clients can add and remove. Also imagine a document in a different collection with the path "messages-stats/data" with a field called "count" that maintains an accurate count of the documents in messages. If the client app performs a transaction like this to add a document:

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)
    }
}

像这样使用事务删除文档:

And like this to delete a document using a transaction:

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)
    }
}

然后您可以使用安全规则来要求添加或删除的文档只能与具有计数字段的文档同时更改.最低限度:

Then you can use security rules to require that both the document being added or removed can only be changed at the same time as the document with the count field. Minimally:

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 中标识的旧文档必须在批处理/事务提交之前存在,并且在事务之后不得存在.
  • Increment or decrement the count in /messages-stats/data while adding or removing a document.
  • Must provide the id of the document being added or removed in the "data" document in a field called messageId.
  • Incrementing the count requires that the new document identified in messageId must not exist before the batch/transaction commits, and exist after transaction.
  • Decrementing the count requires that the old document identified in messageId must exist before the batch/transaction commits, and must not exist after the transaction.

请注意,existsAfter() 检查状态交易完成后指定文档的,而 exists() 之前检查过它.这两个函数之间的差异对于这些规则的工作方式很重要.

Note that existsAfter() checks the state of the named document after the transaction would complete, while exists() checks it before. The difference between these two functions is important to how these rules work.

另请注意,这在重负载下无法很好地扩展.如果文档的添加和删除速度超过每秒 10 次,则会超过数据文档的每文档写入速率,事务将失败.

Also note that this will not scale well under heavy load. If documents are being added and removed faster than 10 per second, the per-document write rate will be exceeded for the data document, and the transaction will fail.

一旦你有了这个,现在你可以真正编写安全规则来限制集合的大小,如下所示:

Once you have this in place, now you can actually write security rules to limit the size of a collection like this:

match /messages/{id} {
  allow create: if
    get(/databases/$(database)/documents/messages-stats/data).data.count < 5;
}

这篇关于您如何强制 Firestore 客户端应用维护集合的正确文档计数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆