Cloud Firestore Web Codelab

1. 概述

目標

在此 Codelab 中,您將構建一個由Cloud Firestore提供支持的餐廳推薦 Web 應用。

img5.png

你將學到什麼

  • 從 Web 應用讀取數據並將其寫入 Cloud Firestore
  • 實時監聽 Cloud Firestore 數據的變化
  • 使用 Firebase 身份驗證和安全規則來保護 Cloud Firestore 數據
  • 編寫複雜的 Cloud Firestore 查詢

你需要什麼

在開始此 Codelab 之前,請確保您已安裝:

2. 創建並設置 Firebase 項目

創建 Firebase 項目

  1. Firebase 控制台中,單擊添加項目,然後將 Firebase 項目命名為FriendlyEats

請記住您的 Firebase 項目的項目 ID。

  1. 單擊創建項目

我們要構建的應用程序使用網絡上提供的一些 Firebase 服務:

  • Firebase 身份驗證可輕鬆識別您的用戶
  • Cloud Firestore將結構化數據保存在雲端,並在數據更新時獲得即時通知
  • Firebase Hosting用於託管和服務您的靜態資產

對於這個特定的 Codelab,我們已經配置了 Firebase 託管。但是,對於 Firebase Auth 和 Cloud Firestore,我們將引導您使用 Firebase 控制台完成服務的配置和啟用。

啟用匿名身份驗證

儘管身份驗證不是此 Codelab 的重點,但在我們的應用程序中進行某種形式的身份驗證非常重要。我們將使用匿名登錄- 這意味著用戶將在沒有提示的情況下靜默登錄。

您需要啟用匿名登錄。

  1. 在 Firebase 控制台中,找到左側導航欄中的“構建”部分。
  2. 單擊“身份驗證” ,然後單擊“登錄方法”選項卡(或單擊此處直接轉到此處)。
  3. 啟用匿名登錄提供程序,然後單擊“保存”

img7.png

這將允許應用程序在用戶訪問 Web 應用程序時以靜默方式登錄。請隨意閱讀匿名身份驗證文檔以了解更多信息。

啟用 Cloud Firestore

該應用程序使用 Cloud Firestore 保存和接收餐廳信息和評級。

您需要啟用 Cloud Firestore。在 Firebase 控制台的“構建”部分中,單擊Firestore 數據庫。單擊 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;
    }
  }
}

上述規則將數據訪問限制為已登錄的用戶,從而阻止未經身份驗證的用戶讀取或寫入。這比允許公共訪問要好,但仍遠不安全,我們稍後將在 Codelab 中改進這些規則。

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 託管。

  1. 通過運行以下 npm 命令安裝 CLI:
npm -g install firebase-tools
  1. 通過運行以下命令驗證 CLI 是否已正確安裝:
firebase --version

確保 Firebase CLI 的版本為 v7.4.0 或更高版本。

  1. 通過運行以下命令授權 Firebase CLI:
firebase login

我們已設置 Web 應用模板,以便從應用的本地目錄和文件中提取應用的 Firebase 託管配置。但要做到這一點,我們需要將您的應用程序與您的 Firebase 項目關聯起來。

  1. 確保您的命令行正在訪問應用程序的本地目錄。
  2. 通過運行以下命令將您的應用與 Firebase 項目關聯:
firebase use --add
  1. 出現提示時,選擇您的項目 ID ,然後為您的 Firebase 項目指定一個別名。

如果您有多個環境(生產、登台等),則別名很有用。但是,對於此 Codelab,我們只使用default別名。

  1. 按照命令行中的其餘說明進行操作。

5. 運行本地服務器

我們已準備好真正開始開發我們的應用程序!讓我們在本地運行我們的應用程序!

  1. 運行以下 Firebase CLI 命令:
firebase emulators:start --only hosting
  1. 您的命令行應顯示以下響應:
hosting: Local server: http://localhost:5000

我們使用Firebase Hosting模擬器在本地為我們的應用程序提供服務。現在應該可以從http://localhost:5000訪問該 Web 應用程序。

  1. http://localhost:5000打開您的應用程序。

您應該會看到已連接到您的 Firebase 項目的FriendlyEats 副本。

該應用已自動連接到您的 Firebase 項目,並以匿名用戶身份以靜默方式讓您登錄。

img2.png

6. 將數據寫入Cloud Firestore

在本部分中,我們將向 Cloud Firestore 寫入一些數據,以便填充應用的 UI。這可以通過Firebase 控制台手動完成,但我們將在應用程序本身中執行此操作以演示基本的 Cloud 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 對象。為此,我們首先獲取對 Cloud Firestore 集合restaurants引用,然後add數據。

讓我們添加餐廳吧!

  1. 返回瀏覽器中的FriendlyEats應用程序並刷新它。
  2. 單擊添加模擬數據

該應用程序將自動生成一組隨機的餐館對象,然後調用您的addRestaurant函數。但是,您還不會在實際的 Web 應用程序中看到數據,因為我們仍然需要實現數據檢索(Codelab 的下一部分)。

不過,如果您導航到 Firebase 控制台中的Cloud 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);
};

在上面的代碼中,我們構建了一個查詢,該查詢將從名為restaurants的頂級集合中檢索最多 50 家餐廳,這些餐廳按平均評分排序(當前全部為零)。聲明此查詢後,我們將其傳遞給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都會觸發回調。

  • 第一次,回調是由查詢的整個結果集觸發的,即來自 Cloud Firestore 的整個restaurants集合。然後它將所有單獨的文檔傳遞給renderer.display函數。
  • 當文檔被刪除時, change.type等於removed 。因此,在本例中,我們將調用一個從 UI 中刪除餐廳的函數。

現在我們已經實現了這兩種方法,刷新應用程序並驗證我們之前在 Firebase 控制台中看到的餐館現在在應用程序中可見。如果您成功完成此部分,那麼您的應用程序現在正在使用 Cloud Firestore 讀取和寫入數據!

當您的餐廳列表發生變化時,此偵聽器將自動更新。嘗試轉到 Firebase 控制台並手動刪除餐廳或更改其名稱 - 您將立即看到更改顯示在您的網站上!

img5.png

8.Get()數據

到目前為止,我們已經展示瞭如何使用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/project-id/database/firestore/indexes?create_composite=...

這些錯誤是因為 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的數值。同時,我們將新的rating添加到ratings子集合中。

12. 保護您的數據

在此 Codelab 開始時,我們設置應用程序的安全規則以完全開放數據庫以進行任何讀取或寫入。在實際的應用程序中,我們希望設置更細粒度的規則來防止不需要的數據訪問或修改。

  1. 在 Firebase 控制台的“構建”部分中,單擊Firestore 數據庫
  2. 單擊 Cloud Firestore 部分中的“規則”選項卡(或單擊此處直接轉到那裡)。
  3. 將默認值替換為以下規則,然後單擊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 執行基本和高級讀取和寫入,以及如何使用安全規則保護數據訪問。您可以在faststarts-js 存儲庫中找到完整的解決方案。

要了解有關 Cloud Firestore 的更多信息,請訪問以下資源: