一、概述
目標
在此 Codelab 中,您將構建一個由Cloud Firestore提供支持的餐廳推薦 Web 應用。
你會學到什麼
- 從網絡應用讀取數據並將數據寫入 Cloud Firestore
- 實時監聽 Cloud Firestore 數據的變化
- 使用 Firebase 身份驗證和安全規則來保護 Cloud Firestore 數據
- 編寫複雜的 Cloud Firestore 查詢
你需要什麼
在開始此 Codelab 之前,請確保您已安裝:
2. 創建並設置 Firebase 項目
創建一個 Firebase 項目
- 在Firebase 控制台中,單擊添加項目,然後將 Firebase 項目命名為FriendlyEats 。
記住您的 Firebase 項目的項目 ID。
- 單擊創建項目。
我們要構建的應用程序使用了一些 Web 上可用的 Firebase 服務:
- Firebase 身份驗證可輕鬆識別您的用戶
- Cloud Firestore將結構化數據保存在雲端,並在數據更新時獲得即時通知
- Firebase 託管來託管和提供您的靜態資產
對於這個特定的代碼實驗室,我們已經配置了 Firebase 託管。但是,對於 Firebase 身份驗證和 Cloud Firestore,我們將引導您使用 Firebase 控制台完成服務的配置和啟用。
啟用匿名身份驗證
儘管身份驗證不是此 Codelab 的重點,但在我們的應用程序中具有某種形式的身份驗證很重要。我們將使用匿名登錄——這意味著用戶將在沒有提示的情況下靜默登錄。
您需要啟用匿名登錄。
- 在 Firebase 控制台中,找到左側導航中的Build部分。
- 點擊Authentication ,然後點擊Sign-in method選項卡(或點擊此處直接前往)。
- 啟用匿名登錄提供程序,然後單擊保存。
這將允許應用程序在您的用戶訪問 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 託管。
- 通過運行以下 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 寫入一些數據,以便我們可以填充應用的 UI。這可以通過Firebase 控制台手動完成,但我們將在應用程序本身中完成,以演示基本的 Cloud Firestore 編寫。
數據模型
Firestore 數據分為集合、文檔、字段和子集合。我們將把每家餐館作為一個文檔存儲在一個名為restaurants
的頂級集合中。
稍後,我們會將每條ratings
存儲在每個餐廳下的名為 rating 的子集合中。
將餐廳添加到 Firestore
我們應用程序中的主要模型對像是餐廳。讓我們編寫一些代碼,將餐廳文檔添加到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 家restaurants
,這些餐館按平均評分排序(目前全為零)。在我們聲明這個查詢之後,我們將它傳遞給負責加載和呈現數據的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/.../database/firestore/indexes?create_index=...
這些錯誤是因為 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 控制台的Build部分,點擊Firestore Database 。
- 單擊 Cloud Firestore 部分中的規則選項卡(或單擊此處直接前往那裡)。
- 將默認值替換為以下規則,然後單擊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 的更多信息,請訪問以下資源: