Cloud Firestore Web Codelab

透過集合功能整理內容 你可以依據偏好儲存及分類內容。

一、概述

目標

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

img5.png

你會學到什麼

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

你需要什麼

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

2. 創建並設置 Firebase 項目

創建一個 Firebase 項目

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

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

  1. 單擊創建項目

我們要構建的應用程序使用了一些 Web 上可用的 Firebase 服務:

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

對於這個特定的代碼實驗室,我們已經配置了 Firebase 託管。但是,對於 Firebase 身份驗證和 Cloud Firestore,我們將引導您使用 Firebase 控制台完成服務的配置和啟用。

啟用匿名身份驗證

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

您需要啟用匿名登錄。

  1. 在 Firebase 控制台中,找到左側導航中的Build部分。
  2. 點擊Authentication ,然後點擊Sign-in method選項卡(或點擊此處直接前往)。
  3. 啟用匿名登錄提供程序,然後單擊保存

img7.png

這將允許應用程序在您的用戶訪問 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 託管。

  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. 出現提示時,選擇您的Project ID ,然後為您的 Firebase 項目指定一個別名。

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

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

5.運行本地服務器

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

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

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

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

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

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

img2.png

6. 將數據寫入 Cloud Firestore

在本節中,我們將向 Cloud Firestore 寫入一些數據,以便我們可以填充應用的 UI。這可以通過Firebase 控制台手動完成,但我們將在應用程序本身中完成,以演示基本的 Cloud Firestore 編寫。

數據模型

Firestore 數據分為集合、文檔、字段和子集合。我們將把每家餐館作為一個文檔存儲在一個名為restaurants的頂級集合中。

img3.png

稍後,我們會將每條ratings存儲在每個餐廳下的名為 rating 的子集合中。

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 應用程序中看到數據,因為我們仍然需要實現檢索數據(代碼實驗室的下一部分)。

但是,如果您導航到 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 家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將在每次查詢結果發生變化時觸發其回調。

  • 第一次,回調是由查詢的整個結果集觸發的——這意味著來自 Cloud Firestore 的整個restaurants集合。然後它將所有單獨的文檔傳遞給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的數值。同時,我們將新rating添加到ratings子集合中。

12. 保護您的數據

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

  1. 在 Firebase 控制台的Build部分,點擊Firestore Database
  2. 單擊 Cloud Firestore 部分中的規則選項卡(或單擊此處直接前往那裡)。
  3. 將默認值替換為以下規則,然後單擊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 的更多信息,請訪問以下資源: