在個人前一篇小文中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...