在个人前一篇小文中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
特性Connect
Mongoose
>=4.1.2+Node.js
4 6 8 10Mongodb
>=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"}' }
connect-mongo
和express-session
而后调用connect-mongo
将express-sessino
传入express-session
中间件的时候对于store
选传入这个类的实例对象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) }));
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 }) }));
这种状况下你须要将一个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的建立以及修改等操做.
connect-mongo
只会使用配置了过时时间的cookie,若是没有设置则会建立一个新的cookie而且使用tll
选项来指定过时时间:
app.use(session({ store: new MongoStore({ url: 'mongodb://localhost/test-app', ttl: 14 * 24 * 60 * 60 // 默认过时时间为14天 }) }));
注意:用户的每次访问都会刷新过时时间.
默认状况下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 // 单位分钟 }) }));
app.use(session({ store: new MongoStore({ url: 'mongodb://localhost/test-app', autoRemove: 'disabled' }) }));
若是你使用的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除外.
sessions
MemoryStore
进行存储也不算是暗坑吧,一用有两点:
connect-mongo
会报错,虽然会被Express拦截可是这个模块没有提供error事件.express-session
中间件,可是失败了中间件没有效果.connect-mongo
模块npm地址https://www.npmjs.com/package...