一、概述
目标
在此 Codelab 中,您将构建一个由Cloud Firestore提供支持的餐厅推荐网络应用。
你会学到什么
- 从 Web 应用程序读取数据并将数据写入 Cloud Firestore
- 实时监听 Cloud Firestore 数据变化
- 使用 Firebase 身份验证和安全规则保护 Cloud Firestore 数据
- 编写复杂的 Cloud Firestore 查询
你需要什么
在开始此 Codelab 之前,请确保您已安装:
2. 创建并设置一个 Firebase 项目
创建一个 Firebase 项目
- 在Firebase 控制台中,单击添加项目,然后将 Firebase 项目命名为FriendlyEats 。
记住您的 Firebase 项目的项目 ID。
- 单击创建项目。
我们要构建的应用程序使用网络上提供的一些 Firebase 服务:
- Firebase 身份验证可轻松识别您的用户
- Cloud Firestore将结构化数据保存在云端,并在数据更新时获得即时通知
- Firebase Hosting 托管和服务您的静态资产
对于这个特定的代码实验室,我们已经配置了 Firebase 托管。但是,对于 Firebase Auth 和 Cloud Firestore,我们将引导您使用 Firebase 控制台完成服务的配置和启用。
启用匿名身份验证
虽然身份验证不是此 Codelab 的重点,但在我们的应用程序中进行某种形式的身份验证非常重要。我们将使用匿名登录——这意味着用户将在没有提示的情况下静默登录。
您需要启用匿名登录。
- 在 Firebase 控制台中,找到左侧导航栏中的构建部分。
- 单击Authentication ,然后单击Sign-in method选项卡(或单击此处直接转到那里)。
- 启用匿名登录提供程序,然后单击保存。
这将允许应用程序在您的用户访问 Web 应用程序时静默登录。请随意阅读匿名身份验证文档以了解更多信息。
启用 Cloud Firestore
该应用使用 Cloud Firestore 保存和接收餐厅信息和评级。
您需要启用 Cloud Firestore。在 Firebase 控制台的构建部分中,单击Firestore 数据库。单击 Cloud Firestore 窗格中的创建数据库。
对 Cloud Firestore 中数据的访问受安全规则控制。我们稍后将在此 Codelab 中详细讨论规则,但首先我们需要为数据设置一些基本规则才能开始。在 Firebase 控制台的Rules 选项卡中添加以下规则,然后单击Publish 。
service cloud.firestore { match /databases/{database}/documents { match /{document=**} { // // WARNING: These rules are insecure! We will replace them with // more secure rules later in the codelab // allow read, write: if request.auth != null; } } }
上述规则将数据访问限制为已登录的用户,从而防止未经身份验证的用户读取或写入。这比允许公共访问要好,但仍远非安全,我们稍后将在代码实验室中改进这些规则。
3.获取示例代码
从命令行克隆GitHub 存储库:
git clone https://github.com/firebase/friendlyeats-web
示例代码应该已被克隆到 📁 friendlyeats-web
目录中。从现在开始,确保从此目录运行所有命令:
cd friendlyeats-web
导入入门应用
使用您的 IDE(WebStorm、Atom、Sublime、Visual Studio Code...)打开或导入 📁 friendlyeats-web
目录。此目录包含 Codelab 的起始代码,该代码实验室由一个尚未正常运行的餐厅推荐应用程序组成。我们将使它在整个 Codelab 中正常运行,因此您需要尽快编辑该目录中的代码。
4. 安装 Firebase 命令行界面
Firebase 命令行界面 (CLI) 允许您在本地提供您的 Web 应用程序并将您的 Web 应用程序部署到 Firebase 托管。
- 通过运行以下 npm 命令安装 CLI:
npm -g install firebase-tools
- 通过运行以下命令验证 CLI 是否已正确安装:
firebase --version
确保 Firebase CLI 的版本为 v7.4.0 或更高版本。
- 通过运行以下命令授权 Firebase CLI:
firebase login
我们已经设置了 Web 应用程序模板,以从您应用程序的本地目录和文件中提取您的应用程序的 Firebase 托管配置。但为此,我们需要将您的应用程序与您的 Firebase 项目相关联。
- 确保您的命令行正在访问您应用程序的本地目录。
- 通过运行以下命令将您的应用与 Firebase 项目相关联:
firebase use --add
- 出现提示时,选择您的Project ID ,然后为您的 Firebase 项目指定一个别名。
如果您有多个环境(生产、暂存等),别名很有用。但是,对于此 Codelab,我们只使用default
的别名。
- 按照命令行中的其余说明进行操作。
5.运行本地服务器
我们已经准备好真正开始开发我们的应用程序了!让我们在本地运行我们的应用程序!
- 运行以下 Firebase CLI 命令:
firebase emulators:start --only hosting
- 您的命令行应显示以下响应:
hosting: Local server: http://localhost:5000
我们正在使用Firebase 托管模拟器在本地提供我们的应用程序。 Web 应用程序现在应该可以从http://localhost:5000获得。
- 在http://localhost:5000打开您的应用程序。
您应该会看到已连接到您的 Firebase 项目的 FriendlyEats 副本。
该应用程序已自动连接到您的 Firebase 项目,并以匿名用户身份静默登录。
6.写入数据到Cloud Firestore
在本部分中,我们将向 Cloud Firestore 写入一些数据,以便我们可以填充应用的界面。这可以通过Firebase 控制台手动完成,但我们将在应用程序本身中完成,以演示基本的 Cloud Firestore 写入。
数据模型
Firestore 数据分为集合、文档、字段和子集合。我们会将每家餐厅作为文档存储在名为restaurants
的顶级集合中。
稍后,我们会将每条评论存储在每家餐厅下名为ratings
的子集合中。
将餐厅添加到 Firestore
我们应用程序中的主要模型对象是餐厅。让我们编写一些代码,将 restaurant 文档添加到restaurants
集合。
- 从您下载的文件中,打开
scripts/FriendlyEats.Data.js
。 - 找到
FriendlyEats.prototype.addRestaurant
函数。 - 用以下代码替换整个函数。
FriendlyEats.Data.js
FriendlyEats.prototype.addRestaurant = function(data) { var collection = firebase.firestore().collection('restaurants'); return collection.add(data); };
上面的代码向restaurants
集合添加了一个新文档。文档数据来自一个普通的 JavaScript 对象。为此,我们首先获取对 Cloud Firestore 集合restaurants
的引用,然后add
数据。
让我们添加餐厅吧!
- 返回浏览器中的 FriendlyEats 应用程序并刷新它。
- 单击添加模拟数据。
该应用程序将自动生成一组随机的餐厅对象,然后调用您的addRestaurant
函数。但是,您还不会在实际的 Web 应用程序中看到数据,因为我们仍然需要实现数据检索(代码实验室的下一部分)。
但是,如果您导航到 Firebase 控制台中的Cloud Firestore 选项卡,您现在应该会在restaurants
集合中看到新文档!
恭喜,您刚刚将数据从 Web 应用程序写入 Cloud Firestore!
在下一节中,您将了解如何从 Cloud Firestore 检索数据并将其显示在您的应用中。
7.显示来自Cloud Firestore的数据
在本部分中,您将了解如何从 Cloud Firestore 检索数据并将其显示在您的应用中。两个关键步骤是创建查询和添加快照侦听器。该侦听器将收到与查询匹配的所有现有数据的通知,并将实时接收更新。
首先,让我们构建将服务于默认的、未过滤的餐馆列表的查询。
- 返回文件
scripts/FriendlyEats.Data.js
。 - 找到
FriendlyEats.prototype.getAllRestaurants
函数。 - 用以下代码替换整个函数。
FriendlyEats.Data.js
FriendlyEats.prototype.getAllRestaurants = function(renderer) { var query = firebase.firestore() .collection('restaurants') .orderBy('avgRating', 'desc') .limit(50); this.getDocumentsInQuery(query, renderer); };
在上面的代码中,我们构建了一个查询,该查询将从名为restaurants
的顶级集合中检索最多 50 家餐厅,这些餐厅按平均评分(目前全为零)排序。声明此查询后,我们将其传递给负责加载和呈现数据的getDocumentsInQuery()
方法。
我们将通过添加快照侦听器来完成此操作。
- 返回文件
scripts/FriendlyEats.Data.js
。 - 找到
FriendlyEats.prototype.getDocumentsInQuery
函数。 - 用以下代码替换整个函数。
FriendlyEats.Data.js
FriendlyEats.prototype.getDocumentsInQuery = function(query, renderer) { query.onSnapshot(function(snapshot) { if (!snapshot.size) return renderer.empty(); // Display "There are no restaurants". snapshot.docChanges().forEach(function(change) { if (change.type === 'removed') { renderer.remove(change.doc); } else { renderer.display(change.doc); } }); }); };
在上面的代码中,每当查询结果发生变化时, query.onSnapshot
都会触发它的回调。
- 第一次,回调是用查询的整个结果集触发的——这意味着来自 Cloud Firestore 的整个
restaurants
集合。然后它将所有单独的文档传递给renderer.display
函数。 - 删除文档时,
change.type
等于removed
。因此,在这种情况下,我们将调用一个从 UI 中删除餐厅的函数。
现在我们已经实现了这两种方法,刷新应用程序并验证我们之前在 Firebase 控制台中看到的餐厅现在是否在应用程序中可见。如果您成功完成了这一部分,那么您的应用现在可以使用 Cloud Firestore 读取和写入数据!
当您的餐厅列表发生变化时,此侦听器将自动更新。尝试转到 Firebase 控制台并手动删除餐厅或更改其名称 - 您会立即看到更改显示在您的网站上!
8.获取()数据
到目前为止,我们已经展示了如何使用onSnapshot
实时检索更新;然而,这并不总是我们想要的。有时只获取一次数据更有意义。
我们想要实现一种方法,当用户点击进入您应用中的特定餐厅时触发该方法。
- 返回到您的文件
scripts/FriendlyEats.Data.js
。 - 找到
FriendlyEats.prototype.getRestaurant
函数。 - 用以下代码替换整个函数。
FriendlyEats.Data.js
FriendlyEats.prototype.getRestaurant = function(id) { return firebase.firestore().collection('restaurants').doc(id).get(); };
实施此方法后,您将能够查看每家餐厅的页面。只需单击列表中的一家餐厅,您就会看到该餐厅的详细信息页面:
目前,您无法添加评分,因为我们稍后仍需要在 Codelab 中实现添加评分。
9.排序和过滤数据
目前,我们的应用程序会显示餐厅列表,但用户无法根据自己的需要进行过滤。在本部分中,您将使用 Cloud Firestore 的高级查询来启用过滤。
下面是一个获取所有Dim Sum
餐厅的简单查询示例:
var filteredQuery = query.where('category', '==', 'Dim Sum')
顾名思义, where()
方法将使我们的查询仅下载其字段满足我们设置的限制的集合成员。在这种情况下,它只会下载category
为Dim Sum
的餐厅。
在我们的应用程序中,用户可以链接多个过滤器来创建特定的查询,例如“旧金山的披萨”或“洛杉矶的海鲜按受欢迎程度订购”。
我们将创建一个方法来构建一个查询,该查询将根据用户选择的多个条件过滤我们的餐厅。
- 返回到您的文件
scripts/FriendlyEats.Data.js
。 - 找到
FriendlyEats.prototype.getFilteredRestaurants
函数。 - 用以下代码替换整个函数。
FriendlyEats.Data.js
FriendlyEats.prototype.getFilteredRestaurants = function(filters, renderer) { var query = firebase.firestore().collection('restaurants'); if (filters.category !== 'Any') { query = query.where('category', '==', filters.category); } if (filters.city !== 'Any') { query = query.where('city', '==', filters.city); } if (filters.price !== 'Any') { query = query.where('price', '==', filters.price.length); } if (filters.sort === 'Rating') { query = query.orderBy('avgRating', 'desc'); } else if (filters.sort === 'Reviews') { query = query.orderBy('numRatings', 'desc'); } this.getDocumentsInQuery(query, renderer); };
上面的代码添加了多个where
过滤器和一个orderBy
子句来构建基于用户输入的复合查询。我们的查询现在将只返回符合用户要求的餐馆。
在浏览器中刷新您的 FriendlyEats 应用程序,然后确认您可以按价格、城市和类别进行过滤。测试时,您会在浏览器的 JavaScript 控制台中看到如下所示的错误:
The query requires an index. You can create it here: https://console.firebase.google.com/project/project-id/database/firestore/indexes?create_composite=...
这些错误是因为 Cloud Firestore 需要大多数复合查询的索引。要求对查询建立索引可以使 Cloud Firestore 保持快速的规模化。
从错误消息中打开链接将自动在 Firebase 控制台中打开索引创建 UI,并填写正确的参数。在下一节中,我们将编写和部署此应用程序所需的索引。
10.部署索引
如果我们不想探索应用程序中的每条路径并跟踪每个索引创建链接,我们可以使用 Firebase CLI 一次轻松部署多个索引。
- 在您的应用下载的本地目录中,您会找到一个
firestore.indexes.json
文件。
该文件描述了所有可能的过滤器组合所需的所有索引。
firestore.indexes.json
{ "indexes": [ { "collectionGroup": "restaurants", "queryScope": "COLLECTION", "fields": [ { "fieldPath": "city", "order": "ASCENDING" }, { "fieldPath": "avgRating", "order": "DESCENDING" } ] }, ... ] }
- 使用以下命令部署这些索引:
firebase deploy --only firestore:indexes
几分钟后,您的索引将生效并且错误消息将消失。
11.在事务中写入数据
在本节中,我们将为用户添加向餐厅提交评论的功能。到目前为止,我们所有的写入都是原子的并且相对简单。如果其中任何一个出错,我们可能只会提示用户重试它们,或者我们的应用程序会自动重试写入。
我们的应用程序将有许多用户想要为餐厅添加评级,因此我们需要协调多个读写操作。首先必须提交评论本身,然后餐厅的评级count
和average rating
需要更新。如果其中一个失败但另一个失败,我们将处于不一致状态,即我们数据库的一部分中的数据与另一部分中的数据不匹配。
幸运的是,Cloud Firestore 提供了事务功能,允许我们在单个原子操作中执行多次读取和写入,从而确保我们的数据保持一致。
- 返回到您的文件
scripts/FriendlyEats.Data.js
。 - 找到
FriendlyEats.prototype.addRating
函数。 - 用以下代码替换整个函数。
FriendlyEats.Data.js
FriendlyEats.prototype.addRating = function(restaurantID, rating) { var collection = firebase.firestore().collection('restaurants'); var document = collection.doc(restaurantID); var newRatingDocument = document.collection('ratings').doc(); return firebase.firestore().runTransaction(function(transaction) { return transaction.get(document).then(function(doc) { var data = doc.data(); var newAverage = (data.numRatings * data.avgRating + rating.rating) / (data.numRatings + 1); transaction.update(document, { numRatings: data.numRatings + 1, avgRating: newAverage }); return transaction.set(newRatingDocument, rating); }); }); };
在上面的代码块中,我们触发了一个事务来更新餐厅文档中avgRating
和numRatings
的数值。同时,我们将新rating
添加到ratings
子集合中。
12.保护您的数据
在此 Codelab 开始时,我们将应用程序的安全规则设置为完全开放数据库以供任何读取或写入。在实际应用程序中,我们希望设置更细粒度的规则来防止不希望的数据访问或修改。
- 在 Firebase 控制台的构建部分中,单击Firestore 数据库。
- 单击 Cloud Firestore 部分中的规则选项卡(或单击此处直接转到那里)。
- 用以下规则替换默认值,然后单击Publish 。
firestore.规则
rules_version = '2'; service cloud.firestore { // Determine if the value of the field "key" is the same // before and after the request. function unchanged(key) { return (key in resource.data) && (key in request.resource.data) && (resource.data[key] == request.resource.data[key]); } match /databases/{database}/documents { // Restaurants: // - Authenticated user can read // - Authenticated user can create/update (for demo purposes only) // - Updates are allowed if no fields are added and name is unchanged // - Deletes are not allowed (default) match /restaurants/{restaurantId} { allow read: if request.auth != null; allow create: if request.auth != null; allow update: if request.auth != null && (request.resource.data.keys() == resource.data.keys()) && unchanged("name"); // Ratings: // - Authenticated user can read // - Authenticated user can create if userId matches // - Deletes and updates are not allowed (default) match /ratings/{ratingId} { allow read: if request.auth != null; allow create: if request.auth != null && request.resource.data.userId == request.auth.uid; } } } }
这些规则限制访问以确保客户端仅进行安全更改。例如:
- 更新餐厅文档只能更改评级,不能更改名称或任何其他不可变数据。
- 仅当用户 ID 与登录用户匹配时才能创建评级,从而防止欺骗。
除了使用 Firebase 控制台,您还可以使用 Firebase CLI 将规则部署到您的 Firebase 项目。您工作目录中的firestore.rules文件已包含上述规则。要从本地文件系统部署这些规则(而不是使用 Firebase 控制台),您需要运行以下命令:
firebase deploy --only firestore:rules
13.结论
在此 Codelab 中,您学习了如何使用 Cloud Firestore 执行基本和高级读写,以及如何使用安全规则保护数据访问。您可以在quickstarts-js 存储库中找到完整的解决方案。
要了解有关 Cloud Firestore 的更多信息,请访问以下资源: