You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firestore安全规则实现合理性及模拟器卡顿问题咨询

Firestore安全规则模拟器卡顿问题排查与优化

问题描述

我编写了如下Firestore安全规则用于校验用户文档的读写操作。当在Firebase控制台的模拟器中执行创建文档操作时,Chrome标签页会冻结超过90秒,直到我关闭模拟结果。这让我怀疑当前的规则实现方式是否不符合其设计初衷。烦请告知我的实现是否符合实际使用及预期用途;若代码可行,如何在模拟器中运行时避免标签页冻结?

规则代码

rules_version = '2';
match /users/{userId} {
  allow get: if isSignedIn();
  allow list: if isSignedIn() && request.query.limit <= 50;
  allow create: if request.auth.uid == userId
    && request.get('resource', null) != null
    && !isUserDeleted(request.resource.data) 
    && !isUserBanned(request.resource.data)
    && isAffectedKeysValid(request.resource.data.keys())
    && isIncomingDataValid();
  allow update: if request.auth.uid == userId
    && request.get('resource', null) != null
    && !isUserDeleted(request.resource.data) 
    && !isUserBanned(request.resource.data)
    && isAffectedKeysValid(getAffectedKeys())
    && isIncomingDataValid();

  function isUserDeleted(userData) {
    return userData.get('deleted', false) == true;
  }

  function isUserBanned(userData) {
    return userData.get('permanently_banned', false) == true 
      || (userData.get('ban_expire_time', '') is timestamp 
            && userData.ban_expire_time >= request.time);
  }
  
  function isAffectedKeysValid(affectedKeys) {
    return affectedKeys.hasOnly([
      'id',
      'first_name',
      'birthday',
      'about',
      'images',
      'country',
      'geohash_5',
      'geohash_6',
      'last_seen',
      'app_locale',
      'deleted'
      ]);
  }

  function isIncomingDataValid() {
    let incomingData = request.resource.data;
    return incomingData.get('id', '') is string 
      && incomingData.get('id', userId) == userId 
      && incomingData.get('first_name', '') is string
      && incomingData.first_name.size() <= 100
      && incomingData.get('birthday', request.time) is timestamp
      && incomingData.get('birthday', request.time) <= request.time
      && incomingData.get('birthday', request.time) >= timestamp.date(1900,1,1)
      && incomingData.get('about', '') is string
      && incomingData.get('about', '').size() <= 700
      && incomingData.get('images', []) is list
      && incomingData.get('images', '').size() <= 6
      && incomingData.get('country', '') is string
      && incomingData.get('country', '').size() <= 2
      && incomingData.get('geohash_5', '') is string
      && incomingData.get('geohash_5', '').size() <= 5
      && incomingData.get('geohash_6', '') is string
      && incomingData.get('geohash_6', '').size() <= 6
      && incomingData.get('last_seen', request.time) is timestamp
      && incomingData.get('last_seen', request.time) <= request.time
      && incomingData.get('app_locale', '') is string
      && incomingData.get('app_locale', '').size() <= 2
      && incomingData.get('deleted', false) is bool;
  }

  function getAffectedKeys() {
    return request.resource.data.diff(resource.data).affectedKeys();
  }
}

注:我编写了350行Firestore安全规则以保护数据库中的各类文档,它们的格式基本一致,因此仅展示了校验用户文档读写的部分。单独运行上述代码时冻结时长较短,但仍会出现明显卡顿。

规则实现合理性分析

你的规则逻辑本身符合Firestore安全规则的设计初衷:

  • 权限校验(request.auth.uid == userId)确保只有用户本人能操作自己的文档,符合最小权限原则
  • 字段合法性校验(isIncomingDataValid)和字段范围限制(isAffectedKeysValid)能防止非法数据写入,保障数据一致性
  • 状态校验(isUserDeleted、isUserBanned)能限制异常状态用户的操作,逻辑合理

但规则的实现细节存在优化空间,这也是导致模拟器卡顿的主要原因。

模拟器卡顿的优化方案

1. 简化isIncomingDataValid中的重复调用

当前代码中多次重复调用incomingData.get('xxx', defaultValue),比如birthday连续调用3次,这会增加规则引擎的计算量。可以将重复获取的值存入变量,减少重复计算:

function isIncomingDataValid() {
  let incomingData = request.resource.data;
  let birthday = incomingData.get('birthday', request.time);
  let lastSeen = incomingData.get('last_seen', request.time);
  let about = incomingData.get('about', '');
  let images = incomingData.get('images', []);
  
  return incomingData.get('id', '') is string 
    && incomingData.get('id', userId) == userId 
    && incomingData.get('first_name', '') is string
    && incomingData.first_name.size() <= 100
    && birthday is timestamp
    && birthday <= request.time
    && birthday >= timestamp.date(1900,1,1)
    && about is string
    && about.size() <= 700
    && images is list
    && images.size() <= 6
    && incomingData.get('country', '') is string
    && incomingData.get('country', '').size() <= 2
    && incomingData.get('geohash_5', '') is string
    && incomingData.get('geohash_5', '').size() <= 5
    && incomingData.get('geohash_6', '') is string
    && incomingData.get('geohash_6', '').size() <= 6
    && lastSeen is timestamp
    && lastSeen <= request.time
    && incomingData.get('app_locale', '') is string
    && incomingData.get('app_locale', '').size() <= 2
    && incomingData.get('deleted', false) is bool;
}

2. 移除不必要的request.get('resource', null) != null校验

对于create操作,request.resource必然存在,不需要额外判断;对于update操作,只要是合法的更新请求,request.resource也一定存在。这个判断属于冗余逻辑,会增加不必要的计算步骤。

3. 优化isAffectedKeysValid的调用时机

在create操作中,request.resource.data.keys()就是所有写入的字段,此时可提前判断字段数量是否在预期范围内(最多11个字段),减少后续集合比较的开销:

// 在create的allow条件中添加
&& request.resource.data.keys().size() <= 11

4. 拆分大型规则文件

350行的单规则文件会让模拟器加载和计算时压力骤增。建议按集合拆分规则,比如将users、posts、comments等集合的规则分别放在不同的文件中,通过import引入主规则文件,降低单文件的复杂度。

5. 模拟器运行时的临时缓解措施

  • 每次只测试单个集合的规则:测试users集合时,暂时注释其他集合的规则代码,减少计算量
  • 使用Firebase CLI的规则模拟器代替控制台模拟器:CLI版本的模拟器性能通常优于网页版,可通过firebase emulators:start --only firestore启动
  • 关闭Chrome的其他标签页:减少浏览器资源占用,缓解冻结情况

内容的提问来源于stack exchange,提问作者user123

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 21:35:02