您如何强制Firestore客户端应用维护集合的正确文档数?

问题描述 投票:1回答:1

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

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

firebase google-cloud-firestore firebase-security
1个回答
2
投票

假设您有一个集合“邮件”,其中包含客户可以添加和删除的邮件。还要想象一个不同集合中的文档,该文档的路径为“ 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个,则数据文档将超出每个文档的写入速率,并且事务将失败。

© www.soinside.com 2019 - 2024. All rights reserved.