这篇文章将帮助您,亲爱的前端开发人员,了解所有关于 Firebase 的知识。 我们将涵盖许多关于 Firebase 是什么、为什么它对您有用以及如何使用它的示例的详细信息。 但首先,我认为您会喜欢一个关于 Firebase 如何诞生的故事。
八年前,Andrew Lee 和 James Tamplin 正在构建一个名为 Envolve 的实时聊天创业公司。 该服务大受欢迎。 它被用于像 Ricky Martin 和 Limp Bizkit 这样的名人网站上。 Envolve 是为那些不想——再次——构建自己的聊天小部件的开发人员准备的。 该服务的价值来自 **设置的简便性和消息传递的速度**。 Envolve 只是一个聊天小部件。 过程很简单。 在页面上放置一个脚本标签。 一旦小部件启动,它就会为您完成所有工作。 这就像拥有一个已经设置好的聊天消息数据库和服务器一样。
James 和 Andrew 发现随着服务越来越受欢迎,出现了一种奇怪的趋势。 一些开发人员会在页面上包含该小部件,但将其隐藏起来。 为什么有人想要一个带有隐藏消息的聊天小部件? 好吧,不仅仅是聊天数据在设备之间发送。 它是游戏数据、高分、应用程序设置、待办事项,或者开发人员需要快速发送和同步的任何内容。 开发人员会监听小部件中的新消息,并使用它们来同步其应用程序中的状态。 这是一个无需后端即可创建实时体验的简单方法。
这对 Firebase 联合创始人来说是一个灵光乍现的时刻。 如果开发人员可以做的 **比发送聊天消息更多** 呢? 如果他们有一个服务可以快速开发和扩展他们的应用程序呢? 移除管理后端基础设施的责任。 **专注于前端**。 这就是 Firebase 的诞生方式。
什么是 Firebase?
Firebase 不是一个数据库吗? 不...是的...主要不是。 Firebase 是一个提供开发人员基础设施和营销人员工具的平台。 但它并不总是这样。
七年前,Firebase 是一款单一产品:实时云数据库。 今天,Firebase 是 **19 种产品** 的集合。 每个产品都旨在增强应用程序基础设施的一部分。 Firebase 还让您深入了解应用程序的性能、用户的行为以及如何改善整体应用程序体验。 虽然 Firebase 可以构成应用程序后端的全部内容,但您也可以单独使用每种产品。
以下仅是这 19 种产品的示例
- 托管: 为每个 GitHub pull 请求部署新版本的网站。
- Firestore: 构建无需服务器即可实时工作的应用程序,即使在离线状态下也能正常运行。
- 身份验证: 使用多种提供商验证和管理用户。
- 存储: 管理用户生成的内容,例如照片、视频和 GIF。
- 云函数: 由事件驱动的服务器代码(例如,记录创建、用户注册等)。
- 扩展: 预先打包的功能,使用 UI 设置(例如,Stripe 支付、文本翻译等)。
- Google Analytics: 了解用户活动,按细分和受众进行组织。
- 远程配置: 带有动态条件的键值存储,非常适合功能门控。
- 性能监控: 来自实际使用情况的页面加载指标和自定义跟踪。
- 云消息传递: 跨平台推送通知。
哇! 很多,我甚至没有列出其他九种产品。 没关系。 没有必要使用所有特定服务,甚至不止一个。 但是现在是时候让这些服务更切实可行,并展示您可以使用 Firebase 做什么了。
学习的最佳方法是查看它是否正常工作。 以下第一个部分将帮助您设置 Firebase 服务。 随后的部分将突出显示演示应用程序的 Firebase 详细信息,以展示如何使用 Firebase 功能。 虽然这是一个关于 Firebase 的相当全面的指南,但它不是一个分步教程。 目的是为了突出显示嵌入式演示中的工作部分,以便在这篇文章中涵盖更多内容。 如果您想要分步的 Firebase 教程,请留言让我写一个。
基本的 Firebase 设置
如果您打算使用您自己的 Firebase 后端分叉演示,此部分很有帮助。 如果您熟悉 Firebase 项目或只想查看闪亮的演示,可以跳过此部分。
Firebase 是一种基于云的服务,这意味着您需要在使用其服务之前进行一些基本帐户设置。 但是,Firebase 开发不依赖于网络连接。 值得注意的是,您可以(并且通常应该)在本地机器上运行 Firebase 进行开发。 本指南演示了使用 CodePen 构建应用程序,这意味着它需要一个连接到云的服务。 这里目标是使用 Firebase 创建您的个人后端,然后检索前端连接到它所需的配置。
创建一个 Firebase 项目
转到 Firebase 控制台。 您将被询问是否要设置 Google Analytics。 这些示例都没有使用它,因此您可以跳过它,并且可以在需要时随时添加回来。
创建一个 Web Firebase 应用程序
接下来,您将看到创建“应用程序”的选项。 点击 Web 选项并为其命名——任何名称都可以。 Firebase 项目可以包含多个“应用程序”。 我不会深入研究这种层次结构,因为它在入门时并不重要。 一旦应用程序创建完成,您将获得一个配置对象。

let firebaseConfig = {
apiKey: "your-key",
authDomain: "your-domain.firebaseapp.com",
projectId: "your-projectId",
storageBucket: "your-projectId.appspot.com",
messagingSenderId: "your-senderId",
appId: "your-appId",
measurementId: "your-measurementId"
};
这是您将在前端使用的配置来连接到 Firebase。 不要担心这些属性中的任何安全问题。 **在您的前端代码中包含这些属性没有任何不安全之处。** 您将在以下部分之一中了解 Firebase 中的安全机制如何工作。
现在是时候用代码来表示您创建的这个“应用程序”了。 这个“应用程序”仅仅是一个容器,它在不同的 Firebase 服务之间共享逻辑和身份验证状态。 Firebase 提供了一套库,使开发变得更加容易。 在这个示例中,我将从 CDN 使用它们,但它们也能很好地与 Webpack 和 Rollup 等模块捆绑器一起使用。
// This pen adds Firebase via the "Add External Scripts" option in codepen
// https://www.gstatic.com/firebasejs/8.2.10/firebase-app.js
// https://www.gstatic.com/firebasejs/8.2.10/firebase-auth.js
// Create a Project at the Firebase Console
// (console.firebase.google.com)
let firebaseConfig = {
apiKey: "your-key",
authDomain: "your-domain.firebaseapp.com",
projectId: "your-projectId",
storageBucket: "your-projectId.appspot.com",
messagingSenderId: "your-senderId",
appId: "your-appId",
measurementId: "your-measurementId"
};
// Create your Firebase app
let firebaseApp = firebase.initializeApp(firebaseConfig);
// The auth instance
console.log(firebaseApp.auth());
太好了! 您离与您自己的 Firebase 后端对话只有一步之遥了。 现在您需要启用您打算使用的服务。
启用身份验证提供商
下面的示例使用身份验证来登录用户并保护数据库中的数据。 当您创建一个新的 Firebase 项目时,您的所有身份验证提供商都处于关闭状态。 这最初很麻烦,但对于安全性至关重要。 您不希望用户尝试使用您的后端不支持的提供商登录。

要打开提供商,请转到侧边导航栏中的 **身份验证** 选项卡,然后点击顶部的“登录方法”按钮。 在下面,您将看到一个大型的提供商列表,例如电子邮件和密码、Google、Facebook、GitHub、Microsoft 和 Twitter。 对于下面的示例,您需要打开 Google 和匿名。 Google 位于列表顶部附近,匿名位于底部。 选择 Google 将要求您提供支持电子邮件,我建议在测试时使用您自己的个人电子邮件,但生产应用程序应该使用专门的电子邮件。
如果您计划在 CodePen 中使用身份验证,那么您还需要将 CodePen 添加为 **授权域**。 您可以在 **“登录方法”** 选项卡底部添加授权域。

关于此授权的一个重要说明:这将允许托管在cdpn.io
上的任何项目登录您的 Firebase 项目。对于短期演示目的,风险并不大。使用 Firebase Auth 免费,除了电话号码身份验证。理想情况下,如果您计划在生产环境中使用此应用程序,则不应将此作为授权域保留。
现在,让我们继续 Firebase 控制台中的最后一步:创建 Firestore 数据库!
创建 Firestore 数据库
点击左侧导航栏中的**Firestore**。从这里,您需要点击按钮创建 Firestore 数据库。系统会询问您是否要以“生产模式”或“测试模式”启动。在此示例中,您需要选择“测试模式”。如果您担心安全问题,我们将在最后一部分讨论。

现在您已经了解了基础知识,让我们开始一些实际用例。
身份验证用户
应用程序中最好的部分通常隐藏在注册表单后面。为什么我们不直接让用户以访客身份登录,让他们自己看看呢?系统通常需要帐户,因为后端并非为访客设计。系统要求拥有一个神圣的userId
属性来保存属于用户的任何记录。这就是“访客”帐户的用处。它们为用户提供低摩擦的加入方式,同时为他们提供一个临时的userId
,以满足系统的要求。Firebase Auth 拥有一个专门用于此目的的过程。
设置匿名身份验证
我最喜欢的 Firebase Auth 功能之一是匿名身份验证。有两个好处。首先,您可以无需任何输入(例如密码、电话号码等)即可验证用户。其次,您可以非常熟练地拼写匿名。这确实是一个双赢的局面。
以这个 CodePen 为例。
这是一个表单,允许用户决定他们是否想使用 Google 登录或以访客身份登录。此示例中的大部分代码都特定于用户界面。Firebase 部分实际上非常简单。
// Firebase-specific code
let firebaseConfig = { /* config */ };
let firebaseApp = firebase.initializeApp(firebaseConfig);
// End Firebase-specific code
let socialForm = document.querySelector('form.sign-in-social');
let guestForm = document.querySelector('form.sign-in-guest');
guestForm.addEventListener('submit', async submitEvent => {
submitEvent.preventDefault();
let formData = new FormData(guestForm);
let displayName = formData.get('name');
let photoURL = await getRandomPhotoURL();
// Firebase-specific code
let { user } = await firebaseApp.auth().signInAnonymously();
await user.updateProfile({ displayName, photoURL });
// End Firebase-specific code
});
在上面的 17 行代码(不包括注释)中,只有 5 行是与 Firebase 相关的。需要 4 行代码进行导入和配置。需要 2 行代码来执行signInAnonymously()
和user.updateProfile()
。第一个登录方法调用您的 Firebase 后端并验证用户。调用返回一个包含所需属性的结果,例如uid
。即使对于访客用户,您也可以使用此uid
将数据与后端中的用户关联。用户登录后,示例会调用用户对象上的updateProfile
。即使此用户是访客,他们仍然可以拥有显示名称和个人资料照片。
设置 Google 身份验证
好消息是,对于所有其他永久提供商,例如电子邮件和密码、Google、Facebook、Twitter、GitHub、Microsoft、电话号码等等,它的工作原理完全相同。实施Google 登录也只需要几行代码。
socialForm.addEventListener('submit', submitEvent => {
submitEvent.preventDefault();
// Firebase-specific code
let provider = new firebase.auth.GoogleAuthProvider();
firebaseApp.auth().signInWithRedirect(provider);
// End Firebase-specific code
});
每种社交风格提供商都会启动基于重定向的身份验证流程。这是一种比较花哨的说法,表示signInWithRedirect
方法将转到提供商拥有的登录页面,然后返回到您的应用程序,并附带已验证的用户。在这种情况下,用户会被重定向到 Google 的登录页面,登录后会返回到您的应用程序。
监控身份验证状态
您如何从此重定向中获取用户?有几种方法,但我会选择最常用的方法。您可以检测任何活动用户的身份验证状态,无论他们已登录还是已退出。
firebaseApp.auth().onAuthStateChanged(user => {
if(user != null) {
console.log(user.toJSON());
} else {
console.log("No user!");
}
});
onAuthStateChanged
方法在用户身份验证状态发生变化时更新。它会在页面加载时首次触发,告知您用户是否已登录、登录或退出。这使您可以构建一个对这些状态变化做出反应的 UI。它也非常适合客户端路由器,因为它可以使用少量代码将用户重定向到正确的页面。
在这种情况下,应用程序仅使用<template>
标记替换“phone”元素的内容。如果用户已登录,应用程序将路由到新的模板。
<div class="container">
<div class="phone">
<!-- Phone contents replaced with template tags -->
</div>
</div>
这提供了一个简单的关系。.phone
是“根视图”。每个<template>
标记都是一个“子视图”。身份验证状态决定显示哪个视图。
firebaseApp.auth().onAuthStateChanged(user => {
if(user != null) {
// Show demo view
routeTo("demo", firebaseApp, user);
} else {
console.log("No user!");
// Show log in page
routeTo("signIn", firebaseApp);
}
});
嵌入式演示不是“生产就绪”,因为其要求只是在单个 CodePen 中工作。目标是使其易于阅读,每个“视图”都包含在模板标记内。这松散地模拟了框架中常见的路由解决方案的外观。
这里需要注意的一点是,用户对象被传递到routeTo
函数。获取登录状态是异步的。我发现将用户状态传递到视图中比在视图中进行异步调用要容易得多。许多框架路由器都有一个用于这种异步数据获取的地方。
将访客转换为永久用户
<template id="demo">
标记有一个提交按钮,用于将访客转换为永久用户。这是通过让用户使用主要提供商(在本例中为 Google)登录来完成的。
let convertForm = document.querySelector('form.convert');
convertForm.addEventListener("submit", submitEvent => {
submitEvent.preventDefault();
let provider = new firebase.auth.GoogleAuthProvider();
firebaseApp.auth().currentUser.linkWithRedirect(provider);
});
使用linkWithRedirect
方法将把用户踢出以使用社交提供商进行身份验证。重定向返回后,帐户将被合并。您无需更改控制“子视图”路由的onAuthStateChanged
方法中的任何内容。需要注意的是,uid
保持不变。
处理帐户合并错误
合并帐户时可能会出现一些边缘情况。在某些情况下,用户可能已经使用 Google 创建了一个帐户,现在正尝试合并该现有帐户。您可以根据您希望应用程序的工作方式来处理这种情况,方法有很多。我不会深入探讨,因为我们还有很多内容要介绍,但了解如何处理这些错误很重要。
async function checkForRedirect() {
let auth = firebaseApp.auth();
try {
let result = await auth.getRedirectResult();
}
catch (error) {
switch(error.code) {
case 'auth/credential-already-in-use': {
// You can check for the provider(s) in use
let providers = await auth.fetchProvidersForEmail(error.email);
// Then decide what strategy to take. A possible strategy is
// notifying the user and asking them to sign in as that account
}
}
}
}
上面的代码使用getRedirectResult
方法检测用户是否已直接从社交登录重定向返回。如果是,则结果中包含很多信息。在这里,最重要的是我们要知道是否存在问题。Firebase Auth 将抛出错误并提供有关错误的相关信息,例如电子邮件和凭据,这将使您可以继续合并帐户。但这并不总是您想要做的事情。在这种情况下,我可能会提示用户该帐户已存在,并提示他们使用该帐户登录。但说真的,我可以讨论登录表单好几个小时。
现在,让我们开始构建数据可视化(好吧,只是一个饼图)并为其提供实时数据流。
设置数据可视化
说实话,我不知道这个应用程序到底有什么用。我只想试着使用conic-gradient
构建饼图。我非常兴奋地使用 CSS 自定义属性来更改图表的值,并将其与数据库(如 Firestore)实时同步。让我们稍微绕一下路,讨论一下conic-gradient
的工作原理。
conic-gradient
是一个令人惊讶地得到良好支持的CSS 功能。其关键特征是其颜色停止点位于圆周上。
.pie-chart {
background-image: conic-gradient(
purple 10%, /* 10% of the circumference */
magenta 0 20%, /* start at 0 go 20%, acts like 10% */
cyan 0 /* Fill the rest */
);
}
您可以使用一些简单的数学运算构建一个饼图:lastStopPercent + percent
。我在应用程序笔中将这些值存储在四个 CSS 自定义属性中:--pie-{n}-value
(将n
替换为数字)。这些值用于另一个自定义属性,该属性充当计算函数。
:root {
--pie-1-value: 10%;
--pie-2-value: 10%;
--pie-3-value: 80%;
--pie-1-computed: var(--pie-1-value);
--pie-2-computed: 0 calc(var(--pie-1-value) + var(--pie-2-value));
--pie-3-computed: 0 calc(var(--pie-2-value) + var(--pie-3-value));
}
然后,将计算后的值设置在conic-gradient
中。
background-image: conic-gradient(
purple var(--pie-1-computed),
magenta var(--pie-2-computed),
cyan 0
);
最后一个计算值--pie-3-computed
被忽略,因为它始终会填充到末尾(类似于 SVG 路径的z
)。我认为在 JavaScript 中设置它仍然是一个好主意,以使整个过程感觉合理。
function setPieChartValue(percentage, index) {
let root = document.documentElement;
root.style.setProperty(`--pie-${index+1}-value`, `${percentage}%`);
}
let percentages = [25, 35, 60];
percentages.forEach(setPieChartValue);
使用这种新发现的知识,您可以将饼图连接到任何数据集。Firestore 数据库有一个.onSnapshot()
方法,它可以将数据从您的数据库流回。
const fullPathDoc = firebaseApp.firestore().doc('/users/1234/expenses/3-2021');
fullPathDoc.onSnapshot(snap => {
const { items } = doc.data();
items.forEach(setPieChartValue);
});
当您 Firestore 数据库中该文档位置的值发生变化时,实时更新将触发 .onSnapshot()
方法。现在您可能在问自己,什么是文档位置?我如何将数据存储到这个数据库中呢?让我们深入了解 Firestore 的工作原理以及如何在 NoSQL 数据库中建模数据。
如何在 NoSQL 数据库中建模数据
Firestore 是一个文档(NoSQL)数据库。它提供了一个集合的层次结构模式,这是一个文档列表。您可以将文档想象成一个 JSON 对象,它具有更多的数据类型。文档本身可以包含一个集合,称为子集合。这对于以“父子”或层次结构模式构建数据非常有用。
如果您没有按照上面的先决条件部分进行操作,请阅读一下,以确保在使用任何代码之前已创建 Firestore 数据库。
就像 Auth 一样,您首先需要在顶部导入 Firestore。
// This pen adds Firebase via the "Add External Scripts" option in codepen
// https://www.gstatic.com/firebasejs/8.2.10/firebase-app.js
// https://www.gstatic.com/firebasejs/8.2.10/firebase-auth.js
// https://www.gstatic.com/firebasejs/8.2.10/firebase-firestore.js
let firebaseConfig = { /* config */ };
let firebaseApp = firebase.initializeApp(firebaseConfig);
这会告诉 Firebase 客户端库如何连接您的 Firestore 数据库。使用此设置,您可以创建一个指向数据库中数据片段的**引用**。
// Reference to collection stored at: '/expenses'
const expensesCol = firebaseApp.firestore('expenses');
// Retrieve a snapshot of data (not in realtime)
// Top level await is coming! (v8.dev/features/top-level-await)
const snapshot = await expensesCol.get();
const expenses = snapshot.docs.map(d => d.data());
上面的代码创建了一个“集合引用”,然后调用 .get()
方法来检索数据快照。这不是数据本身,而是一个包含许多有用的方法和有关数据的元数据的包装器。最后一行通过迭代 .docs
数组并为每个“文档快照”调用 .data()
函数来“解包”快照。请注意,这不是实时的,但很快就会出现!
这里真正重要的是数据结构(有时称为数据模型)。这个应用程序存储用户的“支出”。假设文档具有以下结构。
{
uid: '1234',
items: [
{ label: "Food", value: 10 },
{ label: "Services", value: 24 },
{ label: "Rent", value: 30 },
{ label: "Oops", value: 38 }
]
}
文档的属性称为字段。此文档具有一个名为 items
的数组字段。每个项目都包含一个 label
字符串和一个 value
数字。还有一个名为 uid
的字符串字段,用于存储拥有数据的用户的 id
。此结构简化了迭代值以创建饼图的过程。这一部分已解决,但是如何确定如何获取特定用户的支出呢?
一种基于约束检索数据的传统方法是使用查询。这是您可以在 Firestore 中使用 .where()
方法执行的操作。
// Let's pretend currentUser.uid === '1234'
const currentUser = firebaseApp.auth().currentUser;
// Reference to collection stored at: '/expenses'
const expensesCol = firebaseApp.firestore('expenses');
// Query for the expenses belonging to uid == 1234
const userQuery = expensesCol.where('uid', '==', currentUser.uid);
const snapshot = await userQuery.get();
此结构很好,但没有充分利用集合提供的层次结构,更重要的是,没有充分利用子集合。相反,您可以以“父子”关系构建数据。Firestore 中的结构非常像网站的 URL。您可以设计包含类似路由参数的通配符的路径。
/users/:uid/expenses/month-year
上面的路径以一个名为 /users
的顶级集合构建数据,然后是一个分配给 uid
的文档,最后是一个子集合。即使没有现有的父文档,子集合也可以存在。每个子集合都包含一个文档,您可以在其中通过输入月份的数字(例如,3
表示三月)和年份(2021)来检索支出。
// Let's pretend currentUser.uid === '1234'
const currentUser = firebaseApp.auth().currentUser;
// Reference to collection stored at: '/users'
const usersCol = firebaseApp.firestore().collection('users');
// Reference to document stored at: '/users/1234';
const userDoc = usersCol.doc(currentUser.uid);
// Reference to sub-collection stored at: '/users/1234/expenses'
const userExpensesCol = userDoc.collection('expenses');
// Reference to document stored at: '/users/1234/expenses/3-2021';
const marchDoc = userExpensesCol.doc('3-2021');
// Alternatively, you could express a full path:
const fullPathDoc = firebaseApp.firestore().doc('/users/1234/expenses/3-2021');
上面的示例表明,通过一些建模,您可以检索数据片段,而无需编写查询。这并不总是适用于每种数据结构。在许多情况下,拥有一个顶级集合是有效的。这取决于您要编写的查询类型以及您希望如何保护数据。但是,在建模数据时了解您的选择总是一件好事。如果您想了解有关此主题的更多信息,我的同事 Todd Kerpelman 录制了 有关 Firestore 的完整系列。
我们将在本应用程序中使用层次结构方法。使用此结构化的数据,让我们实时流式传输。
将数据流式传输到可视化
上面的部分详细说明了如何使用 .get()
以“一次性”方式检索数据。文档和集合都具有 .onSnapshot()
方法,允许您实时流式传输数据。
const fullPathDoc = firebaseApp.firestore().doc('/users/1234/expenses/3-2021');
fullPathDoc.onSnapshot(snap => {
const { items } = doc.data();
items.forEach((item, index) => {
console.log(item);
});
});
每当存储在 fullPathDoc
中的数据发生更新时,Firestore 将将更新流式传输到所有连接的客户端。使用此数据同步,您可以设置饼图的所有 CSS 自定义属性。
let db = firebaseApp.firestore();
let { uid } = firebaseApp.auth().currentUser;
let marchDoc = db.doc(`users/${uid}/expenses/3-2021`);
marchDoc.onSnapshot(snap => {
let { factors } = snap.data();
factors.forEach((factor, index) => {
root.style.setProperty(`--pie-${index+1}__value`, `${factor.value}%`);
});
});
现在是激动人心的时刻!更新数据库中的数据,看看饼图片段是如何移动的。

说实话,这太有趣了。我已经在 Firebase 上工作了近七年,但我从未厌倦看到闪电般快的数据库更新速度。但工作还没有完成!数据库是不安全的,因为任何人都可以在任何地方更新它。现在该确保只有拥有该数据的用户才能安全访问它了。
保护您的数据库
如果您是 Firebase 或后端开发的新手,您可能想知道为什么数据库目前是不安全的。Firebase 允许您在不运行自己的服务器的情况下构建应用程序。这意味着您可以直接从浏览器连接到数据库。因此,如果访问数据库如此容易,是什么阻止其他人来做一些恶意的事情呢?答案是 Firebase 安全规则。
安全规则是您保护对 Firebase 服务访问权限的方式。您编写一组规则,指定如何访问后端中的数据。Firebase 会对进入后端的每个请求评估这些规则,只有在通过规则后才会允许请求。换句话说,您编写一组规则,Firebase 在服务器上运行这些规则以保护对服务的访问权限。
安全规则就像路由器
安全规则是一种自定义语言,其工作原理非常像路由器。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// When a request comes in for a "user/:userId"
// let's allow the read or write (not very secure)
// Don't copy this in your code plz!
match /users/{userId} {
allow read, write: if userId == "david";
}
}
}
上面的示例以 rules_version
语句开头。不必太担心它,但它是在告诉 Firebase 您要使用的安全规则语言的版本。目前,建议使用版本 2。然后,示例继续创建服务声明。这会告诉 Firebase 您要保护的服务,在本例中是 Firestore。现在,到了重要的部分:match
语句。
match
语句是使规则像路由器一样工作的部分。示例中的第一个匹配语句表明您正在匹配数据库中的文档。这是一个非常通用的语句,它表示,“嘿,看看文档部分”。下一个匹配语句更具体。它试图匹配 users
集合中的文档。/users/{userId}
的路径语法类似于 /users/:userId
的路由语法,其中 :userId
语法表示路由参数。在安全规则中,{userId}
语法的工作原理与路由参数相同,只是在这里称为“通配符”。集合中的任何用户都可以与此语句匹配。当匹配用户时会发生什么?您使用 allow
语句来控制访问权限。
allow
语句评估表达式,如果结果为真,则允许操作。如果结果为假,则拒绝操作。allow
语句的优点是它包含了许多有用的信息,可以用于包含的 match
块。一条有用的信息是通配符本身。上面的示例将 userId
通配符用作变量,并测试它是否与 "``david``"
的值匹配。只有 userId
为 "``david``"
才会允许 read
或 write
操作。
现在,这并不是一个非常有用的规则,但它有助于从简单开始。让我们花点时间记住数据库的结构。安全规则非常类似于数据结构顶部的注释。您可以在文档路径中添加注释以强制执行数据访问权限。此应用程序将数据存储在 /users/{userId}
集合中,并将支出存储在 /users/{userId}/expenses/month-year
子集合中。安全策略是确保只有经过身份验证的用户才能读取或写入其数据。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId}/{documents=**} {
allow read, write: if request.auth.uid == userId;
}
}
}
上面的示例从相同的地方开始,但在匹配 /users/{userId}
路径时开始发生变化。它在末尾添加了 {documents=**}
语法。这称为递归通配符,它是一种将规则级联到子集合的方式,这意味着 /users/{userId}
的任何子集合都将应用相同的规则。这在当前用例中非常有用,因为 /users/{userId}
和 /users/{userId}/expenses/month-year
都应遵循相同的规则。
在该 match
内部,allow
语句已使用名为 request
的新变量更新。安全规则带有一整套 变量,可帮助您编写复杂的规则。此变量是您评估请求是否来自经过身份验证的用户的方式。allow
语句评估经过身份验证的用户是否具有与 {userId}
通配符匹配的 uid
。如果该语句评估结果为真,则允许读取或写入。如果用户未经过身份验证或与 {userId}
通配符不匹配,则不允许任何操作。因此,它是安全的!
以下代码片段显示了来自经过身份验证用户的两个请求。
// Let's pretend currentUser.uid === '1234'
const currentUser = firebaseApp.auth().currentUser;
// The authenticated user owns this sub-collection
const ownedDoc = firebaseApp.firestore().doc('/users/1234/expenses/3-2021');
// The authenticated user DOES NOT own this sub-collection
const notOwnedDoc = firebaseApp.firestore().doc('/users/abcxyz/expenses/3-2021');
try {
const ownedSnapshot = await ownedDoc.get();
const notOwnedSnapshot = await notOwnedDoc.get();
} catch (error) {
// This will result in an error because the `notOwnedDoc` request will fail the security rule
}
就这样,您已经获得了集合和子集合。但数据库中的其余数据呢?如果您没有为这些路径上的数据编写任何规则,会发生什么?上面的示例将只允许访问与/users
集合及其子集合的allow
语句匹配的访问。对于任何其他集合或子集合,任何其他读写操作都不会生效。换句话说,如果您没有为路径编写allow
语句,那么它将不允许任何读写操作。这是一个很棒的安全默认值,因为它让您在适当的路径上显式强制执行访问权限。但是,您可能会搞砸它!
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Ahh!!! Never do this in a production app!!!
// This will negate any rule you write below!!
// Don't copy and paste this into your rules!!
match /{document=**} {
allow read, write: if true;
}
match /users/{userId}/{documents=**} {
allow read, write: if request.auth.uid == userId;
}
}
}
上面的示例添加了一个match
块,该块匹配/{document=**}
的路径。这是一个递归通配符路径,与整个数据库中的每个文档匹配。allow
语句始终计算为true
,因为它始终为真。此match
块创建一个重叠匹配语句,这意味着两个或多个match
块匹配相同路径。这不像 CSS 那样最后一条规则获胜。安全规则将评估每个匹配的规则,如果其中任何一个计算为true
,则允许操作。因此,全局递归通配符将否定您在下方设置的任何安全规则。全局递归通配符是一种策略,用于在短时间内将您的数据库打开到“测试模式”,其中没有重要的非公开可访问数据(没有私有或重要的数据)保存。除此之外,我不建议使用它。
在本地或控制台中编写规则
要讨论的最后一个主题是您编写和保存规则的位置。您有两个选择。第一个是在 Firebase 控制台中。在 Firestore 数据查看器中,您将看到“规则”选项。此选项卡显示数据库的活动规则。从这里,您可以编写规则,甚至针对规则测试场景。对于那些入门和尝试熟悉安全规则的人来说,建议使用这种方法。

另一个选择是在您的机器上本地编写规则,并使用Firebase CLI 将它们部署到控制台。这样您就可以将它们保存在源代码管理中,并为它们编写测试,以确保它们在您的代码库不断发展时继续工作。对于生产应用程序和团队,建议使用这种方法。
值得再次注意的是,用于创建 Firebase 应用程序的 Firebase 配置并不不安全。这相当于某人知道您网站的域名。某人知道您的域名并不能使您的网站不安全。安全规则是 Firebase 提供对您的数据和服务的安全访问的方式。
总结
这些 Firebase 信息很多,尤其是关于规则的内容(安全很重要!)。本文中的信息登录用户、合并访客帐户、构建数据、实时将数据流式传输到可视化中,并确保所有内容都是安全的。您可以使用这些概念构建许多不同的应用程序。
如果您想继续使用 Firebase 构建,还有很多我想让您知道的事情,例如模拟器套件。本文中构建的应用程序可以在您的机器上本地运行,这是一种更轻松的开发体验,非常适合在 CI/CD 环境中进行测试。还有很多很棒的 Firebase 工具和框架库。以下是一些值得查看的链接。
- Firebase YouTube 频道
- Firebase Codelabs
- Firebase CLI
- 使用模拟器套件在本地运行 Firebase
- Firebase 播客
- 框架库,包括AngularFire、ReactFire、VueFire、RxFire
如果您从本文中了解到一件事,我希望是您看到前端 Firebase 代码行数并不多。这里有一行用于登录用户,那里有几行用于获取数据,但主要是针对应用程序本身的特定代码。Firebase 的目标是让您快速构建。摆脱管理后端基础设施的责任。专注于前端。
精彩总结,感谢您提供的参考 :pray
精彩的总结,非常感谢。如果您能添加一个设置第一个 Firebase 应用程序的分步指南,那就太好了。按照这样的教程来了解具体的操作步骤和方法会很有趣。
谢谢
再次投票支持分步指南。谢谢!
太棒了。您可以更新规则以包含不同的场景,例如我想让人们在线阅读特定文档。
所以它是一个框架(如 Laravel、Symfony),但使用的是 JavaScript 而不是 PHP,并且数据库在某种云中…?
而且数据库不是数据库,而是基于文件的存储(如 Grav)…?
我开发网络应用程序已有十多年,并且花了大量时间使用服务器代码和数据库构建后端。Firebase 并没有完全取代它,但我最近开始使用它,真是太棒了。用这种方式构建应用程序更容易、更快,而且默认情况下所有内容都是实时的。我仍然需要使用 Cloud functions 和 Firebase 不足的其他服务,但总的来说,Firebase 对开发人员来说是一个巨大的胜利。
很想听听您的看法
1. 减少对 Firebase 的调用以提高效率和每次使用的成本
应用程序和 FB 之间用户支付集成的最佳方法。
谢谢,
C
感谢 CSS-Tricks 的这篇博文。我进入“兔子洞”,了解到关于子集合有很多错误信息
阅读并观看 Firebase 的大量文档和视频后,感觉有点零散,我无法看到整体画面来继续前进。最后我决定阅读这篇文章,慢慢地一切都开始变得有意义了。非常感谢。