Cloud Firestore 网络代码实验室

1. 概述

目标

在本程式码实验室,将构建搭载餐馆推荐的Web应用程序云公司的FireStore

img5.png

你会学到什么

  • 从 Web 应用读取数据并将数据写入 Cloud Firestore
  • 实时聆听 Cloud Firestore 数据的变化
  • 使用 Firebase 身份验证和安全规则来保护 Cloud Firestore 数据
  • 编写复杂的 Cloud Firestore 查询

你需要什么

在开始此 Codelab 之前,请确保您已安装:

2. 创建并设置 Firebase 项目

创建 Firebase 项目

  1. 火力地堡控制台,单击添加项目,然后命名火力地堡项目FriendlyEats。

记住您的 Firebase 项目的项目 ID。

  1. 点击创建项目

我们将要构建的应用程序使用了一些网络上可用的 Firebase 服务:

  • 火力地堡认证很容易识别自己的用户
  • 云公司的FireStore保存在云上的结构化数据,并得到即时通知当数据被更新
  • 火力地堡托管到主机并为您的静态资产

对于这个特定的代码实验室,我们已经配置了 Firebase 托管。但是,对于 Firebase 身份验证和 Cloud Firestore,我们将引导您使用 Firebase 控制台完成服务的配置和启用。

启用匿名身份验证

尽管身份验证不是本 Codelab 的重点,但在我们的应用程序中使用某种形式的身份验证很重要。我们将使用匿名登录-这意味着用户将被默默而不会被提示签署。

你将需要启用匿名登录。

  1. 在火力地堡控制台,找到在左侧导航栏中的Build部分。
  2. 点击身份验证,然后点击登录方法选项卡(或点击这里去直接出现)。
  3. 启用匿名登录的提供者,然后点击保存

img7.png

这将允许应用程序在用户访问 Web 应用程序时以静默方式登录您的用户。随意阅读匿名身份验证文件,以了解更多信息。

启用 Cloud Firestore

该应用使用 Cloud Firestore 来保存和接收餐厅信息和评级。

您需要启用 Cloud Firestore。在火力地堡控制台的Build部分,单击数据库的FireStore。单击云公司的FireStore窗格中创建数据库

对 Cloud Firestore 中数据的访问由安全规则控制。我们稍后将在此 Codelab 中更多地讨论规则,但首先我们需要对我们的数据设置一些基本规则才能开始。在规则选项卡的火力地堡控制台添加下面的规则,然后单击发布

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,凌动,崇高,Visual Studio代码...)打开或导入📁 friendlyeats-web目录。该目录包含 codelab 的起始代码,该代码实验室由一个尚未运行的餐厅推荐应用程序组成。我们将使其在整个 Codelab 中发挥作用,因此您需要尽快编辑该目录中的代码。

4. 安装 Firebase 命令行界面

Firebase 命令行界面 (CLI) 允许您在本地为您的 Web 应用提供服务,并将您的 Web 应用部署到 Firebase 托管。

  1. 通过运行以下 npm 命令安装 CLI:
npm -g install firebase-tools
  1. 通过运行以下命令验证 CLI 是否已正确安装:
firebase --version

确保 Firebase CLI 的版本为 v7.4.0 或更高版本。

  1. 通过运行以下命令授权 Firebase CLI:
firebase login

我们已经设置了网络应用模板,以从应用的本地目录和文件中提取应用的 Firebase 托管配置。但要做到这一点,我们需要将您的应用与您的 Firebase 项目相关联。

  1. 确保您的命令行正在访问您的应用程序的本地目录。
  2. 通过运行以下命令将您的应用与 Firebase 项目相关联:
firebase use --add
  1. 提示时,选择您的项目编号,然后给你的火力地堡项目的别名。

如果您有多个环境(生产、暂存等),则别名很有用。然而,对于本程式码实验室,就让我们用的别名default

  1. 按照命令行中的其余说明进行操作。

5. 运行本地服务器

我们已准备好开始实际开发我们的应用程序!让我们在本地运行我们的应用程序!

  1. 运行以下 Firebase CLI 命令:
firebase emulators:start --only hosting
  1. 您的命令行应显示以下响应:
hosting: Local server: http://localhost:5000

我们使用的火力地堡主机模拟器在本地提供我们的应用程序。 Web应用程序现在应该可以从HTTP://本地主机:5000

  1. 在打开的应用程序的http://本地主机:5000

您应该会看到已连接到 Firebase 项目的 FriendlyEats 副本。

该应用已自动连接到您的 Firebase 项目,并以匿名用户的身份让您静默登录。

img2.png

6. 将数据写入 Cloud Firestore

在本节中,我们将向 Cloud Firestore 写入一些数据,以便我们可以填充应用的 UI。这可以通过手动调节来完成火力地堡控制台,但我们会在应用程序本身来证明一个基本的云计算公司的FireStore写做。

数据模型

Firestore 数据分为集合、文档、字段和子集合。我们将存储每个餐厅中称为顶级集合文件restaurants

img3.png

稍后,我们将存储在一个名为子集合每次审查ratings各餐厅下。

img4.png

将餐厅添加到 Firestore

我们应用程序中的主要模型对象是一家餐厅。让我们写一些代码,增加了餐厅的文件到restaurants集合。

  1. 从你下载的文件,打开scripts/FriendlyEats.Data.js
  2. 查找功能FriendlyEats.prototype.addRestaurant
  3. 用以下代码替换整个函数。

FriendlyEats.Data.js

FriendlyEats.prototype.addRestaurant = function(data) {
  var collection = firebase.firestore().collection('restaurants');
  return collection.add(data);
};

上面的代码添加了一个新的文档到restaurants集合。文档数据来自一个普通的 JavaScript 对象。为此,我们首先前往云公司的FireStore集的引用restaurants ,然后add “荷兰国际集团的数据。

让我们添加餐厅!

  1. 在浏览器中返回到您的 FriendlyEats 应用程序并刷新它。
  2. 点击添加模拟数据

该应用程序将自动生成一组随机餐馆的对象,然后调用你的addRestaurant功能。但是,你还不会看到数据在实际的Web应用程序,因为我们仍然需要实现检索数据(代码实验室的下一节)。

如果您导航到云公司的FireStore标签在火力地堡控制台,但是,你现在应该看到在新文档restaurants集合!

img6.png

恭喜,您刚刚从 Web 应用将数据写入 Cloud Firestore!

在下一部分中,您将了解如何从 Cloud Firestore 检索数据并将其显示在您的应用中。

7. 显示来自 Cloud Firestore 的数据

在本部分中,您将了解如何从 Cloud Firestore 检索数据并将其显示在您的应用中。两个关键步骤是创建查询和添加快照侦听器。此侦听器将收到与查询匹配的所有现有数据的通知,并将实时接收更新。

首先,让我们构建一个查询,该查询将为默认的、未过滤的餐馆列表提供服务。

  1. 回到文件scripts/FriendlyEats.Data.js
  2. 查找功能FriendlyEats.prototype.getAllRestaurants
  3. 用以下代码替换整个函数。

FriendlyEats.Data.js

FriendlyEats.prototype.getAllRestaurants = function(renderer) {
  var query = firebase.firestore()
      .collection('restaurants')
      .orderBy('avgRating', 'desc')
      .limit(50);

  this.getDocumentsInQuery(query, renderer);
};

在上面的代码中,我们构建了一个查询将从顶层集合命名检索多达50家餐厅restaurants ,这是由平均分数(目前全部为零)订购。我们宣布这个查询后,我们把它传递给getDocumentsInQuery()负责加载和渲染数据的方法。

我们将通过添加一个快照侦听器来做到这一点。

  1. 回到文件scripts/FriendlyEats.Data.js
  2. 查找功能FriendlyEats.prototype.getDocumentsInQuery
  3. 用以下代码替换整个函数。

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会在每次有一个变化的查询结果时触发其回调。

  • 第一次,将触发回调与整个结果集查询-这意味着整个restaurants从云公司的FireStore集合。然后,它通过了所有的个人文档renderer.display功能。
  • 当一个文件被删除, change.type等于removed 。因此,在这种情况下,我们将调用一个从 UI 中删除餐厅的函数。

现在我们已经实现了这两种方法,刷新应用程序并验证我们之前在 Firebase 控制台中看到的餐厅现在在应用程序中可见。如果您成功完成了本部分,那么您的应用现在正在使用 Cloud Firestore 读取和写入数据!

当您的餐厅列表发生变化时,此侦听器将不断自动更新。尝试转到 Firebase 控制台并手动删除餐厅或更改其名称 - 您会立即看到更改显示在您的网站上!

img5.png

8.获取()数据

到目前为止,我们已经展示了如何使用onSnapshot检索,实时更新;然而,这并不总是我们想要的。有时只获取一次数据更有意义。

我们将要实现一个方法,当用户点击您应用中的特定餐厅时触发该方法。

  1. 回到你的文件scripts/FriendlyEats.Data.js
  2. 查找功能FriendlyEats.prototype.getRestaurant
  3. 用以下代码替换整个函数。

FriendlyEats.Data.js

FriendlyEats.prototype.getRestaurant = function(id) {
  return firebase.firestore().collection('restaurants').doc(id).get();
};

实施此方法后,您将能够查看每家餐厅的页面。只需单击列表中的餐厅,您就会看到餐厅的详细信息页面:

img1.png

目前,您无法添加评分,因为我们稍后仍需要在 codelab 中实现添加评分。

9. 排序和过滤数据

目前,我们的应用程序显示餐厅列表,但用户无法根据自己的需要进行过滤。在本部分中,您将使用 Cloud Firestore 的高级查询来启用过滤。

这里有一个简单的查询来获取所有的例子Dim Sum餐馆:

var filteredQuery = query.where('category', '==', 'Dim Sum')

正如它的名字所暗示的, where()方法将使我们的查询只下载其领域满足我们设定的限制集合的成员。在这种情况下,它只会在那里下载的餐馆categoryDim Sum

在我们的应用程序中,用户可以链接多个过滤器来创建特定查询,例如“旧金山的披萨”或“洛杉矶的海鲜按人气排序”。

我们将创建一个方法来构建一个查询,该查询将根据用户选择的多个条件过滤我们的餐厅。

  1. 回到你的文件scripts/FriendlyEats.Data.js
  2. 查找功能FriendlyEats.prototype.getFilteredRestaurants
  3. 用以下代码替换整个函数。

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 轻松地一次部署多个索引。

  1. 在您的应用程序的下载的本地目录,你会发现一个firestore.indexes.json文件。

该文件描述了所有可能的过滤器组合所需的所有索引。

firestore.indexes.json

{
 "indexes": [
   {
     "collectionGroup": "restaurants",
     "queryScope": "COLLECTION",
     "fields": [
       { "fieldPath": "city", "order": "ASCENDING" },
       { "fieldPath": "avgRating", "order": "DESCENDING" }
     ]
   },

   ...

 ]
}
  1. 使用以下命令部署这些索引:
firebase deploy --only firestore:indexes

几分钟后,您的索引将生效,错误消息将消失。

11.在事务中写入数据

在本节中,我们将添加用户向餐厅提交评论的功能。到目前为止,我们所有的写入都是原子的并且相对简单。如果其中任何一个出错,我们可能只会提示用户重试它们,否则我们的应用程序将自动重试写入。

我们的应用程序将有许多用户想要为餐厅添加评分,因此我们需要协调多次读取和写入。首先本身所具有的提交审核,然后餐厅的等级countaverage rating需要更新。如果其中一个失败,而另一个没有失败,我们就会处于不一致的状态,即数据库一部分中的数据与另一部分中的数据不匹配。

幸运的是,Cloud Firestore 提供了事务功能,允许我们在单个原子操作中执行多次读取和写入,确保我们的数据保持一致。

  1. 回到你的文件scripts/FriendlyEats.Data.js
  2. 查找功能FriendlyEats.prototype.addRating
  3. 用以下代码替换整个函数。

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);
    });
  });
};

在上述结构中,我们触发一个事务更新的数值avgRatingnumRatings餐厅文件内。与此同时,我们添加了新的ratingratings子集合。

12. 保护您的数据

在本 Codelab 开始时,我们将应用程序的安全规则设置为完全开放数据库以进行任何读取或写入。在实际应用程序中,我们希望设置更细粒度的规则以防止不受欢迎的数据访问或修改。

  1. 在火力地堡控制台的Build部分,单击数据库的FireStore。
  2. 点击云公司的FireStore部分规则选项卡(或点击这里去直接出现)。
  3. 替换为以下规则的默认值,然后单击发布

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 执行基本和高级读取和写入,以及如何使用安全规则保护数据访问。你可以找到的完整的解决方案快速入门-JS库

要详细了解 Cloud Firestore,请访问以下资源: