Cloud Firestore 網絡代碼實驗室

目標

在本程式碼實驗室,將構建搭載餐館推薦的Web應用程序雲公司的FireStore

img5.png

你會學到什麼

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

你需要什麼

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

創建 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;
    }
  }
}

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

克隆GitHub的庫在命令行:

git clone https://github.com/firebase/friendlyeats-web

示例代碼應該已經克隆到📁 friendlyeats-web目錄。從現在開始,請確保從此目錄運行所有命令:

cd friendlyeats-web

導入入門應用

使用你的IDE(WebStorm,凌動,崇高,Visual Studio代碼...)打開或導入📁 friendlyeats-web目錄。此目錄包含 codelab 的起始代碼,該代碼實驗室由尚未功能的餐廳推薦應用程序組成。我們將使其在整個 Codelab 中發揮作用,因此您很快就需要編輯該目錄中的代碼。

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. 按照命令行中的其餘說明進行操作。

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

  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

在本節中,我們將向 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 檢索數據並將其顯示在您的應用中。

在本部分中,您將了解如何從 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

到目前為止,我們已經展示了如何使用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 中實現添加評分。

目前,我們的應用程序顯示餐廳列表,但用戶無法根據自己的需要進行過濾。在本部分中,您將使用 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,並填寫正確的參數。在下一節中,我們將編寫和部署此應用程序所需的索引。

如果我們不想探索應用程序中的每條路徑並遵循每個索引創建鏈接,我們可以使用 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

幾分鐘後,您的索引將生效,錯誤消息將消失。

在本節中,我們將添加用戶向餐廳提交評論的功能。到目前為止,我們所有的寫入都是原子的並且相對簡單。如果其中任何一個出錯,我們可能只會提示用戶重試它們,否則我們的應用程序將自動重試寫入。

我們的應用程序將有許多用戶想要為餐廳添加評分,因此我們需要協調多次讀取和寫入。首先本身所具有的提交審核,然後餐廳的等級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子集合。

在本 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

在此 Codelab 中,您學習瞭如何使用 Cloud Firestore 執行基本和高級讀取和寫入,以及如何使用安全規則保護數據訪問。你可以找到的完整的解決方案快速入門-JS庫

要詳細了解 Cloud Firestore,請訪問以下資源: