对于一个网站来说,一个不可避免的问题就是用户登录,这就牵扯到 session 问题了。为此我们需要在app.js中引入两个middleware,和express-session,上一章的代码6.2.1已经介绍过cookie-parser,接下来重点介绍一下,在app.js中添加如下代码:

    代码 7.1.1 引入session
    其实express默认的session是存储在内存里的,但是这种做法不适合在生产环境使用,首先node如果使用cluster模式的话,内存无法共享,也就是说只能使用单进程;其次,如果在线人数一直增多的话,会造成内存猛增。所以这里的store参数使用了redis,这同样也意味着你需要在你本地(或者远程机器上)启动redis服务,否则程序会报错。
    session 函数的 key 参数代表生成cookie的名称。resave参数设置默认为true,代表每次请求结束都会重写store中的数据,不管当前的session有没有被修改。saveUninitialized参数值默认为true,代表将未赋值过的session写入到store,也就是说假设我们的网站需要登录,那么在未登陆之前,也会往store写入数据。所以我们将resavesaveUninitialized都设置为了false。

    为了减少篇幅,给出的代码都是片段形式:

    1. <form method="post" action="/user/login" id="loginForm">
    2. <p><input name="username" /><label for="username">用户名</label><p/>
    3. <p><input name="password" type="password" /><label for="password">密码</label><p/>
    4. <p><input type="submit" value="登陆" /><p/>
    5. </form>
    6. <script src="//upcdn.b0.upaiyun.com/libs/jquery/jquery-1.10.2.min.js" type="text/javascript"></script>
    7. <script type="text/javascript">
    8. $(document).ready(function() {
    9. $('#loginForm').submit(function() {
    10. var $this = $(this);
    11. $.ajax({
    12. method:$this.attr('method'),
    13. url:$this.attr('action'),
    14. data:$this.serialize(),
    15. dataType:'json'
    16. }).done(function(result) {
    17. if (result.code == 0) {
    18. return location.href = '/user/admin';
    19. }
    20. alert(result.msg || '服务器异常');
    21. }).fail(function() {
    22. alert('网络异常');
    23. return false;
    24. });
    25. });
    26. </script>

    代码7.1.3 登陆后端代码
    代码7.1.3中通过req.session.user来给session增加一个user的属性,在代码7.1.2中登陆成功后要跳转到/user/admin地址上去,我们接下来看这个地址映射的后端代码:

    1. exports.admin = function(req, res) {
    2. var user = req.session.user;
    3. res.render('user/admin',{user:user});
    4. }

    代码 7.1.4 读取session
    通过req.session.user,就可以方便的将之前存储的user属性给读取出来。

    在7.2中,我们已经讲述了怎样使用mongodb了,下面就有机会对于我们7.1中提到的登陆进行改进了。虽然登陆在前端页面登陆仅仅是一个表单,但是在后台处理的流程就不是那么简单。这次我们决定读取数据库中的账号信息进行登陆验证,为此我们创建文件夹models,在其内新建文件user_model.js

    代码 7.3.1 登陆验证逻辑

    那么现在控制器中的代码就可以这么写:

    1. exports.loginWithDb = function(req, res) {
    2. var _body = req.body;
    3. var username = _body.username;
    4. var password = _body.password;
    5. if (!username) {
    6. return res.send({code:1,msg:'用户名不能为空'});
    7. }
    8. userModel.loginCheck(username,password,function(err,item) {
    9. if (err) {
    10. return res.send({code:1,msg:err});
    11. }
    12. req.session.user = item;
    13. res.send({code:0});
    14. });
    15. };

    代码 7.3.2 登陆验证控制器代码
    我们在路由器中增加一个链接 /user/login-with-db 指向代码 7.3.2 中的控制器函数,修改代码7.1.2中的表单提交地址即可。

    之前的章节中介绍过express的middleware,翻译一下就是中间件,下面的内容其实是做一个中间件,但是为啥我给它起名叫拦截器呢,因为我认为对于业务逻辑处理叫拦截器更贴切,因为我理解的middleware仅仅负责解析http数据,不处理业务逻辑。仅仅是个人见解。
    之前我们已经做了登陆操作,但是对于一个网站的若干地址(比如说后台地址),不登录是没法用的,我们需要用户在加载这些地址的时候,如果检测到当前处于未登录状态,就统一跳转到登陆页。在每一个控制器中都做一遍登陆状态判断来决定是否跳转,显然是一个笨拙的方法。但是如果使用了拦截器,一切问题就显得简单了。
    我们新建文件夹,然后在其内新建文件auth_filter.js:

    代码 7.4.1 授权拦截器逻辑
    同时我们在app.js中引入这段代码:

    1. var cookieParser = require('cookie-parser');
    2. var bodyParser = require('body-parser');
    3. var RedisStore = require('connect-redis')(session);
    4. var redis = require('redis');
    5. var routes = require('./routes/index');
    6. var authFilter = require('./filters/auth_filter');
    7. app.use(bodyParser.json());
    8. app.use(bodyParser.urlencoded({ extended: false }));
    9. app.use(cookieParser());
    10. app.use(express.static(path.join(__dirname, 'public')));
    11. app.use(session({
    12. secret: 'GG##@$',
    13. cookie:{domain:'localhost'},
    14. key:'express_chapter6',
    15. resave:false,
    16. saveUninitialized:false,
    17. store: new RedisStore({
    18. client:redis.createClient(6379, '127.0.0.1'),
    19. ttl:3600*72,
    20. db:2,
    21. prefix:'session:chapter6:'
    22. })
    23. }));
    24. app.use('/', routes);