一個設置 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 的訪問限制在僅允許我的服務器進行,從而加強它的安全性。