如何使用Firebase Firestore作为你的数据库

588 阅读3分钟

我需要为我的会员俱乐部(我教编程的地方)的一些数据创建一个存储器。

我希望我的用户能够通过点击一个按钮,手动说出 "我完成了这个课程"。

基本上,我想为每个用户存储一个特定的对象。

设置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的访问,加强其安全性。