概览
Firebase Firestore 是 Google 的全托管 NoSQL 文档数据库,核心卖点是实时监听——数据变更时自动推送到所有在线客户端。深度集成 Firebase 生态(Auth、Cloud Functions、Hosting),无需管理基础设施。支持离线持久化、结构化查询、多区域复制。适合聊天应用、协作工具、移动 App 等需要实时同步的场景。
Firebase Firestore 是 Google 的全托管 NoSQL 文档数据库,核心卖点是实时监听——数据变更时自动推送到所有在线客户端。深度集成 Firebase 生态(Auth、Cloud Functions、Hosting),无需管理基础设施。支持离线持久化、结构化查询、多区域复制。适合聊天应用、协作工具、移动 App 等需要实时同步的场景。
| 要求 | 说明 |
|---|---|
| Google 账号 | 注册 https://firebase.google.com/ |
| Node.js | 16+(用于 Firebase CLI) |
| 开发框架 | Web(JS SDK)、Android、iOS、Flutter、Unity 等 |
| Firebase 项目 | 在 Firebase Console 创建 |
| 计费 | Spark 免费计划(1 GB 存储 + 每日 5 万读/2 万写/2 万删) |
Firestore 是云端托管服务,无需安装数据库。本地开发用 Firebase Emulator。
# 安装 CLI
npm install -g firebase-tools
# 登录
firebase login
# 初始化项目
firebase init firestore
# npm
npm install firebase
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_PROJECT.firebaseapp.com",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_PROJECT.appspot.com",
messagingSenderId: "YOUR_SENDER_ID",
appId: "YOUR_APP_ID"
};
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
# 安装 emulator
firebase init emulators
# 启动
firebase emulators:start --only firestore
# 配置 SDK 连接 emulator
import { connectFirestoreEmulator } from 'firebase/firestore';
connectFirestoreEmulator(db, 'localhost', 8080);
pip install google-cloud-firestore
# 认证
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
默认安全规则禁止所有读写。修改 firestore.rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.time < timestamp.date(2025, 1, 1);
}
}
}
部署:firebase deploy --only firestore:rules
终端浏览器无法打开时使用:
firebase login --no-localhost
默认退出后数据丢失。持久化:
firebase emulators:start --only firestore --export-on-exit=./emulator_data
Firestore 支持中文 ID,但推荐使用英文 slug 或自动生成的 ID 以保证兼容性。
连接 Firestore,创建聊天消息集合并实现实时监听——新增消息自动出现在客户端。
<!DOCTYPE html>
<html>
<head>
<title>Firestore 聊天室</title>
</head>
<body>
<h2>实时聊天室</h2>
<div id="messages" style="height:300px;overflow-y:auto;border:1px solid #ccc;padding:10px;margin-bottom:10px;"></div>
<input id="msgInput" placeholder="输入消息..." style="width:300px;">
<button onclick="sendMessage()">发送</button>
<script type="module">
import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.12.0/firebase-app.js';
import { getFirestore, collection, addDoc, query, orderBy, limit, onSnapshot, serverTimestamp } from 'https://www.gstatic.com/firebasejs/10.12.0/firebase-firestore.js';
// == 1. 初始化 ==
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_PROJECT.firebaseapp.com",
projectId: "YOUR_PROJECT_ID",
};
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
// == 2. 发送消息 ==
window.sendMessage = async function() {
const input = document.getElementById('msgInput');
const text = input.value.trim();
if (!text) return;
await addDoc(collection(db, 'messages'), {
text: text,
user: '匿名用户',
createdAt: serverTimestamp()
});
input.value = '';
};
// == 3. 实时监听 ==
const messagesRef = collection(db, 'messages');
const q = query(messagesRef, orderBy('createdAt', 'desc'), limit(50));
onSnapshot(q, (snapshot) => {
const container = document.getElementById('messages');
container.innerHTML = '';
snapshot.docChanges().forEach(change => {
if (change.type === 'added') {
const msg = change.doc.data();
const div = document.createElement('div');
div.textContent = `[${msg.user}] ${msg.text}`;
container.prepend(div);
}
});
});
</script>
</body>
</html>
// npm install firebase-admin
const { initializeApp, cert } = require('firebase-admin/app');
const { getFirestore, Timestamp, FieldValue } = require('firebase-admin/firestore');
initializeApp({ credential: cert('./serviceAccountKey.json') });
const db = getFirestore();
// 写入消息
async function sendMessage(user, text) {
const docRef = await db.collection('messages').add({
user: user,
text: text,
createdAt: FieldValue.serverTimestamp(),
});
console.log(`消息 ID: ${docRef.id}`);
return docRef;
}
// 实时监听(服务端订阅)
function listenForMessages() {
const unsubscribe = db.collection('messages')
.orderBy('createdAt', 'desc')
.limit(10)
.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if (change.type === 'added') {
const msg = change.doc.data();
console.log(`[新消息] ${msg.user}: ${msg.text}`);
}
});
});
// 取消监听:unsubscribe();
}
// 查询历史
async function getMessages(limit = 20) {
const snapshot = await db.collection('messages')
.orderBy('createdAt', 'desc')
.limit(limit)
.get();
const messages = [];
snapshot.forEach(doc => {
messages.push({ id: doc.id, ...doc.data() });
});
return messages;
}
// 运行
sendMessage('Alice', '大家好!');
getMessages().then(msgs => console.log(msgs));
# pip install firebase-admin
import firebase_admin
from firebase_admin import credentials, firestore
from google.cloud.firestore_v1 import SERVER_TIMESTAMP
cred = credentials.Certificate('./serviceAccountKey.json')
firebase_admin.initialize_app(cred)
db = firestore.client()
# 写入
doc_ref = db.collection('messages').document()
doc_ref.set({
'user': 'Bob',
'text': 'Hello from Python!',
'createdAt': SERVER_TIMESTAMP
})
print(f"消息 ID: {doc_ref.id}")
# 查询
docs = db.collection('messages').order_by('createdAt', direction='DESCENDING').limit(10).stream()
for doc in docs:
print(f"{doc.to_dict()['user']}: {doc.to_dict()['text']}")
# 实时监听(需长时间运行的脚本)
def on_snapshot(col_snapshot, changes, read_time):
for change in changes:
if change.type.name == 'ADDED':
print(f"[实时] {change.document.to_dict()['user']}: {change.document.to_dict()['text']}")
col_ref = db.collection('messages')
watch = col_ref.on_snapshot(on_snapshot)
# watch.unsubscribe() # 停止监听
消息 ID: abc123...
[新消息] Alice: 大家好!
[新消息] Bob: Hello from Python!
// 实时监听:当有新消息时自动打印
[实时] Carol: 我也在!
onSnapshot 是 Firestore 的核心:变更实时推送serverTimestamp() 避免客户端时间不同步docChanges() 区分 add/modify/remove 事件Firestore 不是传统"请求-响应"数据库。它的灵魂是实时监听:
客户端 A ──写入──▶ Firestore ──推送──▶ 客户端 B、C、D
这使其天然适合:聊天、协作文档、实时仪表盘、游戏状态同步。
collection (集合)
└── document (文档)
├── field: value
├── nested_object: { ... }
└── subcollection (子集合)
└── document
⚠️ 与 Realtime Database 不同:Firestore 支持丰富的查询(where / orderBy / limit),Realtime Database 是 JSON 树。
多人在线白板:用户可在画布上放置便签(Sticky Note),其他用户实时看到。便签支持拖拽移动和文字编辑。
/boards/{boardId}
- name: "小组讨论"
- createdBy: "user123"
/boards/{boardId}/notes/{noteId}
- text: "会议主题:项目进度"
- x: 150 (像素坐标)
- y: 200
- color: "#FFD700"
- createdBy: "user123"
- updatedAt: Timestamp
import { getFirestore, collection, doc, setDoc, addDoc, onSnapshot, updateDoc, deleteDoc, serverTimestamp } from 'firebase/firestore';
const db = getFirestore();
const boardId = 'board_001';
// 创建画板
await setDoc(doc(db, 'boards', boardId), {
name: '小组讨论',
createdBy: 'user123',
createdAt: serverTimestamp()
});
// 核心:实时监听便签变更
function subscribeNotes(boardId, callback) {
const notesRef = collection(db, 'boards', boardId, 'notes');
return onSnapshot(notesRef, (snapshot) => {
snapshot.docChanges().forEach(change => {
const note = { id: change.doc.id, ...change.doc.data() };
switch (change.type) {
case 'added':
callback('add', note);
break;
case 'modified':
callback('update', note);
break;
case 'removed':
callback('remove', note);
break;
}
});
});
}
// 使用
const unsubscribe = subscribeNotes(boardId, (type, note) => {
if (type === 'add') {
renderStickyNote(note); // 创建便签 DOM
} else if (type === 'update') {
updateStickyNote(note); // 更新位置/文字
} else if (type === 'remove') {
removeStickyNote(note.id); // 删除 DOM
}
});
// 添加便签(任何用户)
async function addNote(text, x, y) {
await addDoc(collection(db, 'boards', boardId, 'notes'), {
text: text,
x: x,
y: y,
color: getRandomColor(),
createdBy: currentUser.uid,
updatedAt: serverTimestamp()
});
}
// 拖拽移动(实时同步坐标)
async function moveNote(noteId, newX, newY) {
await updateDoc(doc(db, 'boards', boardId, 'notes', noteId), {
x: newX,
y: newY,
updatedAt: serverTimestamp()
});
}
// 编辑文字
async function editNote(noteId, newText) {
await updateDoc(doc(db, 'boards', boardId, 'notes', noteId), {
text: newText,
updatedAt: serverTimestamp()
});
}
// 删除便签
async function deleteNote(noteId) {
await deleteDoc(doc(db, 'boards', boardId, 'notes', noteId));
}
// firestore.rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /boards/{boardId} {
allow read: if request.auth != null;
allow create: if request.auth != null;
match /notes/{noteId} {
allow read, write: if request.auth != null;
// 验证:便签坐标不能为负
allow update: if request.resource.data.x >= 0
&& request.resource.data.y >= 0;
}
}
}
}
import { enableIndexedDbPersistence } from 'firebase/firestore';
// 启用离线持久化(IndexedDB 缓存)
await enableIndexedDbPersistence(db);
// 离线时本地写入,联网后自动同步
// 无需额外代码!
docChanges() 返回的 change.type 有哪几种?modified 和 added 什么时候会被同时触发?