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...
相關文章
相關標籤/搜索