一、概述
目标
在此 Codelab 中,您将构建一个由Cloud Firestore提供支持的餐厅推荐 Web 应用。
你会学到什么
- 从网络应用读取数据并将数据写入 Cloud Firestore
- 实时监听 Cloud Firestore 数据的变化
- 使用 Firebase 身份验证和安全规则来保护 Cloud Firestore 数据
- 编写复杂的 Cloud Firestore 查询
你需要什么
在开始此 Codelab 之前,请确保您已安装:
2. 创建并设置 Firebase 项目
创建一个 Firebase 项目
- 在Firebase 控制台中,单击添加项目,然后将 Firebase 项目命名为FriendlyEats 。
记住您的 Firebase 项目的项目 ID。
- 单击创建项目。
我们要构建的应用程序使用了一些 Web 上可用的 Firebase 服务:
- Firebase 身份验证可轻松识别您的用户
- Cloud Firestore将结构化数据保存在云端,并在数据更新时获得即时通知
- Firebase 托管来托管和提供您的静态资产
对于这个特定的代码实验室,我们已经配置了 Firebase 托管。但是,对于 Firebase 身份验证和 Cloud Firestore,我们将引导您使用 Firebase 控制台完成服务的配置和启用。
启用匿名身份验证
尽管身份验证不是此 Codelab 的重点,但在我们的应用程序中具有某种形式的身份验证很重要。我们将使用匿名登录——这意味着用户将在没有提示的情况下静默登录。
您需要启用匿名登录。
- 在 Firebase 控制台中,找到左侧导航中的Build部分。
- 点击Authentication ,然后点击Sign-in method选项卡(或点击此处直接前往)。
- 启用匿名登录提供程序,然后单击保存。
这将允许应用程序在您的用户访问 Web 应用程序时静默登录。随意阅读匿名身份验证文档以了解更多信息。
启用 Cloud Firestore
该应用程序使用 Cloud Firestore 来保存和接收餐厅信息和评级。
您需要启用 Cloud Firestore。在 Firebase 控制台的Build部分,点击Firestore Database 。单击 Cloud Firestore 窗格中的创建数据库。
对 Cloud Firestore 中数据的访问由安全规则控制。我们稍后将在此 Codelab 中详细讨论规则,但首先我们需要为数据设置一些基本规则才能开始。在 Firebase 控制台的规则选项卡中添加以下规则,然后单击发布。
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 friendlyeats-web
目录中。从现在开始,请确保从此目录运行所有命令:
cd friendlyeats-web
导入入门应用
使用您的 IDE(WebStorm、Atom、Sublime、Visual Studio Code...)打开或导入 📁friendlyeats 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 写入一些数据,以便我们可以填充应用的 UI。这可以通过Firebase 控制台手动完成,但我们将在应用程序本身中完成,以演示基本的 Cloud Firestore 编写。
数据模型
Firestore 数据分为集合、文档、字段和子集合。我们将把每家餐馆作为一个文档存储在一个名为restaurants
的顶级集合中。
稍后,我们会将每条ratings
存储在每个餐厅下的名为 rating 的子集合中。
将餐厅添加到 Firestore
我们应用程序中的主要模型对象是餐厅。让我们编写一些代码,将餐厅文档添加到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 家restaurants
,这些餐馆按平均评分排序(目前全为零)。在我们声明这个查询之后,我们将它传递给负责加载和呈现数据的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/.../database/firestore/indexes?create_index=...
这些错误是因为 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 控制台的Build部分,点击Firestore Database 。
- 单击 Cloud Firestore 部分中的规则选项卡(或单击此处直接前往那里)。
- 将默认值替换为以下规则,然后单击Publish 。
firestore.rules
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 的更多信息,请访问以下资源: