Express使用mongodb管理会话储存 connect-mongo模块简介

简介

在个人前一篇小文中express-session小书提到了express-session能够更换会话储存.javascript

那么这篇文章咱们就来说讲express在进行会话管理的时候如何将会话数据保存在外部数据库中,本文中咱们使用mongodb用做会话储存数据库.java

本文中使用的模块以及版本号一览:mongodb

模块名称 版本号
express 4.16.4
mongodb 3.1.8
express-session 1.15.6
connect-mongo 2.0.3

connect-mongo特性

  • 支持Express5
  • 支持全部版本的Connect
  • 支持Mongoose>=4.1.2+
  • 支持原生Mongodb驱动>=2.0.36
  • 支持Node.js 4 6 8 10
  • 支持Mongodb>=3.0

事前分析

因为mongodb客户端和服务器能够是多对多的关系,故有以下组合.数据库

  • 一个客户端链接多个服务器
  • 多个客户端链接一个服务器
  • 多个客户端链接多个服务器
  • 一个客户端链接一个服务器

本文主要讲解一个客户端链接一个服务器.express

这种状况下,通常服务器监听一个端口,而咱们但愿能够共享同一个mongodb驱动的实例.npm

可是在通常状况下,咱们的mongodb数据库不可能只用于会话管理任务,因此本文复用同一个链接(端口).json

只要复用同一个链接能够完成,那么使用单独的驱动实例来用做会话管理也就不在话下了.segmentfault

起步

首先咱们引入全部的模块:api

const
    Express = require('express')(),
    MongoClient = require('mongodb').MongoClient,
    ExpressSession = require('express-session'),
    MongoStore= require('connect-mongo')(ExpressSession);

看起来connect-mongo须要将express-session包装一下,这步是固定的.浏览器

接下来咱们定义几个常量用于链接数据库:

const
    UrlOfDb = 'mongodb://localhost:27017',
    NameOfDb = 'demo',
    Client = new MongoClient(UrlOfDb);// 建立mongodb客户端

客户端链接数据库:

Client.connect((error) => {

    if (error) {
        throw error;
    }
    
});

使用一个数据表,而且查询几条数据:

const
        DataBase = Client.db(NameOfDb),
        Collection = DataBase.collection('sessions');
        
    Collection.find({}).toArray((error, result) => {

        if (error) {
            throw error;
        }
    
        for (const element of result) {
    
            console.log(element);
    
        }

    });

到目前为止咱们没有进行session管理,你能够替换本例中的数据表名称用于测试一下运行是否正常.

完整代码:

const
    Express = require('express')(),
    MongoClient = require('mongodb').MongoClient,// 获取数据库驱动
    ExpressSession = require('express-session'),// 获取session中间件
    MongoStore= require('connect-mongo')(ExpressSession);// 获取session储存插件
    
const
    UrlOfDb = 'mongodb://localhost:27017',
    NameOfDb = 'demo',
    Client = new MongoClient(UrlOfDb);// 建立客户端
    
Client.connect((error) => {

    if (error) {
        throw error;
    }
    
    const
        DataBase = Client.db(NameOfDb),// 获取数据库
        Collection = DataBase.collection('sessions'); // 获取数据表
    
    // 查询数据表
    Collection.find({}).toArray((error, result) => {

        if (error) {
            throw error;
        }

        for (const element of result) {

            console.log(element);

        }

    });
    
});

如今咱们来使用express-session中间件,而且替换掉默认的储存:

// +++++

const
    DataBase = Client.db(NameOfDb),// 获取数据库
    Collection = DataBase.collection('sessions'),// 获取数据表
    MongoStoreInstance = new MongoStore({ // 建立一个储存实例,传入db参数对于的数据库对象
        db:DataBase
    });

// 使用中间件
Express.use(ExpressSession({
    secret: 'hello mongo',// cookie签名
    cookie: {maxAge: 1800000},
    rolling:true,
    saveUninitialized:true,
    resave: false,
    store:MongoStoreInstance // 替换掉默认的储存
}));

// +++++++

注意:connect-mongo会在该database下建立一个sessions的数据表(没有这个数据表的状况下).

添加一个路由用于完成简单的验证,用于测试是否正常工做:

Express.get('/',(request,response)=>{

    if(request.session.name){

        response.send(`欢迎回来${request.session.name}`);

        return ;
    }
    
    // 使用查询字符串看成保存的信息
    request.session.name = request.query.name;
    request.session.pwd = request.query.pwd;

    response.send(`欢迎登陆${request.session.name}`);

});

// 启动服务器
Express.listen(8888, function () {

    console.log('server is listening 8888 port!');

});

完整代码:

const
    Express = require('express')(),
    MongoClient = require('mongodb').MongoClient,
    ExpressSession = require('express-session'),
    MongoStore= require('connect-mongo')(ExpressSession);


const
    UrlOfDb = 'mongodb://localhost:27017',
    NameOfDb = 'demo',
    Client = new MongoClient(UrlOfDb);


function destroyDb(Client) {

    return destroyDb = function () {

        const info = 'Client has been closed!';

        Client.close();
        Client = null;
        console.log(info);

        return info;

    }
}

Client.connect((error) => {

    if (error) {
        throw error;
    }

    const
        DataBase = Client.db(NameOfDb),
        Collection = DataBase.collection('sessions'),
        MongoStoreInstance = new MongoStore({
            db:DataBase
        });


    Express.use(ExpressSession({
        secret: 'hello mongo',
        cookie: {maxAge: 1800000},
        rolling:true,
        saveUninitialized:true,
        resave: false,
        store:MongoStoreInstance
    }));

    // 使用闭包将关闭数据库挂载到全局
    destroyDb(Client);

    // 展现复用一个链接
    Collection.find({}).toArray((error, result) => {

        if (error) {
            throw error;
        }

        for (const element of result) {

            console.log(element);

        }

    });

    Express.get('/',(request,response)=>{

        if(request.session.name){

            response.send(`欢迎回来${request.session.name}`);

            return ;
        }

        request.session.name = request.query.name;
        request.session.pwd = request.query.pwd;

        response.send(`欢迎登陆${request.session.name}`);

    });

    Express.get('/closedatabase',  (request, respnose) => {

        respnose.send(destroyDb());

    });


    Express.listen(8888, function () {

        console.log('server is listening 8888 port!');

    });

});

注意:我没有删除数据库表的常规输出,在这个例子启动的时候,你会发现他们共用了同一个链接,启动的时候会先输出数据表中的内容.

测试

在浏览器中输入以下内容:

http://localhost:8888/?name=ascll&pwd=123456

浏览器输出:

欢迎登陆ascll

直接再次访问该页面:

http://localhost:8888/

浏览器输出:

欢迎回来ascll

此时在数据库中手动查询后,或者重启本项目,你会在控制台中发现上次留下的session记录:

{ _id: 'qbP36wE0nJkvtyNqx_6Amoesjjcsr-sD',
  expires: 2018-12-14T08:27:19.809Z,
  session:
   '{"cookie":{"originalMaxAge":1800000,"expires":"2018-12-14T08:20:21.519Z","httpOnly":true,"path":"/"},"name":"ascll","pwd":"123456"}' }

使用总结

  1. 引入connect-mongoexpress-session而后调用connect-mongoexpress-sessino传入
  2. 获取上一步返回的类,而后使用express-session中间件的时候对于store选传入这个类的实例对象

api

建立

Express 4.x, 5.0 and Connect 3.x:

const session = require('express-session');
const MongoStore = require('connect-mongo')(session);
 
app.use(session({
    secret: 'foo',
    store: new MongoStore(options)
}));

Express 2.x, 3.x and Connect 1.x, 2.x:

const MongoStore = require('connect-mongo')(express);
 
app.use(express.session({
    secret: 'foo',
    store: new MongoStore(options)
}));

链接到MongoDb

使用mongoose

const mongoose = require('mongoose');
 
// 基本使用
mongoose.connect(connectionOptions);
 
app.use(session({
    store: new MongoStore({ mongooseConnection: mongoose.connection })
}));
 
// 建议使用方式,这样能够复用链接
const connection = mongoose.createConnection(connectionOptions);
 
app.use(session({
    store: new MongoStore({ mongooseConnection: connection })
}));

使用Mongo原生Node驱动

这种状况下你须要将一个mongodb驱动的一个数据库实例传递给connect-mongo.若是数据库没有打开connect-mongo会自动帮你链接.

/*
    这里有不少种方式来获取一个数据库实例,具体能够参考官网文档.
*/

app.use(session({
    store: new MongoStore({ db: dbInstance }) // 别忘了MongoStore是connect-mongo传入express-session后返回的一个函数
}));

// 或者也可使用Promise版本

app.use(session({
    store: new MongoStore({ dbPromise: dbInstancePromise })
}));

经过链接字符串建立一个链接

// Basic usage
app.use(session({
    store: new MongoStore({ url: 'mongodb://localhost/test-app' })
}));
 
// Advanced usage
app.use(session({
    store: new MongoStore({
        url: 'mongodb://user12345:foobar@localhost/test-app?authSource=admins&w=1',
        mongoOptions: advancedOptions // See below for details
    })
}));

事件

一个MongoStore实例有以下的事件:

事件名称 描述 回调参数
create session建立后触发 sessionId
touch session被获取可是未修改 sessionId
update session被更新 sessionId
set session建立后或者更新后(为了兼容) sessionId
destroy session被销毁后 sessionId

使用咱们以前的例子中添加以下的代码:

// +++
MongoStoreInstance.on('create',(sessionId)=>{
    console.log('create',sessionId);
});

MongoStoreInstance.on('touch',(sessionId)=>{
    console.log('create', sessionId);
});

MongoStoreInstance.on('update',(sessionId)=>{
    console.log('update', sessionId);
});

MongoStoreInstance.on('set',(sessionId)=>{
    console.log('set', sessionId);
});

MongoStoreInstance.on('destroy',(sessionId)=>{
    console.log('destroy', sessionId);
});

// +++

清空cookie后再次运行服务器,多执行几个操做你就能够看到session的建立以及修改等操做.

session过时处理

基本处理方式

connect-mongo只会使用配置了过时时间的cookie,若是没有设置则会建立一个新的cookie而且使用tll选项来指定过时时间:

app.use(session({
    store: new MongoStore({
      url: 'mongodb://localhost/test-app',
      ttl: 14 * 24 * 60 * 60 // 默认过时时间为14天
    })
}));

注意:用户的每次访问都会刷新过时时间.

删除过时session

默认状况下connect-mongo使用MongoDB's TTL collection特性(2.2+)用于自动的移出过时的session.可是你能够修改这种行为.

connect-mongo会在开始的时候建立一个TTl索引,前提是你的Mongo db版本在(2.2+)且有权限执行这一操做.

app.use(session({
    store: new MongoStore({
      url: 'mongodb://localhost/test-app',
      autoRemove: 'native' // Default
    })
}));

注意:这种默认的行为不适用于高并发的状况,这种状况下你须要禁用默认模式,而后自行定义TTl索引.

使用兼容模式

若是你使用了Mongodb的老版本或者不但愿建立TTL索引,你能够指定一个间隔时间让connect-mongo来删除这些过时的session.

app.use(session({
    store: new MongoStore({
      url: 'mongodb://localhost/test-app',
      autoRemove: 'interval',
      autoRemoveInterval: 10 // 单位分钟
    })
}));

禁用过时session删除

app.use(session({
    store: new MongoStore({
      url: 'mongodb://localhost/test-app',
      autoRemove: 'disabled'
    })
}));

session懒更新

若是你使用的express-session版本>=1.10,而后不但愿用户每次浏览页面的时候或刷新页面的时候都要从新保存,你能够限制一段时间内更新session.

app.use(express.session({
    secret: 'keyboard cat',
    saveUninitialized: false, // 若是不保存则不会建立session
    resave: false, // 若是未修改则不会保存
    store: new MongoStore({
        url: 'mongodb://localhost/test-app',
        touchAfter: 24 * 3600 // 指定触发间隔时间 单位秒
    })
}));

经过这样设置session只会在24小时内触发1次不管用户浏览多少次页面或者刷新多少次.修改session除外.

其余选项

  • collection 指定缓存数据表的名字默认sessions
  • fallbackMemory 回退处理默认使用MemoryStore进行存储
  • stringify 默认是true,若是为true则序列化和反序列化使用原生的JSON.xxx处理.
  • serialize 自定义序列化函数
  • unserialize 自定义反序列化函数
  • transformId 将sessionId转为你想要的任何键而后进行储存

暗坑

也不算是暗坑吧,一用有两点:

  1. Mongodb客户端正常关闭后connect-mongo会报错,虽然会被Express拦截可是这个模块没有提供error事件.
  2. Express中间件必须同步挂载?在个人例子中尝试异步加载express-session中间件,可是失败了中间件没有效果.

connect-mongo模块npm地址

https://www.npmjs.com/package...
相关文章
相关标签/搜索