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

查看:26
本文介绍了您如何强制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)
    }
}

然后,您可以使用安全规则来要求添加或删除的文档只能与带有count字段的文档同时更改.最少:

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()检查状态交易完成后指定文档的名称,而

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天全站免登陆