Google is committed to advancing racial equity for Black communities. See how.
本頁面由 Cloud Translation API 翻譯而成。
Switch to English

Cloud Firestore Web Codelab

目標

在此代碼實驗室中,您將構建一個由Cloud Firestore支持的餐廳推薦Web應用程序。

img5.png

你會學到什麼

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

你需要什麼

在啟動此代碼實驗室之前,請確保已安裝:

創建一個Firebase項目

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

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

  1. 單擊創建項目

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

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

對於此特定的代碼實驗室,我們已經配置了Firebase Hosting。但是,對於Firebase Auth和Cloud Firestore,我們將引導您使用Firebase控制台配置和啟用服務。

啟用匿名身份驗證

儘管身份驗證不是此代碼實驗室的重點,但在我們的應用程序中進行某種形式的身份驗證很重要。我們將使用匿名登錄-意味著用戶將被靜默登錄而不會得到提示。

您需要啟用匿名登錄。

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

img7.png

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

啟用Cloud Firestore

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

您需要啟用Cloud Firestore。在Firebase控制台的“開發”部分中,單擊Firestore 。在“ Cloud Firestore”窗格中,單擊“創建數據庫”。

對Cloud Firestore中數據的訪問由安全規則控制。我們稍後將在此代碼實驗室中討論更多規則,但是首先我們需要在數據上設置一些基本規則以開始使用。在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;
    }
  }
}

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

從命令行克隆GitHub存儲庫

第055章

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

cd friendlyeats-web

導入入門應用

使用您的IDE(WebStorm,Atom,Sublime,Visual Studio Code ...)打開或導入📁friendlyeats friendlyeats-web目錄。該目錄包含該代碼實驗室的起始代碼,該代碼實驗室由一個尚不實用的餐廳推薦應用程序組成。我們將在整個代碼實驗室中使其正常運行,因此您需要盡快在該目錄中編輯代碼。

Firebase命令行界面(CLI)允許您在本地提供Web應用程序,並將Web應用程序部署到Firebase Hosting。

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

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

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

我們已經設置了Web應用程序模板,以從應用程序的本地目錄和文件中提取Firebase Hosting的應用程序配置。但是,為此,我們需要將您的應用程序與Firebase項目關聯。

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

如果您有多個環境(生產,暫存等),則別名很有用。但是,對於此代碼實驗室,我們僅使用default的別名。

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

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

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

我們正在使用Firebase託管模擬器在本地提供應用程序。現在應該可以從http:// localhost:5000訪問該Web應用程序。

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

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

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

img2.png

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

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

在上面的代碼中,我們構造了一個查詢,該查詢將從名為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

到目前為止,我們已經展示瞭如何使用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

目前,您無法添加評分,因為我們稍後仍需要在代碼實驗室中實現添加評分。

當前,我們的應用程序顯示餐廳列表,但用戶無法根據需要進行過濾。在本部分中,您將使用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的數值。同時,我們將新的rating添加到ratings子集合中。

在此代碼實驗室的開頭,我們設置了應用程序的安全規則,以完全打開數據庫以進行任何讀取或寫入。在實際的應用程序中,我們希望設置更多細粒度的規則以防止不良的數據訪問或修改。

  1. 在Firebase控制台的“開發”部分中,單擊“數據庫”
  2. 單擊 Cloud 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

在此代碼實驗室中,您學習瞭如何使用Cloud Firestore執行基本和高級讀寫,以及如何使用安全規則保護數據訪問。您可以在quickstarts-js存儲庫中找到完整的解決方案。

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