Cloud Firestore 对子集合的规则

Cloud Firestore rules on subcollection

我正在开发一个 iOS 应用程序,它具有(哇哦,惊喜!)聊天功能。整个应用程序大量使用 Firebase 工具,对于数据库,我使用新的 Cloud Firestore 解决方案。

目前我正在使用数据库规则加强安全性,但我对自己的数据模型有点吃力 :) 这可能意味着我的数据模型选择不当,但我真的很满意,除了实施规则部分。

模型的对话部分如下所示。在我的数据库的根目录下,我有一个 conversations 集合:

/conversations/$conversationId
        - owner // id of the user that created the conversation
        - ts // timestamp when the conversation was created
        - members: {
                $user_id_1: true // usually the same as 'owner'
                $user_id_2: true // the other person in this conversation
                ...
          }
        - memberInfo: {
                // some extra info about user typing, names, last message etc.
                ...
          }

然后我在每个对话中都有一个名为 messages 的子集。消息文档非常简单,只包含有关每条已发送消息的信息。

/conversations/$conversationId/messages/$messageId
        - body
        - sender
        - ts

以及模型截图:

对话文档中的规则相当简单易行:

match /conversations/{conversationId} {
  allow read, write: if resource.data.members[(request.auth.uid)] == true;

  match /messages/{messageId} {
        allow read, write: if get(/databases/$(database)/documents/conversations/$(conversationId)).data.members[(request.auth.uid)] == true;
  }
}

问题

我的问题出在该对话中的消息子集上。上面的工作,但我不喜欢在那里使用 get() 调用。 每个 get() 调用都会执行读取操作,因此会影响我月底的账单,请参阅 documentation

如果我正在构建的应用程序成功,这可能会成为一个问题,文档读取当然是非常少的,但是每次用户打开对话时都这样做似乎有点低效。我非常喜欢我模型中的子集合解决方案,但不确定如何有效地实现这里的规则。

我愿意接受任何数据模型更改,我的目标是在没有这些 get() 调用的情况下评估规则。任何想法都非常受欢迎。

老实说,我认为您对自己的结构没问题,get 按原样调用。原因如下:

  1. 如果您要获取子集合中的一堆文档,Cloud Firestore 通常足够智能,可以根据需要缓存值。例如,如果您要求获取 "conversions/chat_abc/messages" 中的所有 200 个项目,Cloud Firestore 将只执行一次该获取操作并将其重新用于整个批处理操作。所以你最终会得到 201 次读取,而不是 400 次。

  2. 一般来说,我不喜欢在您的安全规则中针对定价进行优化。是的,您最终可能会在每次操作中进行一两次额外的读取,但这可能不会像编写糟糕的 Cloud Function 那样给您带来麻烦。这些是您最好优化的领域。

如果你想保存那些额外的读取,你实际上可以在custom claims.

的基础上实现一个"cache"

例如,您可以将用户有权访问的聊天记录保存在对象 "conversations" 下的自定义声明中。请记住,自定义声明的限制为 1000 字节,如其文档中所述。

限制的一个解决方法是只保存自定义声明中的最近对话,例如前 50 个。然后在安全规则中,您可以这样做:

allow read, write: if request.auth.token.conversations[conversationId] || get(/databases/$(database)/documents/conversations/$(conversationId)).data.members[(request.auth.uid)] == true;

如果您已经在使用云功能在发布消息后审核消息,这尤其有用,您只需更新自定义声明