Node with React: Fullstack Web Development 课程手记(三)——MongoDB

上篇地址

MongoDB简介

  • MongoDB是一个基于文档(document)的数据库。在MongoDB中,数据是以Collection的形式来组织的,也就是一个Collection表明一种数据。一个Collection中的每条记录(document/record)没必要拥有相同的字段,也就是说咱们能够动态地为数据添加、减小或者修改字段。以下图所示,不一样的User记录具有能够拥有不一样的字段。
  • 咱们使用mongoose来进行数据库的操做。这其中包括两个部分:js和数据库。js部分每一个Model Class对应数据库部分的每一个Collection,js部分的每一个实例对应数据库部分的每条记录(record)。

add mongoDB

  • 使用mongoDB有两种方式:本地安装;远程安装。本次课采用后者,使用MongoDB后的系统架构以下图所示。
  • 登录 mlab.com,建立帐号,登录,建立一个免费的database,进入其控制面板。建立管理员用户名和密码。done!
  • 在server端引入mongoose,并链接咱们刚才建立的数据库。首先安装mongoose,npm install --save mongoose。 刚才在建立数据库成功的页面有这样一句话To connect using a driver via the standard MongoDB URI (what's this?):。这句话后面的内容就是咱们要访问这个数据库的URI。把里面的<dbuser>dbpassword改成咱们刚才建立管理员的用户名和密码,就能够访问了。由于这个信息也属于敏感信息,因此把这部份内容写在./config/keys.js中,在index.js中引入,并使用的代码以下所示:
const keys = require('./config/keys');
const mongoose = require("mongoose");
mongoose.connect(keys.mongoURI);复制代码
  • 这里看一下咱们所处的状态和接下来要作的事情。首先咱们有了用于存储数据的MongoDB和用于操做数据的mongoose。接下来咱们要对访问的用户进行检查,检查他们是否在咱们的存储记录中,若是在就让他登录,若是不在点击受权,咱们用受权返回的GoogleID为内容建立一条新的记录,那么当用户下次进入网站的时候就没必要再次受权了。
  • 接下来建立model。MongoDB自身的collection是能够包含不一样结构 的记录的,可是mongoose却须要预先定义collection的记录解构是什么样子的。所以这里须要预先设置Schema。传入的参数是一个对象,定义collection的各个key,及对应的数据类型。(容许在中途修改Schema)。这里建立一个新的文件./models/Users.js
const mongoose = require('mongoose');
const { Schema } = mongoose;
// es6 解构赋值 <=> const Schema = mongoose.Schema
const userSchema = new Schema({
    googleId: String
});复制代码
  • mongoose是经过建立一个class的方式建立一个collection的。接下来的代码建立一个名字为users的collection,使用的Schema就是上面建立的userSchema,这个Schema定义了这个collection的每一个记录都包含一个类型为string,名为googleId的数据。
mongoose.model('users', userSchema);复制代码
  • 最后再index.js中引入./models/Users.js文件,以使这一堆代码运行。
require('./models/Users');复制代码
  • 而后咱们要作的是就是要把从Google服务器拿到的id,存储为一个Collection为users的记录。咱们是在./services/passport使用new GoogleStrategy()方法中的回调函数拿到用户资料的。所以你咱们将会在那个回调函数中使用mongoose将数据存储到Collection为users数据库中。首先咱们要拿到名为users的collection。代码以下,注意咱们使用了一样的函数mongoose.use,这个函数当传入Schema时,是建立collection,当只传名字的时候,就是取到Collection。
const mongoose = require('mongoose');
const User = mongoose.model('users');复制代码
  • new GoogleStrategy()传入的回调函数中,咱们建立一个user实例。注意,这里new User()是建立了一个JavaScript对象,并未将数据存入数据库中(参考上面mongoose vs mongoDB的图),要将数据写入数据库,必须调用这个对象的save方法。
new User({ googleId: profile.id }).save();复制代码
  • 注意咱们是在./models/Users.js中定义名为users的collection的,但在./services/passport.js中使用了这个collection,所以在index.js中引入这两个文件时要注意前后顺序,前者要先引用。
  • 如今访问localhost:5000/auth/google,而后去mlab的面板上刷新,能够看到Collection目录下多了一条名为user的条目,点击进去能够看到有一条记录,其中的googleId就是你刚才用于受权的googleId帐户的id。可是如今有一个问题,当咱们重复这个操做,就会发现咱们的数据库中多了一条重复的记录。而咱们想要的结果是,若是已经有了相同的记录就再也不建立记录。
  • 咱们接着使用mongoose class的查询功能,检查当前用户是否存在,若是不存在才新建一个。逻辑变为:
User.findOne({ googleId: profile.id }).then((existingUser) => {
    if (!existingUser) {
        new User({
            googleId: profile.id
        }).save();
    }
});复制代码
  • 注意,全部的数据库操做都是异步的,mongoose为咱们封装了Promise来对返回结果进行操做,所以这里将判断逻辑写在了then的回调函数中。
  • 还没完,咱们尚未用户的信息传递给passport。如何把用户信息传递给passport呢。注意以前的回调函数中传入了done参数,done是一个函数,其第一个参数是为了传递错误信息,第二个参数是为了传输passport验证所需的信息。因此咱们能够把user信息传入done的第二个参数,从而传递给passport,具体代码以下:
User.findOne({ googleId: profile.id }).then(existingUser => {
    if (!existingUser) {
        new User({
            googleId: profile.id
        })
            .save()
            .then(user => {
                done(null, user);
            });
    } else {
        done(null, existingUser);
    }
});复制代码
  • 为何咱们要搞数据库呢?——固然是为了验证流程了。咱们此次采用的是使用cookie的验证流程,而全部数据库这一套东西都是为了产生cookie。
  • 用户访问网站,经过查找数据库来判断是新用户仍是老用户。
  • 是新用户,那么在数据库产生一个新的记录,并用这个新的数据库来产生cookie,并返回给浏览器。之后浏览器在对这个服务器产生其余请求时,cookie将自动携带,服务器就能识别这个请求是属于这个用户了。
  • 若是是老用户,直接从数据库中取出用户信息,产生cookie,并给浏览器设置cookie。设置cookie的目的同上。
  • 具体从用户信息到cookie是经过序列化(serialize)完成的,从cookie到用户信息是经过反序列化(deserialize)完成的。
  • 序列化和反序列化是passport帮咱们完成的。分别以下:
// 序列化
passport.serializeUser((user, done) => {
    done(null user.id);
});复制代码
  • 这里传入的参数user正式咱们在从数据库取到(建立)一条用户信息后传递给done函数的值。实际上就是数据库中的用户信息。这里的user.id是数据库自动生成的id,而非googleId。缘由有两个:一、咱们可能会用到不一样的验证方法(Facebook、Wechat等),不一样系统下采用profile.id没法保证惟一性;二、这里咱们使用googleId的惟一做用就是为了受权登录,登录后的一切请求都与googleId无关,因此以后请求中携带的cookie信息(正是此次序列化所生成的)应该包含数据库id而非googleId。
passport.deserializeUser((id, done) => {
    User.findById(id).then((user) => {
        done(null, user);
    })
})复制代码
  • 反序列化中id就是cookie信息,也就是数据库产生的id,咱们在数据库中根据这个id找到用户信息,以进行进一步操做,最后调用done函数,以完成反序列化。
  • 接下来咱们要完成的就是读写cookie的操做。这里咱们使用cookie-session这个包,来帮助咱们实现对cookie的操做。先看代码,而后解释原理。
  • 注意,这里引入了cookieKey,这实际上是咱们呢在./config/keys中加入的一段随机字符串(仅字母和数字),用于对cookie信息加密。
// index.js
const passport from 'passport';
const cookieSession from 'cookie-session';

app.use(
    cookieSession({
        maxAge: 7*24*3600*1000,
        keys: [keys.cookieKey]
    )
);
app.use(passport.initialize());
app.use(passport.session());复制代码
  • 至此全部的受权、验证工做已经作完了。cookie-session passport是怎么完成这个工做呢。对于接下来的请求来讲,每一个请求都会先经过cookie-session,cookie-session从中提取cookie信息、解密而后反序列化,获得一个用户实例。最后把这个用户实例挂在req对象中,而后才把这个req对象传递给实际的route handler。
  • 为了验证上述逻辑是对的,咱们新增一个route handler,其中只返回req中挂的user,看其中是否为实例化的model。而后咱们先经过localhost:5000/auth/google登录,而后再访问localhost:5000/api/current_user,查看当前请求所携带的user,不出意外正是googleId为刚才受权的user实例对象。
// ./routes/authRoutes.js
app.get('/api/current_user', (req, res) => {
  res.send(req.user);  
})复制代码
  • 接下来增长一个用于注销用户的api,以方便咱们以后的测试。咱们以前提到,passport为传递给实际route handler的req对象增长了user,实际上passport还增长了别的东西,其中一个就是logout方法。咱们经过调用req.logout(),就能够实现用户的注销登陆。
app.get('/api/logout', (req, res) => {
    req.logout();
    res.send(req.user); // logout后应该为undefined
});复制代码
  • 接下来解释几处比较奇怪的代码。
  • 首先是index.js中几处app.use。咱们知道express app的做用就是接受请求,并给出响应。app.use中传入的是function,这些function叫作中间件,做用是修改接收的请求,而后再把它传递给实际处理请求的route handler。对于全部请求通用的逻辑比较适合写在中间件中,好比这里的验证用户的逻辑。由于不少请求都须要验证用户的身份才能给出合适的响应,与其在每一个route handler都写相同的逻辑(读cookie->解密->反序列化->拿到user model实例),咱们把逻辑写在中间件中,全部的请求都会走一遍。这里咱们实际用到了两个中间件的逻辑,一个是cookie-session,一个是passport。
  • cookie-session做用是从请求中拿到cookie并解密,那它是如何把解密后的cookie传递给passport的呢?若是咱们把/api/current_user中的逻辑改成res.send(req.session),咱们会看到一个实际返回的是一个像下面代码所示的对象。这说明此时req.session中存储的是解密以后的cookie信息,其实是cookie-session把这段解密后的信息挂在了req.session上传递给了passport。而后passport再拿这段信息进行反序列化。
passport: {
    user: "59f893ef4a3dde26c5d9bce2"
}复制代码
  • express官方推荐处理的cookie的库有两个,一个是咱们此次用的cookie-session,另外一个是express-session,这里主要讲一下两者的区别:就是用户信息存储方式不一样。在cookie-session中,cookie就是session,也就是说cookie中包含了session的全部信息。
  • 在express-cookie中,cookie提供对session的引用,具体讲,session是有本身的存储空间(session_store)的,实际要取的数据是从这个存储空间中取的,cookie只提供对这个session的引用(经过session_id)。相比之下后者能存储更多的数据,前者只能存储4KB数据。可是后者可能要设置remote存储,因此更麻烦。
相关文章
相关标签/搜索