资讯专栏INFORMATION COLUMN

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

zhangxiangliang / 1387人阅读

摘要:简介在我的前一篇小文中小书提到了可以更换会话储存那么这篇文章我们就来讲讲在进行会话管理的时候如何将会话数据保存在外部数据库中本文中我们使用用作会话储存数据库本文中使用的模块以及版本号一览模块名称版本号特性支持支持所有版本的支持支持

简介

在我的前一篇小文中express-session小书提到了express-session可以更换会话储存.

那么这篇文章我们就来讲讲express在进行会话管理的时候如何将会话数据保存在外部数据库中,本文中我们使用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客户端和服务器可以是多对多的关系,故有如下组合.

一个客户端连接多个服务器

多个客户端连接一个服务器

多个客户端连接多个服务器

一个客户端连接一个服务器

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

这种情况下,一般服务器监听一个端口,而我们希望可以共享同一个mongodb驱动的实例.

但是在一般情况下,我们的mongodb数据库不可能只用于会话管理任务,所以本文复用同一个连接(端口).

只要复用同一个连接可以完成,那么使用多带带的驱动实例来用作会话管理也就不在话下了.

起步

首先我们引入所有的模块:

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-mongoexpress-session然后调用connect-mongoexpress-sessino传入

获取上一步返回的类,然后使用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转为你想要的任何键然后进行储存

暗坑

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

Mongodb客户端正常关闭后connect-mongo会报错,虽然会被Express拦截但是这个模块没有提供error事件.

Express中间件必须同步挂载?在我的例子中尝试异步加载express-session中间件,但是失败了中间件没有效果.

connect-mongo模块npm地址
https://www.npmjs.com/package...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/19449.html

相关文章

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

    摘要:简介在我的前一篇小文中小书提到了可以更换会话储存那么这篇文章我们就来讲讲在进行会话管理的时候如何将会话数据保存在外部数据库中本文中我们使用用作会话储存数据库本文中使用的模块以及版本号一览模块名称版本号特性支持支持所有版本的支持支持 简介 在我的前一篇小文中express-session小书提到了express-session可以更换会话储存. 那么这篇文章我们就来讲讲express在进...

    jackzou 评论0 收藏0
  • 微信小程序server-2-实现会话

    摘要:安装安装及其客户端命令行工具查看版本启动创建目录,用于数据和日志存储启动注首次启动可能会花费大概时间可以使用下面的命令来检查是否启动成功注默认监听端口添加用户登录本地服务创建用户退出安装模块实现小程序的会话功能 1.安装MongoDB #安装 MongoDB及其客户端命令行工具 yum install mongodb-server mongodb -y #查看版本 mongod --v...

    Eminjannn 评论0 收藏0
  • node中的session

    摘要:当会话过期或被放弃后,服务器将终止该会话。原来中间件生成的是一个对象,里面包含了信息。这个有一个过期时间,比如,上面代码中设置的是小时。也就是说,小时后,这个在浏览器中会自动消失。 前言 在上一篇中node中的cookie,对cookie进行了相关介绍,本篇将继续前行,对session进行说明。 session是什么 session不就是会话嘛,那什么是会话呢?会话是一个比连接粒度更大...

    yankeys 评论0 收藏0
  • node中的session

    摘要:当会话过期或被放弃后,服务器将终止该会话。原来中间件生成的是一个对象,里面包含了信息。这个有一个过期时间,比如,上面代码中设置的是小时。也就是说,小时后,这个在浏览器中会自动消失。 前言 在上一篇中node中的cookie,对cookie进行了相关介绍,本篇将继续前行,对session进行说明。 session是什么 session不就是会话嘛,那什么是会话呢?会话是一个比连接粒度更大...

    fredshare 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<