mcpField
F
firebase-cloud-firestore

firebase-cloud-firestore

github.com/evanca数据与数据库663186 次安装3 周前更新
代码仓库

关于 firebase-cloud-firestore

面向 Flutter 应用开发者的 Cloud Firestore 实施指南,涵盖数据结构设计、查询、实时更新、安全规则和扩展实践。

主要功能

  1. 01规划文档与子集合结构
  2. 02筛选查询与游标分页
  3. 03批量写入和事务
  4. 04实时监听器
  5. 05索引、热点与流量建议
  6. 06安全规则和输入验证

适用场景

  1. 01设计分层应用数据
  2. 02为筛选后的集合查询分页
  3. 03原子更新多个文档
  4. 04通过安全规则限制访问

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/evanca/flutter-ai-rules --skill firebase-cloud-firestore

安装后在 Claude Code 中输入/firebase-cloud-firestore确认已加载。

详细介绍

Firebase Cloud Firestore 技能

此技能介绍如何在 Flutter 应用中正确实现 Cloud Firestore,涵盖数据建模、查询、实时更新、安全规则和规模优化。

适用场景

在以下情况下使用此技能:

  • 在 Flutter 项目中设置和配置 Cloud Firestore。
  • 设计文档和集合结构,或规划子集合。
  • 执行读取、写入、批量写入或事务操作。
  • 实现实时监听器或分页查询。
  • 针对扩展进行优化并避免写入热点。
  • 编写或调试 Firestore 安全规则。

1. 数据库选择

当应用需要以下功能时,选择 Cloud Firestore:

  • 包含子集合的丰富分层数据模型。
  • 复杂查询:串联筛选条件,或组合按属性筛选和排序。
  • 可从数据库任意位置以原子方式读取和写入数据的事务。
  • 高可用性(典型正常运行时间为 99.999%)或关键级别的可靠性。
  • 自动扩展以支持数百万并发用户。

如果只需要简单的数据模型、简单查找以及极低延迟的同步(典型响应时间低于 10 ms),则改用 Realtime Database。


2. 设置和配置

flutter pub add cloud_firestore
import 'package:cloud_firestore/cloud_firestore.dart';

final db = FirebaseFirestore.instance; // after Firebase.initializeApp()

位置:

  • 选择距离用户和计算资源最近的数据库位置。
  • 关键应用使用多区域位置(可获得最高可用性和持久性)。
  • 需要降低成本和写入延迟时,使用区域位置。

iOS/macOS: 可考虑使用预编译框架来缩短构建时间:

pod 'FirebaseFirestore',
  :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git',
  :tag => 'IOS_SDK_VERSION'

离线持久化默认在移动设备上启用。配置缓存大小:

FirebaseFirestore.instance.settings = const Settings(
  persistenceEnabled: true,
  cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED,
);

3. 文档结构

  • 避免使用 . 和 .. 作为文档 ID(它们在 Firestore 路径中有特殊含义)。
  • 避免在文档 ID 中使用正斜杠(/)(它是路径分隔符)。
  • 不要使用单调递增的文档 ID(例如 Customer1、Customer2)——这会导致写入热点。
  • 尽可能使用 Firestore 的自动文档 ID:
final docRef = await db.collection("users").add({
  'name': 'Ada Lovelace',
  'email': '[email protected]',
  'created_at': FieldValue.serverTimestamp(),
});
print('Created document with ID: ${docRef.id}');
  • 避免在字段名称中使用以下字符(需要额外转义):. [ ] * `
  • 使用文档中的子集合来组织复杂的分层数据,而不是使用深度嵌套的对象。

4. 索引

  • Firestore 查询默认会建立索引;查询性能取决于结果集大小,而非数据集大小。
  • 设置集合级索引豁免,以降低写入延迟和存储成本。
  • 对不需要降序或数组索引的字段禁用这些索引。
  • 对不用于查询的长字符串字段设置索引豁免。
  • 如果不在查询中使用具有连续值(例如时间戳)的字段,则对其设置索引豁免——这样可以避免每秒 500 次写入的索引限制。
  • 对 TTL 字段添加单字段索引豁免。
  • 对不用于查询的大型数组或映射字段设置索引豁免——这样可以避免每个文档 40,000 个索引条目的限制。

5. 读取和写入操作

读取集合中的所有文档
final querySnapshot = await db.collection("users").get();
for (var doc in querySnapshot.docs) {
  print("${doc.id} => ${doc.data()}");
}
使用筛选条件查询
final query = db.collection("users")
    .where("age", isGreaterThanOrEqualTo: 18)
    .orderBy("age")
    .limit(20);

final results = await query.get();
基于游标的分页
// First page
final first = db.collection("cities").orderBy("name").limit(25);
final firstSnapshot = await first.get();

// Next page using last document as cursor
final lastDoc = firstSnapshot.docs.last;
final next = db.collection("cities")
    .orderBy("name")
    .startAfterDocument(lastDoc)
    .limit(25);
  • 不要使用偏移量进行分页——使用游标,以避免检索被跳过的文档并为其付费。
使用服务器时间戳写入
await db.collection("users").doc("user_1").set({
  'name': 'Grace Hopper',
  'updated_at': FieldValue.serverTimestamp(),
});
批量写入(原子操作,最多 500 次操作)
final batch = db.batch();
batch.set(db.collection("cities").doc("LA"), {'name': 'Los Angeles'});
batch.update(db.collection("cities").doc("SF"), {'population': 860000});
batch.delete(db.collection("cities").doc("OLD"));
await batch.commit();
事务
await db.runTransaction((transaction) async {
  final snapshot = await transaction.get(db.collection("counters").doc("visits"));
  final currentCount = snapshot.get("count") as int;
  transaction.update(snapshot.reference, {"count": currentCount + 1});
});
  • 将独立操作(例如文档查找和查询)并行执行,而不是按顺序执行。
  • 注意写入速率限制:每个文档每秒约 1 次写入。
  • 需要写入大量文档时,使用批量写入器,而不是原子批量写入器。

6. 面向规模进行设计

  • 避免以很高的读写频率访问按字典序相近的文档(热点问题)。
  • 避免以很高的频率创建包含单调递增字段(例如时间戳)的新文档。
  • 避免以很高的频率删除集合中的文档。
  • 以很高的频率向数据库写入时,逐步增加流量——用 5 分钟逐渐提升。
  • 避免查询跳过最近删除的数据——使用 start_at 查找正确的起始点。
  • 将写入分散到不同的文档路径,以避免争用。
  • Firestore 会自动扩展至约 100 万个并发连接和每秒 10,000 次写入。

7. 实时更新

final subscription = db.collection("messages")
    .where("room", isEqualTo: "general")
    .orderBy("timestamp", descending: true)
    .limit(50)
    .snapshots()
    .listen((querySnapshot) {
      for (var change in querySnapshot.docChanges) {
        switch (change.type) {
          case DocumentChangeType.added:
            print("New message: ${change.doc.data()}");
            break;
          case DocumentChangeType.modified:
            print("Modified: ${change.doc.data()}");
            break;
          case DocumentChangeType.removed:
            print("Removed: ${change.doc.id}");
            break;
        }
      }
    });

// Detach when no longer needed:
subscription.cancel();
  • 限制同时运行的实时监听器数量。
  • 不再需要时分离监听器,以避免内存泄漏和不必要的读取。
  • 使用复合查询在服务器端筛选数据,而不是在客户端筛选。
  • 对于大型集合,使用查询限制所监听的数据量——切勿监听整个大型集合。

8. 安全

  • 始终使用 Firebase 安全规则保护 Firestore 数据。
  • 除非使用通配符,否则安全规则不会级联。
  • 如果查询结果可能包含用户无权访问的数据,整个查询都会失败。

用户自有文档的规则示例:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow read, update, delete: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null;
    }
  }
}
  • 在提交到 Firestore 之前验证用户输入,以防止注入攻击。
  • 对需要原子性更新多个文档的操作使用事务。
  • 为所有 Firestore 操作实施适当的错误处理。
  • 未设置适当访问控制时,切勿在 Firestore 中存储敏感信息。

参考资料

评论

支持 Markdown