我需要为我的会员俱乐部(我教编程的地方)的一些数据创建一个存储器。
我希望我的用户能够通过点击一个按钮,手动说出 "我完成了这个课程"。
基本上,我想为每个用户存储一个特定的对象。
设置Firebase
我决定为此使用Firebase,特别是他们提供的Firestore数据库。
它的免费层级很慷慨,每月最多可以存储1GB的数据和10GB的网络传输。这远远超过了我对自己需求的估计
打开Firebase网站,firebase.google.com/
Firebase是谷歌的产品,所以一旦你登录了谷歌,你基本上也就登录了Firebase。
我点击 "创建一个项目",创建了一个新的Firebase项目

我给它起了个名字。

就这样了。

我点击了iOS和Android旁边的 "Web "图标,并输入了应用程序的名称。

然后Firebase立即给了我所需要的访问密钥,以及一些示例代码。

紧接着,Firebase提示我为数据库添加一些安全规则。
你可以默认选择2种情况:对所有人开放,或者对所有人关闭。我开始向所有人开放,他们称之为测试模式。

就这样了。我已经准备好了,通过创建一个集合。
什么是集合?在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_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。
这也是隐藏服务所需的私人/秘密密钥的常用方法:将它们隐藏在你控制的服务器后面。
我没有从浏览器中调用Firebase,而是在自己的服务器上创建了一组端点,例如。
- 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的访问,加强其安全性。