一、概述
目標
在此 Codelab 中,您將構建一個由Cloud Firestore提供支持的餐廳推薦網絡應用。
你會學到什麼
- 從 Web 應用程序讀取數據並將數據寫入 Cloud Firestore
- 實時監聽 Cloud Firestore 數據變化
- 使用 Firebase 身份驗證和安全規則保護 Cloud Firestore 數據
- 編寫複雜的 Cloud Firestore 查詢
你需要什麼
在開始此 Codelab 之前,請確保您已安裝:
2. 創建並設置一個 Firebase 項目
創建一個 Firebase 項目
- 在Firebase 控制台中,單擊添加項目,然後將 Firebase 項目命名為FriendlyEats 。
記住您的 Firebase 項目的項目 ID。
- 單擊創建項目。
我們要構建的應用程序使用網絡上提供的一些 Firebase 服務:
- Firebase 身份驗證可輕鬆識別您的用戶
- Cloud Firestore將結構化數據保存在雲端,並在數據更新時獲得即時通知
- Firebase Hosting託管和服務您的靜態資產
對於這個特定的代碼實驗室,我們已經配置了 Firebase 託管。但是,對於 Firebase Auth 和 Cloud Firestore,我們將引導您使用 Firebase 控制台完成服務的配置和啟用。
啟用匿名身份驗證
雖然身份驗證不是此 Codelab 的重點,但在我們的應用程序中進行某種形式的身份驗證非常重要。我們將使用匿名登錄——這意味著用戶將在沒有提示的情況下靜默登錄。
您需要啟用匿名登錄。
- 在 Firebase 控制台中,找到左側導航欄中的構建部分。
- 單擊Authentication ,然後單擊Sign-in method選項卡(或單擊此處直接轉到那裡)。
- 啟用匿名登錄提供程序,然後單擊保存。
這將允許應用程序在您的用戶訪問 Web 應用程序時靜默登錄。請隨意閱讀匿名身份驗證文檔以了解更多信息。
啟用 Cloud Firestore
該應用使用 Cloud Firestore 保存和接收餐廳信息和評級。
您需要啟用 Cloud Firestore。在 Firebase 控制台的構建部分中,單擊Firestore 數據庫。單擊 Cloud Firestore 窗格中的創建數據庫。
對 Cloud Firestore 中數據的訪問受安全規則控制。我們稍後將在此 Codelab 中詳細討論規則,但首先我們需要為數據設置一些基本規則才能開始。在 Firebase 控制台的Rules 選項卡中添加以下規則,然後單擊Publish 。
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-web
目錄中。從現在開始,確保從此目錄運行所有命令:
cd friendlyeats-web
導入入門應用
使用您的 IDE(WebStorm、Atom、Sublime、Visual Studio Code...)打開或導入 📁 friendlyeats-web
目錄。此目錄包含 Codelab 的起始代碼,該代碼實驗室由一個尚未正常運行的餐廳推薦應用程序組成。我們將使它在整個 Codelab 中正常運行,因此您需要盡快編輯該目錄中的代碼。
4. 安裝 Firebase 命令行界面
Firebase 命令行界面 (CLI) 允許您在本地提供您的 Web 應用程序並將您的 Web 應用程序部署到 Firebase 託管。
- 通過運行以下 npm 命令安裝 CLI:
npm -g install firebase-tools
- 通過運行以下命令驗證 CLI 是否已正確安裝:
firebase --version
確保 Firebase CLI 的版本為 v7.4.0 或更高版本。
- 通過運行以下命令授權 Firebase CLI:
firebase login
我們已經設置了 Web 應用程序模板,以從您應用程序的本地目錄和文件中提取您的應用程序的 Firebase 託管配置。但為此,我們需要將您的應用程序與您的 Firebase 項目相關聯。
- 確保您的命令行正在訪問您應用程序的本地目錄。
- 通過運行以下命令將您的應用與 Firebase 項目相關聯:
firebase use --add
- 出現提示時,選擇您的Project ID ,然後為您的 Firebase 項目指定一個別名。
如果您有多個環境(生產、暫存等),別名很有用。但是,對於此 Codelab,我們只使用default
的別名。
- 按照命令行中的其餘說明進行操作。
5.運行本地服務器
我們已經準備好真正開始開發我們的應用程序了!讓我們在本地運行我們的應用程序!
- 運行以下 Firebase CLI 命令:
firebase emulators:start --only hosting
- 您的命令行應顯示以下響應:
hosting: Local server: http://localhost:5000
我們正在使用Firebase 託管模擬器在本地提供我們的應用程序。 Web 應用程序現在應該可以從http://localhost:5000獲得。
- 在http://localhost:5000打開您的應用程序。
您應該會看到已連接到您的 Firebase 項目的 FriendlyEats 副本。
該應用程序已自動連接到您的 Firebase 項目,並以匿名用戶身份靜默登錄。
6.寫入數據到Cloud Firestore
在本部分中,我們將向 Cloud Firestore 寫入一些數據,以便我們可以填充應用的界面。這可以通過Firebase 控制台手動完成,但我們將在應用程序本身中完成,以演示基本的 Cloud Firestore 寫入。
數據模型
Firestore 數據分為集合、文檔、字段和子集合。我們會將每家餐廳作為文檔存儲在名為restaurants
的頂級集合中。
稍後,我們會將每條評論存儲在每家餐廳下名為ratings
的子集合中。
將餐廳添加到 Firestore
我們應用程序中的主要模型對像是餐廳。讓我們編寫一些代碼,將 restaurant 文檔添加到restaurants
集合。
- 從您下載的文件中,打開
scripts/FriendlyEats.Data.js
。 - 找到
FriendlyEats.prototype.addRestaurant
函數。 - 用以下代碼替換整個函數。
FriendlyEats.Data.js
FriendlyEats.prototype.addRestaurant = function(data) { var collection = firebase.firestore().collection('restaurants'); return collection.add(data); };
上面的代碼向restaurants
集合添加了一個新文檔。文檔數據來自一個普通的 JavaScript 對象。為此,我們首先獲取對 Cloud Firestore 集合restaurants
引用,然後add
數據。
讓我們添加餐廳吧!
- 返回瀏覽器中的 FriendlyEats 應用程序並刷新它。
- 單擊添加模擬數據。
該應用程序將自動生成一組隨機的餐廳對象,然後調用您的addRestaurant
函數。但是,您還不會在實際的 Web 應用程序中看到數據,因為我們仍然需要實現數據檢索(代碼實驗室的下一部分)。
但是,如果您導航到 Firebase 控制台中的Cloud Firestore 選項卡,您現在應該會在restaurants
集合中看到新文檔!
恭喜,您剛剛將數據從 Web 應用程序寫入 Cloud Firestore!
在下一節中,您將了解如何從 Cloud Firestore 檢索數據並將其顯示在您的應用中。
7.顯示來自Cloud Firestore的數據
在本部分中,您將了解如何從 Cloud Firestore 檢索數據並將其顯示在您的應用中。兩個關鍵步驟是創建查詢和添加快照偵聽器。該偵聽器將收到與查詢匹配的所有現有數據的通知,並將實時接收更新。
首先,讓我們構建將服務於默認的、未過濾的餐館列表的查詢。
- 返回文件
scripts/FriendlyEats.Data.js
。 - 找到
FriendlyEats.prototype.getAllRestaurants
函數。 - 用以下代碼替換整個函數。
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()
方法。
我們將通過添加快照偵聽器來完成此操作。
- 返回文件
scripts/FriendlyEats.Data.js
。 - 找到
FriendlyEats.prototype.getDocumentsInQuery
函數。 - 用以下代碼替換整個函數。
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 控制台並手動刪除餐廳或更改其名稱 - 您會立即看到更改顯示在您的網站上!
8.獲取()數據
到目前為止,我們已經展示瞭如何使用onSnapshot
實時檢索更新;然而,這並不總是我們想要的。有時只獲取一次數據更有意義。
我們想要實現一種方法,當用戶點擊進入您應用中的特定餐廳時觸發該方法。
- 返回到您的文件
scripts/FriendlyEats.Data.js
。 - 找到
FriendlyEats.prototype.getRestaurant
函數。 - 用以下代碼替換整個函數。
FriendlyEats.Data.js
FriendlyEats.prototype.getRestaurant = function(id) { return firebase.firestore().collection('restaurants').doc(id).get(); };
實施此方法後,您將能夠查看每家餐廳的頁面。只需單擊列表中的一家餐廳,您就會看到該餐廳的詳細信息頁面:
目前,您無法添加評分,因為我們稍後仍需要在 Codelab 中實現添加評分。
9.排序和過濾數據
目前,我們的應用程序會顯示餐廳列表,但用戶無法根據自己的需要進行過濾。在本部分中,您將使用 Cloud Firestore 的高級查詢來啟用過濾。
下面是一個獲取所有Dim Sum
餐廳的簡單查詢示例:
var filteredQuery = query.where('category', '==', 'Dim Sum')
顧名思義, where()
方法將使我們的查詢僅下載其字段滿足我們設置的限制的集合成員。在這種情況下,它只會下載category
為Dim Sum
的餐廳。
在我們的應用程序中,用戶可以鏈接多個過濾器來創建特定的查詢,例如“舊金山的披薩”或“洛杉磯的海鮮按受歡迎程度訂購”。
我們將創建一個方法來構建一個查詢,該查詢將根據用戶選擇的多個條件過濾我們的餐廳。
- 返回到您的文件
scripts/FriendlyEats.Data.js
。 - 找到
FriendlyEats.prototype.getFilteredRestaurants
函數。 - 用以下代碼替換整個函數。
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 一次輕鬆部署多個索引。
- 在您的應用下載的本地目錄中,您會找到一個
firestore.indexes.json
文件。
該文件描述了所有可能的過濾器組合所需的所有索引。
firestore.indexes.json
{ "indexes": [ { "collectionGroup": "restaurants", "queryScope": "COLLECTION", "fields": [ { "fieldPath": "city", "order": "ASCENDING" }, { "fieldPath": "avgRating", "order": "DESCENDING" } ] }, ... ] }
- 使用以下命令部署這些索引:
firebase deploy --only firestore:indexes
幾分鐘後,您的索引將生效並且錯誤消息將消失。
11.在事務中寫入數據
在本節中,我們將為用戶添加向餐廳提交評論的功能。到目前為止,我們所有的寫入都是原子的並且相對簡單。如果其中任何一個出錯,我們可能只會提示用戶重試它們,或者我們的應用程序會自動重試寫入。
我們的應用程序將有許多用戶想要為餐廳添加評級,因此我們需要協調多個讀寫操作。首先必須提交評論本身,然後餐廳的評級count
和average rating
需要更新。如果其中一個失敗但另一個失敗,我們將處於不一致狀態,即我們數據庫的一部分中的數據與另一部分中的數據不匹配。
幸運的是,Cloud Firestore 提供了事務功能,允許我們在單個原子操作中執行多次讀取和寫入,從而確保我們的數據保持一致。
- 返回到您的文件
scripts/FriendlyEats.Data.js
。 - 找到
FriendlyEats.prototype.addRating
函數。 - 用以下代碼替換整個函數。
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); }); }); };
在上面的代碼塊中,我們觸發了一個事務來更新餐廳文檔中avgRating
和numRatings
的數值。同時,我們將新rating
添加到ratings
子集合中。
12.保護您的數據
在此 Codelab 開始時,我們將應用程序的安全規則設置為完全開放數據庫以供任何讀取或寫入。在實際應用程序中,我們希望設置更細粒度的規則來防止不希望的數據訪問或修改。
- 在 Firebase 控制台的構建部分中,單擊Firestore 數據庫。
- 單擊 Cloud Firestore 部分中的規則選項卡(或單擊此處直接轉到那裡)。
- 用以下規則替換默認值,然後單擊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 執行基本和高級讀寫,以及如何使用安全規則保護數據訪問。您可以在quickstarts-js 存儲庫中找到完整的解決方案。
要了解有關 Cloud Firestore 的更多信息,請訪問以下資源: