一個設置 Firestore 作為資料庫的教程,這是一個非常方便的解決方案,可以解決你的儲存問題!

我有一個需求,需要為我的 成員俱樂部 創建一個儲存數據的地方,這是我教授編程的地方。

我希望我的用戶可以通過點擊一個按鈕來手動說“我完成了這個課程”。

基本上,我想為每個用戶存儲一個特定的對象。

設置 Firebase

我決定使用 Firebase 以及他們提供的 Firestore 資料庫 來實現這個需求。

它的免費版本是很慷慨的,每個月最多可以存儲 1GB 的數據,並且網絡傳輸量可以達到 10GB,這遠超出了我的預期。

在 Firebase 網站上打開 https://firebase.google.com/

Firebase 是一個由 Google 提供的產品,所以一旦你登錄到 Google,你也就登錄到 Firebase。

通過點擊“創建專案”來創建一個新的 Firebase 專案。

給它一個名字:

然後就完成了:

我點擊了 iOS 和 Android 旁邊的“Web”圖標,然後輸入了應用程序名稱:

Firebase 立即給了我需要的訪問密鑰,還附帶了一些示例代碼:

在這之後,Firebase 讓我添加一些數據庫的安全規則。

你可以選擇兩個默認選項:對每個人開放,或對每個人關閉。我選擇了對每個人開放,這是他們稱之為 測試模式

就是這樣!我準備好了,可以創建一個集合了。

什麼是集合?在 Firestore 的術語中,我們可以創建許多不同的集合,並為每個集合分配文檔。

然後,文檔可以包含字段和其他集合。

這和其他 NoSQL 數據庫(比如 MongoDB)沒有太大區別。

我強烈建議觀看這個主題的 YouTube 播放列表,非常棒。

所以,我添加了一個我稱之為 users 的集合。

我希望使用一個特殊的字符串 id 來識別每個用戶。

前端代碼

現在我們進入 JavaScript 部分。

在頁腳中,我包含了 Firebase 提供的這兩個文件:

<script src="https://www.gstatic.com/
firebasejs/7.2.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/
firebasejs/7.2.1/firebase-firestore.js"></script>

然後,我添加了一個 DOMContentLoaded 事件監聽器,以確保在 DOM 準備好時運行代碼:

<script>
document.addEventListener('DOMContentLoaded', event => {

})
</script>

在這個區塊中,我添加了 Firebase 配置:

const firebaseConfig = {
 apiKey: "MY-API-KEY",
 authDomain: "MY-AUTH-DOMAIN",
 projectId: "MY-PROJECT-ID"
}

我將這個對象傳遞給 firebase.initializeApp(),然後調用 firebase.firestore() 來獲取對數據庫對象的引用:

firebase.initializeApp(firebaseConfig)
const db = firebase.firestore()

現在,我創建了一個腳本來從後端列表中填充用戶 ID:

const list = [/*...my list...*/]

list.forEach(item => {
 db.collection('users').doc(item).set({})
})

我運行了一次該腳本,以填充數據庫。我基本上是以程序化的方式 為每個用戶創建了一個文檔

這非常重要,因為一旦我創建了一個文檔,它意味著我可以限制權限只能更新這些文檔,不允許添加新文檔或刪除它們(這是我們稍後要做的事情)。

好的,現在我有一些複雜的邏輯來識別用戶 ID 和課程 ID,這與我們這裡的任務無關,所以我不會詳細介紹。

一旦我獲取了這些信息,我就可以獲取該對象的引用:

const id = /* the user ID */
const course = /* the course ID */
const docRef = db.doc(`membership/${id}`)

太好了!現在,我可以從 Firebase 獲取文檔引用:

docRef.get().then(function(doc) {
 if (doc.exists) {
 const data = doc.data()
 document.querySelector('button')
 .addEventListener('click', () => {
 data[course] = true
 docRef.update(data)
 })
 } else {
 //user does not exist..
 }
})

我的邏輯實際上要複雜得多,因為有其他的可移動部分,但你明白了!

通過調用 doc.data(),我初始化了文檔數據,當按鈕被點擊時(我假設它是“我完成了課程”的按鈕),我將 true 布爾值關聯到俱樂部標識符。

稍後,在每次加載課程列表頁面時,我可以初始化頁面並為已完成的課程分配一個類,就像這樣:

for (const [key, value] of Object.entries(data[course])) {
 const element = document.querySelector('.course-' + course)
 if (element) {
 element.classList.add('completed')
 }
}

權限問題

我在測試模式下啟動了 Firebase,還記得嗎?這使得數據庫對每個人都是開放的 - 對具有公開且在前端代碼中發布的訪問密鑰的所有人都是開放的。

所以我必須做一件事:決定允許的權限級別。

這給我帶來了一個非常重要的問題。

使用 Firebase 控制台,在 Rules 下,我們可以設置權限。最初,這是默認規則:

rules_version = '2';
service cloud.firestore {
 match /databases/{database}/documents {
 match /{document=**} {
 allow read, write;
 }
 }
}

我將規則更改為 read, update,這樣只允許更新文檔,不允許創建新文檔:

rules_version = '2';
service cloud.firestore {
 match /databases/{database}/documents {
 match /{document=**} {
 allow read, update;
 }
 }
}

但我無法阻止人們使用 Firebase API 在瀏覽器中進行操作,從而列出集合中的所有其他文檔 - 從而獲取到其他人的文件。

雖然這並沒有涉及到任何敏感數據,但無法發布這段代碼。

通過自定義 API,從前端將代碼移至後端

權限問題是一個擋路石。

我考慮刪除所有我已經有的代碼,但最終我想出了一個辦法,完全隱藏了瀏覽器中的所有 API 訪問,並使用 Node.js 服務器運行 Firebase API。

這也是隱藏服務所需的私密/密鑰的常見方法:將其隱藏在你控制的服務器後面。

我創建了一個自己的服務器上的一組端點,例如:

  • POST 到 /course 來設置一門課程已完成
  • POST 到 /data 來獲取與用戶關聯的數據

並使用 Fetch API 訪問它們:

const options = {
 method: 'POST',
 headers: {
 'Accept': 'application/json',
 'Content-Type': 'application/json'
 },
 body: JSON.stringify({ id, course, lesson })
}
const url = BASE_URL + '/lesson'
fetch(url, options).catch(err => {
 console.error('Request failed', err)
})

當然,所有與按鈕點擊等相關的邏輯仍然存在於客戶端代碼中,我只是將 Firebase 邏輯移到了其他地方。

在 Node.js 服務器端,我使用 npm install firebase 安裝了官方的 firebase 包並引入它:

const firebase = require('firebase')

我設置了一個 Express 服務器,使用 CORS,並初始化了 Firebase:

const firebaseConfig = {
 apiKey: process.env.APIKEY,
 authDomain: process.env.AUTHDOMAIN,
 projectId: process.env.PROJECTID
}

firebase.initializeApp(firebaseConfig)
const db = firebase.firestore()

然後,代碼與我在前端使用的代碼完全相同,只是現在在 HTTP 端點調用時觸發。這是從我們的集合返回特定文檔的代碼:

const getData = async (id) => {
 const doc = await db.doc(`membership/${id}`).get()
 const data = doc.data()
 if (!data) {
 console.error('member does not exist')
 return
 }
 return data
}

app.post('/data', cors(), async (req, res) => {
 const id = req.body.id
 if (id) {
 res.json(await getData(id))
 return
 }
 res.end()
})

這是將一門課程設置為已完成的 API 代碼:

const setCourseAsCompleted = async (id, course) => {
 const doc = await db.doc(`membership/${id}`).get()
 const data = doc.data()
 if (!data) {
 console.error('member does not exist')
 return
 }
 if (!data[course]) {
 data[course] = {}
 }
 data[course]['done'] = true
 db.doc(`membership/${id}`).update(data)
}

app.post('/course', cors(), (req, res) => {
 const id = req.body.id
 const course = req.body.course
 if (id && course) {
 setCourseAsCompleted(id, course)
 res.end('ok')
 return
 }
 res.end()
})

基本上就是這樣了。還需要一些更多的代碼來處理其他邏輯,但 Firebase 的精髓就是我所發布的這個。現在我也可以為我的服務器端服務添加用戶,並將所有其他對於 Firebase API 的訪問限制在僅允許我的服務器進行,從而加強它的安全性。