Cloud Firestore Web 程式碼實驗室

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/vanilla-js

導入入門應用程式

使用 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 的更多信息,請訪問以下資源:

14. [可選] 透過應用程式檢查強制執行

Firebase App Check透過協助驗證和防止應用程式出現不必要的流量來提供保護。在此步驟中,您將透過使用reCAPTCHA Enterprise新增 App Check 來保護對服務的存取。

首先,您需要啟用 App Check 和 reCaptcha。

啟用 reCaptcha Enterprise

  1. 在 Cloud 控制台中,找到並選擇「安全性」下的reCaptcha Enterprise
  2. 根據提示啟用服務,然後按一下「建立金鑰」
  3. 根據提示輸入顯示名稱,平台類型選擇「網站」
  4. 將您部署的 URL 新增至網域清單中,並確保未選擇「使用複選框質詢」選項。
  5. 按一下「建立金鑰」 ,並將產生的金鑰儲存在某處以妥善保管。您稍後將在此步驟中需要它。

啟用應用程式檢查

  1. 在 Firebase 控制台中,找到左側面板中的「建置」部分。
  2. 按一下App Check ,然後按一下Get Started按鈕(或直接重新導向至控制台)。
  3. 按一下「註冊」並在出現提示時輸入您的 reCaptcha Enterprise 金鑰,然後按一下「儲存」
  4. 在 API 視圖中,選擇儲存並按一下強制。對Cloud Firestore執行相同的動作。

現在應該強制執行應用程式檢查!刷新您的應用程式並嘗試建立/查看餐廳。您應該收到錯誤訊息:

Uncaught Error in snapshot listener: FirebaseError: [code=permission-denied]: Missing or insufficient permissions.

這表示 App Check 預設會阻止未經驗證的請求。現在讓我們為您的應用程式添加驗證。

導航至FriendlyEats.View.js文件,更新initAppCheck函數並添加您的reCaptcha 金鑰以初始化App Check。

FriendlyEats.prototype.initAppCheck = function() {
    var appCheck = firebase.appCheck();
    appCheck.activate(
    new firebase.appCheck.ReCaptchaEnterpriseProvider(
      /* reCAPTCHA Enterprise site key */
    ),
    true // Set to true to allow auto-refresh.
  );
};

appCheck實例使用您的金鑰透過ReCaptchaEnterpriseProvider進行初始化,並且isTokenAutoRefreshEnabled允許令牌在您的應用程式中自動刷新。

若要啟用本機測試,請在FriendlyEats.js檔案中找到應用程式初始化的部分,並將下列行新增至FriendlyEats.prototype.initAppCheck函數中:

if(isLocalhost) {
  self.FIREBASE_APPCHECK_DEBUG_TOKEN = true;
}

這將在本機 Web 應用程式的控制台中記錄一個偵錯令牌,類似於:

App Check debug token: 8DBDF614-649D-4D22-B0A3-6D489412838B. You will need to add it to your app's App Check settings in the Firebase console for it to work.

現在,前往 Firebase 控制台中 App Check 的應用程式檢視

按一下溢出選單,然後選擇管理偵錯令牌

然後,按一下「新增偵錯令牌」並根據提示從控制台貼上偵錯令牌。

恭喜! App Check 現在應該可以在您的應用程式中運行。