Article

文档数据库Firestore

更新于:2026-07-16

第一章:Firestore 基础概念

1.1 什么是 Firestore

概念名称说明注意事项
FirestoreGoogle Firebase 提供的 NoSQL 文档数据库,支持实时同步、离线访问和强一致性不是传统关系型数据库,不支持 JOIN 或 SQL 查询
云托管 vs 本地模拟器可在 Firebase 云端运行,也可通过 Firebase Emulator Suite 在本地开发调试本地模拟器数据不会同步到生产环境
多平台支持支持 Web、iOS、Android、Node.js、Python、Go、Java 等 SDK各平台 API 风格略有差异,但核心模型一致
自动扩展无需手动分片或扩容,Firestore 自动处理大规模读写高频写入同一文档仍可能受限(每秒约 1 次写入限制)

1.2 文档、集合与子集合

概念名称说明注意事项
文档(Document)Firestore 中的基本数据单元,以 JSON 格式存储,有唯一 ID(如 user_123)文档大小上限为 1 MiB
集合(Collection)文档的容器,只能包含文档,不能直接嵌套其他集合集合名必须是字符串,且区分大小写
子集合(Subcollection)文档内部可包含子集合,形成层级结构(如 users/user_123/orders)子集合的存在不依赖父文档是否实际存在(“幽灵文档”)
路径(Path)唯一标识文档或集合的层级路径,如 /users/alice/messages/msg1路径层级必须为奇数(文档)或偶数(集合)

1.3 数据模型与结构特点

概念名称说明注意事项
无模式(Schema-less)每个文档可拥有不同字段,无需预定义结构仍建议在应用层保持一致性以利于查询和维护
嵌套对象支持文档字段可包含嵌套的 Map(对象)结构嵌套深度不限,但整体文档不能超过 1 MiB
数组类型支持数组字段,可用于存储列表(如 tags: [“news”, “tech”])数组内元素不可单独更新,需整体替换或使用 arrayUnion/arrayRemove
时间戳(Timestamp)内置时间类型,比 JavaScript Date 更精确,支持纳秒级在客户端使用 serverTimestamp() 可确保服务端生成时间
引用类型(Reference)可存储指向其他文档的引用(如 author: /users/alice)查询时不能跨文档联查,仅作指针用途

1.4 实时监听机制简介

概念名称说明注意事项
快照监听(Snapshot Listener)客户端可注册监听器,当文档或集合变更时自动触发回调监听会持续消耗读取配额,需及时取消(调用 unsubscribe)
单次读取 vs 实时监听get() 为单次读取,onSnapshot() 为实时监听实时监听首次触发即返回当前快照
本地缓存同步即使离线,监听器仍能基于本地缓存触发回调离线修改会在恢复网络后自动同步到服务器
元数据变化(hasPendingWrites / fromCache)快照包含元数据,可判断数据来源(本地缓存 or 服务端)及是否有待提交写入用于 UI 显示”同步中”状态等场景

第二章:环境准备与项目初始化

2.1 创建 Firebase 项目

步骤名称操作细节注意事项
登录 Firebase 控制台访问 https://console.firebase.google.com/,使用 Google 账号登录需启用 Google 账号的两步验证(推荐)
创建新项目点击”添加项目” → 输入项目名称(如 my-firestore-app)→ 按向导完成创建项目 ID 全局唯一,创建后不可修改
启用 Firestore在控制台左侧菜单选择”Firestore 数据库” → 点击”创建数据库”首次创建时需选择初始模式(测试模式或锁定模式)
选择位置选择 Firestore 数据存储位置(如 nam5 / us-central)位置一旦选定无法更改,影响延迟和合规性
获取项目配置在”项目设置”中可查看 Web 应用配置(apiKey, projectId 等)配置信息用于客户端 SDK 初始化

2.2 安装 Firebase CLI

步骤名称操作细节注意事项
安装 Node.jshttps://nodejs.org 下载并安装 LTS 版本(≥ v18)Firebase CLI 依赖 Node.js 环境
全局安装 Firebase CLI执行命令:npm install -g firebase-tools需确保 npm 全局路径可执行
验证安装执行命令:firebase --version应返回版本号(如 13.x.x)
登录 CLI 账号执行命令:firebase login会打开浏览器授权,需使用与 Firebase 项目相同的 Google 账号
切换项目(可选)执行命令:firebase use --add 添加项目,firebase use 切换多项目开发时常用

2.3 初始化本地 Firestore 项目

步骤名称操作细节注意事项
创建项目目录mkdir my-firestore-app && cd my-firestore-app建议使用独立目录管理
初始化 Firebase 项目执行命令:firebase init交互式命令行工具
选择功能使用空格键选中 “Firestore” 和 “Emulators”(建议同时选)可多选,按回车确认
关联 Firebase 项目从列表中选择已创建的项目(如 my-firestore-app)必须提前在控制台创建
生成配置文件自动生成 firebase.json、firestore.rules、firestore.indexes.json 等文件不要手动删除这些文件
本地目录结构示例├── firebase.json ├── firestore.rules ├── firestore.indexes.json └── public/(若选 Hosting)最小可运行项目只需前三者

2.4 配置安全规则与本地模拟器

步骤名称操作细节注意事项
编辑安全规则文件修改 firestore.rules初始建议设为锁定模式(deny all),逐步开放权限
启动本地模拟器执行命令:firebase emulators:start --only firestore默认监听 localhost:8080
连接应用到模拟器在客户端初始化时添加:connectFirestoreEmulator(db, 'localhost', 8080);仅在开发环境调用,生产环境应移除
查看模拟器 UI启动后访问 http://localhost:4000可查看数据、日志、规则测试等
部署规则到生产环境执行命令:firebase deploy --only firestore:rules需具备项目部署权限
模拟器数据不持久化默认关闭模拟器后数据丢失可通过 —export-on-exit 保存,—import 导入

安全规则示例:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;
    }
  }
}

第三章:Firestore 命令行操作(Firebase CLI)

3.1 启动与管理本地模拟器

操作名称命令语法用途说明注意事项
启动 Firestore 模拟器firebase emulators:start --only firestore仅启动 Firestore 模拟器默认端口 8080,可通过 firebase.json 配置
启动全部模拟器firebase emulators:start同时启动 Firestore、Auth、Functions 等需在 firebase init 时已选择对应服务
指定端口启动firebase emulators:start --only firestore --port 9090自定义 Firestore 模拟器端口避免端口冲突
后台运行并导出数据firebase emulators:start --only firestore --export-on-exit ./data关闭时自动导出数据到 ./data 目录导出格式为 JSON 和元数据文件
导入已有数据启动firebase emulators:start --only firestore --import ./data启动时加载之前导出的数据路径需包含 emulator-export.json
停止模拟器Ctrl + C(终端中)手动终止模拟器进程强制 kill 可能导致数据未保存

3.2 导入与导出数据

操作名称命令语法用途说明注意事项
导出生产环境数据gcloud firestore export gs://[BUCKET]/backup --async将 Firestore 生产数据导出到 GCS 存储桶需安装 Google Cloud SDK 并授权
从 GCS 导入生产数据gcloud firestore import gs://[BUCKET]/backup从 GCS 备份恢复数据到生产环境会覆盖现有同名文档
导出模拟器数据使用 --export-on-exit 参数(见 3.1)保存本地模拟器当前状态仅适用于模拟器,非生产环境
导入到模拟器使用 --import 参数(见 3.1)加载历史数据用于本地测试必须与导出格式一致
查看导出结构(手动查看导出目录)理解数据组织方式目录包含 .json 文件和 firestore_export/ 子目录,不建议手动编辑导出文件

注: Firebase CLI 本身不直接支持生产环境数据导入/导出,需依赖 gcloud 命令(属于 Google Cloud SDK)。

3.3 部署安全规则与索引配置

操作名称命令语法用途说明注意事项
部署安全规则firebase deploy --only firestore:rules将本地 firestore.rules 推送到生产环境规则语法错误会导致部署失败
部署索引配置firebase deploy --only firestore:indexes将 firestore.indexes.json 推送至云端复合查询需提前定义索引
同时部署规则和索引firebase deploy --only firestore一次性部署规则 + 索引推荐做法
预览部署变更firebase deploy --only firestore --dry-run查看将要部署的内容(不实际执行)用于 CI/CD 安全检查
本地规则测试在 Emulator Suite UI 中测试规则无需部署即可验证规则逻辑访问 http://localhost:4000 → Rules 标签页,支持模拟不同用户身份

3.4 查看与调试日志

操作名称命令语法 / 操作方式用途说明注意事项
查看模拟器日志启动模拟器后终端输出实时显示读写、规则拒绝、函数调用等事件日志级别不可配置
访问 Emulator UI浏览器打开 http://localhost:4000图形化查看数据、日志、网络请求、规则测试仅本地有效
启用详细日志设置环境变量 DEBUG=firebase*输出更详细的内部调试信息日志量大,仅开发时使用
查看规则拒绝原因在日志或 Emulator UI 中查找 “PERMISSION_DENIED”定位安全规则拦截的请求结合规则文件逐行排查
清除日志缓存重启模拟器清空历史日志记录无单独清日志命令

第四章:数据操作(CRUD)

4.1 添加文档

方法名称语法用途说明注意事项
addDocaddDoc(collectionRef, data)向集合添加新文档,自动生成唯一 ID文档 ID 由 Firestore 自动生成(20 字符随机字符串)
setDoc(带自定义 ID)setDoc(docRef, data)使用指定 ID 创建文档若 ID 已存在,默认覆盖整个文档;可用 { merge: true } 避免覆盖
setDoc with mergesetDoc(docRef, data, { merge: true })创建或部分更新文档(保留未提及字段)适用于”创建若不存在,否则更新部分字段”场景

代码示例:

// addDoc - 自动生成 ID
import { collection, addDoc } from 'firebase/firestore';
const docRef = await addDoc(collection(db, 'users'), { name: 'Alice', age: 30 });
console.log('Added doc ID:', docRef.id);

// setDoc - 自定义 ID
import { doc, setDoc } from 'firebase/firestore';
await setDoc(doc(db, 'users', 'user_123'), { name: 'Bob' });

// setDoc with merge
await setDoc(doc(db, 'users', 'user_123'), { email: 'bob@example.com' }, { merge: true });

4.2 读取文档与集合

方法名称语法用途说明注意事项
getDocgetDoc(docRef)获取单个文档快照即使文档不存在,也不会抛错,需用 exists() 判断
getDocsgetDocs(queryOrCollectionRef)获取集合或查询结果的所有文档返回 QuerySnapshot,包含多个 DocumentSnapshot
onSnapshot(文档)onSnapshot(docRef, callback)实时监听单个文档变更返回取消函数 unsub(),用于停止监听
onSnapshot(集合)onSnapshot(collectionRef, callback)实时监听整个集合变更可通过 docChanges() 区分 added/modified/removed

代码示例:

// getDoc - 单文档读取
import { doc, getDoc } from 'firebase/firestore';
const docSnap = await getDoc(doc(db, 'users', 'user_123'));
if (docSnap.exists()) console.log(docSnap.data());

// getDocs - 集合读取
import { collection, getDocs } from 'firebase/firestore';
const querySnapshot = await getDocs(collection(db, 'users'));
querySnapshot.forEach(doc => console.log(doc.id, doc.data()));

// onSnapshot - 文档监听
const unsub = onSnapshot(doc(db, 'users', 'user_123'), (doc) => {
  console.log('Current data:', doc.data());
});

// onSnapshot - 集合监听
const unsub = onSnapshot(collection(db, 'users'), (snapshot) => {
  snapshot.docChanges().forEach(change => {
    console.log(change.type, change.doc.data());
  });
});

4.3 更新文档

方法名称语法用途说明注意事项
updateDocupdateDoc(docRef, updateData)更新文档的部分字段仅更新指定字段,其他字段保留;文档必须存在,否则报错
arrayUnionupdateDoc(docRef, { field: arrayUnion(...items) })向数组字段追加不重复元素自动去重,顺序不保证
arrayRemoveupdateDoc(docRef, { field: arrayRemove(...items) })从数组字段移除指定元素仅移除匹配项,不影响其他元素
incrementupdateDoc(docRef, { field: increment(delta) })对数值字段原子增减支持正负数,服务端原子操作,避免竞态条件

代码示例:

// updateDoc - 部分字段更新
import { doc, updateDoc } from 'firebase/firestore';
await updateDoc(doc(db, 'users', 'user_123'), { age: 31 });

// arrayUnion - 数组追加
await updateDoc(doc(db, 'users', 'user_123'), { tags: arrayUnion('premium', 'beta') });

// arrayRemove - 数组移除
await updateDoc(doc(db, 'users', 'user_123'), { tags: arrayRemove('beta') });

// increment - 原子增减
await updateDoc(doc(db, 'users', 'user_123'), { loginCount: increment(1) });

4.4 删除文档与集合

方法名称语法用途说明注意事项
deleteDocdeleteDoc(docRef)删除单个文档删除后文档不可恢复;子集合不受影响(仍存在)
批量删除集合文档使用 getDocs + WriteBatch删除集合内所有文档(无内置方法)Firestore 不支持”删除整个集合”操作,需逐个删除;注意读取配额和批量大小限制(≤500 操作/批)
deleteFieldupdateDoc(docRef, { field: deleteField() })删除文档中的特定字段仅删除字段,保留文档其他内容

代码示例:

// deleteDoc - 删除单个文档
import { doc, deleteDoc } from 'firebase/firestore';
await deleteDoc(doc(db, 'users', 'user_123'));

// 批量删除集合文档
const batch = writeBatch(db);
const snapshot = await getDocs(collection(db, 'temp_logs'));
snapshot.docs.forEach(doc => batch.delete(doc.ref));
await batch.commit();

// deleteField - 删除字段
await updateDoc(doc(db, 'users', 'user_123'), { oldEmail: deleteField() });

⚠️ 重要提示: Firestore 没有提供直接删除整个集合的 API。删除集合需先读取所有文档,再逐个删除(或使用 Cloud Functions + 客户端逻辑)。生产环境中应谨慎操作,避免触发大量读写费用。

第五章:查询与过滤

5.1 基本查询(where、orderBy、limit)

方法名称语法用途说明注意事项
queryquery(collectionRef, ...constraints)构建带条件的查询所有约束必须通过 query() 组合
wherewhere(fieldPath, opStr, value)添加字段过滤条件支持 ==, !=, <, <=, >, >=, in, not-in, array-contains
orderByorderBy(fieldPath, directionStr?)指定排序字段和方向默认升序(asc);若使用 !=not-in,必须对同一字段 orderBy
limitlimit(limitSize)限制返回文档数量常用于分页或性能优化
getDocs + queryawait getDocs(query(...))执行查询并获取结果返回 QuerySnapshot,非实时

代码示例:

import { collection, query, where, orderBy, limit } from 'firebase/firestore';

const q = query(
  collection(db, 'users'),
  where('age', '>', 18),
  orderBy('name'),
  limit(10)
);

const snapshot = await getDocs(q);
snapshot.forEach(doc => console.log(doc.data()));

5.2 复合查询与索引要求

概念/操作名称说明注意事项
单字段索引Firestore 自动为每个字段创建单字段索引(支持基本 where + orderBy)无需手动配置
复合索引当查询包含多个字段过滤或混合过滤+排序时,需手动定义复合索引否则抛出错误并提供索引创建链接
索引配置文件定义在 firestore.indexes.json 中可通过 firebase deploy --only firestore:indexes 部署
复合查询限制不支持对不同字段同时使用范围比较(如 where('a', '>', 1).where('b', '>', 2)必须有一个字段用 ==inarray-contains
错误提示与自动建议查询失败时,错误信息包含”Click here to create index”链接点击可自动生成索引配置并跳转控制台
示例复合查询where('category', '==', 'tech').where('score', '>=', 80).orderBy('score', 'desc')需要复合索引:[category (asc), score (desc)]

💡 索引最佳实践: 尽量用 == 条件缩小范围,再对单一字段做范围或排序,可避免复合索引。

5.3 分页查询(startAt / startAfter)

方法名称语法用途说明注意事项
startAtstartAt(snapshotOrFieldValues...)从指定值或文档快照开始(含该文档)常用于”下一页”
startAfterstartAfter(snapshotOrFieldValues...)从指定值或文档快照之后开始(不含)推荐用于分页,避免重复
基于字段值分页startAfter('2026-01-01')直接传入字段值(需与 orderBy 一致)字段类型必须匹配
获取上一页需反向排序 + startAt + limitToLast(不推荐)Firestore 不原生支持高效”上一页”更佳方案:前端缓存前一页最后文档,或使用双向游标逻辑;避免使用 limitToLast,性能差

代码示例:

// 首页
const first = query(collection(db, 'posts'), orderBy('date'), limit(10));
const firstSnap = await getDocs(first);

// 下一页
const lastVisible = firstSnap.docs[firstSnap.docs.length - 1];
const next = query(
  collection(db, 'posts'),
  orderBy('date'),
  startAfter(lastVisible),
  limit(10)
);

// 基于字段值分页
query(
  collection(db, 'logs'),
  orderBy('timestamp'),
  startAfter(new Date('2026-01-01')),
  limit(50)
);

5.4 数组与嵌套字段查询

方法名称 / 操作语法 / 说明用途说明注意事项
array-containswhere('tags', 'array-contains', 'news')查询数组字段是否包含某元素一次只能查一个值;不支持多个值(用 array-contains-any)
array-contains-anywhere('tags', 'array-contains-any', ['news', 'tech'])查询数组是否包含任一指定元素最多支持 10 个值
inwhere('status', 'in', ['pending', 'approved'])字段值是否在给定列表中最多支持 10 个值
not-inwhere('status', 'not-in', ['deleted', 'spam'])字段值不在给定列表中最多 10 个值;不能与其他 != 共用
嵌套字段查询使用点号访问嵌套字段查询 Map 类型内部字段嵌套路径用字符串表示,如 'user.profile.name'
嵌套字段索引复合查询涉及嵌套字段时需手动创建索引在 firestore.indexes.json 中定义控制台错误提示会引导创建

代码示例:

// array-contains
where('categories', 'array-contains', 'sports')

// array-contains-any
where('roles', 'array-contains-any', ['admin', 'moderator'])

// in / not-in
where('country', 'in', ['US', 'CA', 'MX'])
where('score', 'not-in', [0, -1])

// 嵌套字段查询
where('address.city', '==', 'Beijing')
orderBy('profile.createdAt')

嵌套字段索引示例:

{
  "collectionGroup": "users",
  "queryScope": "COLLECTION",
  "fields": [
    { "fieldPath": "profile.level", "order": "ASCENDING" }
  ]
}

第六章:实时监听与离线支持

6.1 监听单个文档变更

方法名称语法用途说明注意事项
onSnapshotonSnapshot(docRef, onNext, onError?)实时监听单个文档的变更返回取消函数 unsub(),必须在组件卸载时调用以避免内存泄漏
带元数据监听onSnapshot(docRef, { includeMetadataChanges: true }, callback)包含元数据变化(如 fromCache)默认不触发纯元数据变更;开启后可能多次回调相同数据
监听不存在文档onSnapshot(docRef, callback)即使文档不存在也会触发一次回调可用于检测文档是否存在

代码示例:

import { doc, onSnapshot } from 'firebase/firestore';

// 基本监听
const unsub = onSnapshot(
  doc(db, 'users', 'alice'),
  (docSnap) => {
    console.log('Data:', docSnap.data());
  },
  (error) => {
    console.error('Listen failed:', error);
  }
);

// 带元数据监听
onSnapshot(
  doc(db, 'config', 'app'),
  { includeMetadataChanges: true },
  (snap) => {
    if (snap.metadata.hasPendingWrites) console.log('Local change');
  }
);

// 监听不存在文档
onSnapshot(doc(db, 'users', 'nonexistent'), (snap) => {
  if (!snap.exists()) console.log('Doc not found');
});

6.2 监听集合变更

方法名称语法用途说明注意事项
onSnapshot(集合)onSnapshot(collectionRef, onNext, onError?)监听整个集合的增删改docChanges() 返回变更列表,类型为 ‘added’ / ‘modified’ / ‘removed’
带查询监听onSnapshot(query(...), callback)监听带过滤/排序的查询结果查询必须有对应索引,否则监听失败
性能优化建议使用 docChanges() 而非遍历所有 docs避免重复渲染未变更项尤其在大型集合中可显著提升性能

代码示例:

// 集合监听(使用 docChanges 推荐方式)
const unsub = onSnapshot(collection(db, 'messages'), (snapshot) => {
  // 推荐:仅处理变更
  snapshot.docChanges().forEach(change => {
    console.log(change.type, change.doc.id, change.doc.data());
  });
  // 不推荐:遍历全部
  // snapshot.docs.forEach(d => render(d));
});

// 带查询监听
const q = query(
  collection(db, 'posts'),
  where('published', '==', true),
  orderBy('date', 'desc')
);
onSnapshot(q, (snap) => { /* handle */ });

6.3 离线数据持久化配置

操作名称语法 / 配置方式用途说明注意事项
启用本地持久化(Web)enableIndexedDbPersistence(db)enableMultiTabIndexedDbPersistence(db)在浏览器中启用 IndexedDB 缓存Web 默认不启用持久化;需显式调用
多标签页共享缓存enableMultiTabIndexedDbPersistence(db)允许多个浏览器标签页共享同一缓存若已存在单 tab 实例,则失败
移动端(iOS/Android)默认行为无需配置默认启用磁盘持久化Web 是唯一需手动启用的平台
禁用离线写入db.settings({ experimentalForceLongPolling: true })(不推荐)强制仅在线操作一般不建议禁用离线能力,会破坏用户体验

代码示例:

import { enableIndexedDbPersistence } from 'firebase/firestore';

enableIndexedDbPersistence(db).catch(err => {
  if (err.code === 'failed-precondition') {
    console.warn('Multiple tabs open – persistence disabled');
  }
});

⚠️ 注意: Web 端持久化依赖 IndexedDB,受浏览器隐私模式或存储配额限制。若用户清除站点数据,缓存将丢失。

6.4 快照元数据与状态判断

元数据属性说明使用场景注意事项
snap.exists()判断文档是否存在避免读取 null 数据所有 DocumentSnapshot 必须先检查
snap.metadata.fromCache布尔值,表示数据来自本地缓存(true)还是服务端(false)显示”离线中”提示即使联网,首次读取也可能来自缓存
snap.metadata.hasPendingWrites布尔值,表示本地有未同步到服务端的写入显示”同步中”状态仅当启用持久化时有效
QuerySnapshot.metadata集合快照也包含 metadata,但信息有限一般不用于集合级状态判断不推荐依赖集合快照的 metadata
错误处理onError 回调接收 FirebaseError捕获权限拒绝、网络失败等监听失败后不会自动重试,需手动重建

代码示例:

// 状态判断
if (docSnap.exists()) {
  const data = docSnap.data();
}

// 离线判断
if (snap.metadata.fromCache) showOfflineIndicator();

// 同步中判断
if (snap.metadata.hasPendingWrites) showSyncingSpinner();

// 错误处理
onSnapshot(ref, onNext, (err) => {
  if (err.code === 'permission-denied') alert('Access denied');
});

第七章:安全规则(Security Rules)

7.1 规则语法基础

概念/语法元素说明注意事项
rules_version声明规则语言版本(必须为 ‘2’)版本 1 已弃用,新项目必须使用版本 2
service cloud.firestore定义服务类型Firestore 规则必须包裹在此块内
match 语句定义资源路径匹配规则路径必须从 /databases/{database}/documents 开始
通配符 {doc}匹配任意文档 ID通配符变量可在条件中使用(如 userId)
allow 语句授权操作(read, write, create, update, delete)可组合多个操作;write = create + update + delete
return / function支持自定义函数简化逻辑函数必须在 match 块内或全局定义
默认拒绝未明确允许的操作一律拒绝安全第一原则:最小权限开放

规则示例:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow read: if request.auth != null;
      allow write: if request.auth.uid == userId;
    }
  }
}

7.2 身份验证与用户权限控制

概念/表达式说明注意事项
request.auth当前用户认证信息(未登录时为 null)必须集成 Firebase Authentication
request.auth.uid当前用户唯一 ID常用于”仅本人可写”场景
request.auth.tokenJWT 中的自定义声明(如 email, admin)自定义声明需通过 Admin SDK 设置
resource当前文档的当前数据(仅 update/delete 时可用)create 操作中 resource 为 null
request.resource客户端请求中的新数据用于验证写入内容
多层级权限(子集合)在子集合规则中引用父文档 ID利用路径通配符实现层级权限

代码示例:

// 基础认证检查
allow read: if request.auth != null;

// 仅本人可写
allow write: if request.auth.uid == userId;

// 验证邮箱
allow read: if request.auth.token.email_verified == true;

// 仅 owner 可更新
allow update: if resource.data.owner == request.auth.uid;

// 创建时验证 owner
allow create: if request.resource.data.owner == request.auth.uid;

// 子集合层级权限
match /users/{userId}/posts/{postId} {
  allow write: if userId == request.auth.uid;
}

7.3 数据验证与字段约束

验证方法 / 表达式说明注意事项
字段存在性检查使用 in 操作符防止缺失关键字段
类型检查使用 is 关键字支持 int, float, string, bool, timestamp, map, list
字符串长度使用 size()适用于 string 和 list
数值范围直接比较支持链式比较(v2 语法)
数组元素类型遍历或结合函数无法直接验证所有元素类型,需假设或限制长度
嵌套字段验证通过点号访问若字段不存在会报错,需先检查存在性
时间戳验证比较 timestamp 类型request.time 是服务端时间,防客户端伪造

代码示例:

// 字段存在性
allow create: if 'email' in request.resource.data;

// 类型检查
allow create: if request.resource.data.age is int;

// 字符串长度
allow create: if request.resource.data.name.size() <= 50;

// 数值范围(v2 链式比较)
allow create: if request.resource.data.score >= 0 && <= 100;

// 嵌套字段验证
allow create: if request.resource.data.profile.bio.size() > 0;

// 时间戳验证
allow create: if request.resource.data.createdAt == request.time;

// 数组元素验证函数
function isValidTags(tags) {
  return tags is list && tags.size() <= 5 && tags[0] is string;
}

7.4 测试安全规则(使用模拟器)

操作名称操作细节注意事项
启动 Emulator Suite运行命令启动包含 Firestore 和 Auth 的模拟器需已初始化项目
访问 Rules 测试 UI浏览器打开 http://localhost:4000 → 切换到 “Rules” 标签页可实时修改规则并测试
模拟认证用户在测试 UI 中设置 UID、Token 声明等模拟不同角色权限
执行读/写测试选择操作类型(get, set, update, delete),输入文档路径和数据点击”Run”查看是否允许
查看评估日志测试后显示规则逐行评估过程用于调试复杂规则
集成到单元测试使用 @firebase/rules-unit-testing需在测试中连接模拟器

启动命令:

firebase emulators:start --only firestore,auth

单元测试示例:

import { assertFails, assertSucceeds } from '@firebase/rules-unit-testing';

await assertSucceeds(getDoc(doc(db, 'users/alice')));

💡 提示: 所有规则变更必须先在模拟器中测试,再部署到生产环境,避免因规则错误导致应用功能中断。

第八章:性能优化与最佳实践

8.1 索引策略与自动索引

概念/操作名称说明注意事项
单字段索引(自动)Firestore 自动为每个字段创建升序/降序索引,支持基本查询可在 Firebase 控制台禁用某字段的自动索引(节省存储)
复合索引(手动)多字段查询或混合排序需手动定义查询失败时错误信息会提供一键生成链接
覆盖索引(Covering Index)将 orderBy 和 where 字段组合,避免回表提升查询效率,减少读取文档数
索引限制每个项目最多 200 个复合索引设计数据模型时应尽量复用索引
查看索引使用情况在 Firebase 控制台 → Firestore → “索引” 标签页定期清理未使用索引可降低成本

复合索引示例:

查询:where('status','==','active').orderBy('createdAt') → 索引 [status, createdAt]

firestore.indexes.json 示例:

{
  "collectionGroup": "posts",
  "queryScope": "COLLECTION",
  "fields": [
    { "fieldPath": "category", "order": "ASCENDING" },
    { "fieldPath": "score", "order": "DESCENDING" }
  ]
}

8.2 避免大集合全量读取

优化策略说明注意事项
使用分页(limit + startAfter)避免一次性加载数千文档结合 5.3 分页查询实现滚动加载
添加过滤条件缩小范围利用 where 减少返回文档数量利用用户 ID 或状态字段做分区
集合分片(Sharding)将大集合拆分为多个子集合(如 logs_2026_01, logs_2026_02)按时间、租户或哈希值分片;增加查询复杂度
使用聚合文档缓存计数避免 count() 全量扫描(Firestore 不支持原生 count)维护一个 stats 文档,通过事务更新 totalPosts 字段
限制监听范围实时监听时务必加 limit 和 where否则可能监听数万文档,耗尽配额

代码示例:

// 分页查询
const q = query(collection(db, 'logs'), orderBy('ts'), limit(50));

// 过滤条件缩小范围
where('userId', '==', currentUid).where('archived', '==', false)

// 限制监听范围
onSnapshot(
  query(collection(db, 'chat'), where('roomId', '==', 'r1'), limit(100))
);

8.3 批处理与事务操作

方法名称语法用途说明注意事项
writeBatchbatch.set(...); batch.commit();原子性执行多写入(≤500 操作)所有操作要么全成功,要么全失败;不支持读取
runTransactionrunTransaction(db, async (transaction) => { ... })带条件的原子读-改-写(支持读取)事务内读取的是最新快照;重试机制自动处理冲突
批量删除结合 getDocs + writeBatch(见 4.4)安全删除大量文档单次批处理 ≤500 操作;避免在循环中 commit
避免嵌套事务不要在事务中启动新事务Firestore 不支持嵌套事务
事务超时默认 27 秒长时间运行逻辑应拆解到 Cloud Functions

代码示例:

// writeBatch - 原子批量写入
const batch = writeBatch(db);
batch.update(doc(db, 'users/alice'), { posts: increment(1) });
batch.set(doc(db, 'activity/log1'), { action: 'post' });
await batch.commit();

// runTransaction - 带读写的原子操作(如库存扣减)
await runTransaction(db, async (t) => {
  const doc = await t.get(docRef);
  if (doc.data().stock > 0) {
    t.update(docRef, { stock: increment(-1) });
  }
});

8.4 成本控制与用量监控

优化措施说明注意事项
监控读写删除次数Firestore 按操作次数计费(非数据量)在 Firebase 控制台 → “用量” 查看每日操作数;实时监听每次变更计为 1 次读取
避免重复监听同一查询多次 onSnapshot 会重复计费使用全局状态管理(如 React Context)共享监听器
合理设置监听生命周期在组件卸载时调用 unsub()防止内存泄漏和无效计费
使用缓存减少读取启用持久化后,相同查询可能命中本地缓存(fromCache=true)缓存不减少配额,但提升用户体验
避免高频写入同一文档单文档写入限约 1 次/秒计数器场景改用分布式计数(sharded counter)
设置预算告警在 Google Cloud Console 中配置支出限额和邮件告警路径:Billing → Budgets & alerts
模拟器测试成本敏感逻辑在本地验证查询效率和操作次数开发阶段即可优化成本结构

代码示例:

// 正确的监听生命周期管理
useEffect(() => {
  const unsub = onSnapshot(/* ... */);
  return () => unsub(); // 组件卸载时取消
}, []);

第九章:与其他 Firebase 服务集成

9.1 与 Authentication 集成

集成要点说明注意事项
获取当前用户 UID在客户端通过 Auth SDK 获取已认证用户 IDUID 是 Firestore 权限控制的核心依据
在安全规则中使用 request.auth.uid控制文档级访问权限最常见的”仅本人访问”模式
自动创建用户文档用户首次登录时在 Firestore 创建 profile使用 { merge: true } 避免覆盖
自定义声明(Custom Claims)通过 Admin SDK 设置 admin、role 等高级权限声明同步有延迟(约 1 小时),可强制刷新 token
匿名用户转正式用户支持游客体验后升级为注册用户升级后 UID 不变,原有 Firestore 数据保留

代码示例:

// 获取当前用户
import { getAuth, onAuthStateChanged } from 'firebase/auth';

onAuthStateChanged(getAuth(), (user) => {
  if (user) console.log('UID:', user.uid);
});

// 自动创建用户文档
onAuthStateChanged(auth, async (user) => {
  if (user) {
    const userRef = doc(db, 'users', user.uid);
    await setDoc(userRef, { email: user.email }, { merge: true });
  }
});

// 匿名用户升级
const credential = EmailAuthProvider.credential(email, password);
await linkWithCredential(auth.currentUser, credential);

安全规则示例:

// 仅本人访问
match /users/{userId} {
  allow read, write: if request.auth.uid == userId;
}

// 管理员才可删除
allow delete: if request.auth.token.admin == true;

Admin SDK 设置自定义声明:

// 后端(Cloud Functions)
admin.auth().setCustomUserClaims(uid, { admin: true });

9.2 与 Cloud Functions 联动

集成方式说明注意事项
触发器:onCreate监听文档创建事件路径支持通配符 {uid};自动重试失败函数
触发器:onUpdate / onDelete监听更新或删除可访问 before/after 快照对比差异
在函数中写入 Firestore使用 Admin SDK 绕过安全规则Admin SDK 拥有完全权限,需谨慎操作
调用 HTTP 函数更新数据客户端通过 HTTPS 调用函数间接写入适用于复杂业务逻辑或跨服务协调
避免无限循环函数内写入可能再次触发监听必须设计防重机制

代码示例:

// onCreate 触发器
import { onDocumentCreated } from 'firebase-functions/v2/firestore';

export const logUserSignup = onDocumentCreated('users/{uid}', (event) => {
  console.log('New user:', event.params.uid);
});

// onUpdate 触发器
onDocumentUpdated('orders/{id}', (event) => { /* 处理状态变更 */ });

// Admin SDK 写入
import { getFirestore } from 'firebase-admin/firestore';
const db = getFirestore();
await db.doc('stats/daily').update({ signups: increment(1) });

// 客户端调用 HTTP 函数
fetch('https://.../createOrder', { method: 'POST', body: JSON.stringify(data) });

// 避免无限循环
if (!change.after.data()?.processed) {
  // 执行逻辑
  await setDoc(docRef, { processed: true }, { merge: true });
}

9.3 与 Storage 协同使用

协同场景说明注意事项
存储文件并记录引用上传文件到 Storage,将下载 URL 或引用路径存入 Firestore推荐存储 Storage 引用路径(如 gs://bucket/…),而非公开 URL
在安全规则中联动验证Storage 规则可引用 Firestore 文档状态需启用 Firestore 规则中的 firestore.get() 权限
自动生成缩略图并更新元数据通过 Cloud Functions 监听 Storage 上传,处理图像后写入 Firestore典型 Serverless 图像处理流程
删除文件时清理 Firestore 引用避免遗留无效引用建议使用事务或函数保证一致性
公开 vs 私有访问控制根据业务决定是否生成公开 URL敏感文件应通过 Storage 规则 + Auth 控制访问

代码示例:

// 上传文件并存储引用
const storageRef = ref(storage, `avatars/${uid}.jpg`);
await uploadBytes(storageRef, file);
await updateDoc(userDoc, { avatar: storageRef.toString() });

// 获取临时公开链接(默认 7 天)
const url = await getDownloadURL(storageRef);

Storage 规则联动 Firestore 示例:

match /avatars/{userId}/{fileName} {
  allow write: if resource.metadata.userId == request.auth.uid &&
    firestore.get(/databases/(default)/documents/users/$(userId)).data.status == 'active';
}

删除流程建议:

1. 从 Firestore 移除 avatar 字段
2. 调用 deleteObject(storageRef)

第十章:生产部署与运维

10.1 从模拟器迁移到生产环境

步骤名称操作细节注意事项
停用本地模拟器连接移除或注释客户端代码中的 useEmulator() 调用否则客户端会尝试连接本地地址,导致生产环境失败
部署安全规则使用 CLI 将本地规则推送到生产环境确保规则已通过模拟器充分测试
部署索引配置同步复合索引定义缺失索引会导致查询失败
导入初始数据(可选)若有种子数据,使用 gcloud 或脚本写入避免在客户端首次启动时批量写入(触发高费用)
验证生产连接在真实设备或浏览器中测试读写操作检查 Firebase 项目 ID 是否正确(非 emulated)
环境隔离策略使用不同 Firebase 项目区分 dev/staging/prod防止测试数据污染生产库

命令示例:

# 部署规则
firebase deploy --only firestore:rules

# 部署索引
firebase deploy --only firestore:indexes

# 导入初始数据
gcloud firestore import gs://my-bucket/initial-data

# 切换环境
firebase use prod-project-id

客户端代码调整:

// 开发时(上线前需移除)
// connectFirestoreEmulator(db, 'localhost', 8080);

10.2 版本控制与 CI/CD 集成

操作名称操作细节注意事项
规则与索引纳入 Git将 firestore.rules 和 firestore.indexes.json 加入版本控制这是基础设施即代码(IaC)的核心
使用 .firebaserc 切换环境定义多个项目别名支持团队统一环境管理
CI/CD 中部署规则在 GitHub Actions / GitLab CI 中执行部署需提前生成 CI 专用部署令牌(firebase login:ci)
自动化测试集成在 CI 流程中运行规则单元测试使用 @firebase/rules-unit-testing 库
部署前校验(dry-run)预览变更而不实际部署用于 PR 检查或人工审核前验证
回滚机制手动恢复旧版规则文件并重新部署Firestore 不提供自动版本回滚,需依赖 Git 历史

配置文件示例:

// .firebaserc
{
  "projects": {
    "dev": "myapp-dev",
    "prod": "myapp-prod"
  }
}

CI/CD 示例(GitHub Actions):

- name: Deploy Firestore Rules
  run: firebase deploy --only firestore --token ${{ secrets.FIREBASE_TOKEN }}

常用命令:

# 纳入 Git
git add firestore.rules firestore.indexes.json

# 部署前预览
firebase deploy --only firestore --dry-run

# 回滚
git checkout v1.2 -- firestore.rules && firebase deploy --only firestore:rules

10.3 监控、告警与错误排查

监控/操作项工具 / 路径注意事项
查看实时用量Firebase Console → Project Overview → “Usage” tab免费层级有配额限制,超限将拒绝请求
设置预算与支出告警Google Cloud Console → Billing → Budgets & alerts告警阈值建议设为 50%/90%
分析慢查询Firestore 控制台 → “Monitoring” → “Slow queries”(需启用)需提前在项目设置中开启监控
错误日志收集在 onSnapshot onError 或 try/catch 中上报错误到 Crashlytics 或 Sentry关键错误应包含用户 ID、文档路径、错误码
常见错误码排查错误信息通常包含修复建议链接
使用 Cloud LoggingGoogle Cloud Console → Logging需启用 Firestore 审计日志(默认不开启)
性能基准测试编写 Node.js 脚本批量执行典型查询建议在 staging 环境进行

常见错误码及排查:

  • PERMISSION_DENIED:检查安全规则和 Auth 状态
  • UNAVAILABLE:网络或服务中断
  • FAILED_PRECONDITION:索引缺失

Cloud Logging 查询示例:

resource.type="firestore_document"