Node.js开发入门—用MongoDB改造LoginDemo

这次的示例基于之前的LoginDemo(见使用cookie保持登录),我们引入MongoDB来保存用户数据。要运行这个示例,前提是MongoDB数据要正常运行(见Node.js开发入门——MongoDB与Mongoose)。示例运行的结果呢,和之前的LoginDemo是一样一样的。因此,我们就只分析引入数据库时项目本身的变化吧。

安装mongoose

命令行环境下导航到LoginDemo目录,执行下面的命令:

npm install mongoose --save

它会自动帮我们安装依赖,也会把mongoose作为依赖项写入项目的package.json文件。

数据库初始化脚本dbInit.js

dbInit.js用来初始化数据库,创建一个名为users的库、一个名为accounts的集合、插入两个账户。代码如下:

var crypto = require('crypto');
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/users');

function hashPW(userName, pwd){
  var hash = crypto.createHash('md5');
  hash.update(userName + pwd);
  return hash.digest('hex');
}

var db = mongoose.connection;

db.on('error', console.error.bind(console, 'connection error:'));

var count = 2;

function checkClose(){
  count = count - 1;
  if(count==0) mongoose.disconnect();
}

db.once('open', function() {
  console.log('mongoose opened!');
  var userSchema = new mongoose.Schema({
      name: {type: String, unique: true}, 
      hash: String,
      last: String
    }, 
    {collection: "accounts"}
    );
  var User = mongoose.model('accounts', userSchema);

  var doc = new User({
    name:"admin", hash: hashPW("admin","123456"), last:""
  });
  doc.save(function(err, doc){
    if(err)console.log(err);
    else console.log(doc.name + ' saved');
    checkClose();
  }); 

  doc = new User({
    name:"foruok", hash: hashPW("foruok","888888"), last:""
  });
  doc.save(function(err, doc){
    if(err)console.log(err);
    else console.log(doc.name + ' saved');
    checkClose();
  }); 
});

在启动网站前,执行“node dbInit.js”来做初始化。

在dbInit.js里,我使用了Mongoose的Document对象的save方法保存新建的文档,在“MongoDB与Mongoose”一文中已经用到了,不再啰嗦。

3. 重写users.js

备份一下原来的users.js,新的users.js如下:

var express = require('express');
var router = express.Router();
var crypto = require('crypto');

var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/users');
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
var User = null;
db.once('open', function() {
  console.log('mongoose opened!');
  var userSchema = new mongoose.Schema({
      name: {type: String, unique: true}, 
      hash: String,
      last: String
    }, 
    {collection: "accounts"}
    );
  User = mongoose.model('accounts', userSchema);
});

function hashPW(userName, pwd){
  var hash = crypto.createHash('md5');
  hash.update(userName + pwd);
  return hash.digest('hex');
}

function getLastLoginTime(userName, callback){
  if(!User){
    callback("");
    return;
  }
  var loginTime = Date().toString();
  User.findOne({name:userName}, function(err, doc){
    if(err) callback("");
    else{
      callback(doc.last);

      //update login time
      doc.update({$set:{last: loginTime}}, function(err, doc){
        if(err) console.log("update login time error: " + err);
        else console.log("update login time for " + doc.name);
      });
    }
  });
}

function authenticate(userName, hash, callback){
  if(!User){ callback(2); return;}
  var query = User.findOne().where('name', userName);
  query.exec(function(err, doc){
    if(err || !doc){ console.log("get user error: " + err); callback(2); return}
    if(doc.hash === hash) callback(0);
    else callback(1);
  });
}

router.requireAuthentication = function(req, res, next){
  if(req.path == "/login"){
    next();
    return;
  }

  if(req.cookies["account"] != null){
    var account = req.cookies["account"];
    var user = account.account;
    var hash = account.hash;
    authenticate(user, hash, function(ret){ 
      if(ret==0){
        console.log(req.cookies.account.account + " had logined.");
        next();
      }else{
        console.log("invalid user or pwd, redirect to /login");
        res.redirect('/login?'+Date.now());
      }
    });
  }else{
    console.log("not login, redirect to /login");
    res.redirect('/login?'+Date.now());
  }
};

router.post('/login', function(req, res, next){
  var userName = req.body.login_username;
  var hash = hashPW(userName, req.body.login_password);
  console.log("login_username - " + userName + " password - " + req.body.login_password + " hash - " + hash);
  authenticate(userName, hash, function(ret){
    switch(ret){
      case 0: //success
        getLastLoginTime(userName, function(lastTime){
        console.log("login ok, last - " + lastTime);
        res.cookie("account", {account: userName, hash: hash, last: lastTime}, {maxAge: 60000});
        //res.cookie("logined", 1, {maxAge: 60000});
        res.redirect('/profile?'+Date.now());
        console.log("after redirect");
      });
      break;
    case 1: //password error
      console.log("password error");
      res.render('login', {msg:"密码错误"});
      break;
    case 2: //user not found
      console.log("user not found");
      res.render('login', {msg:"用户名不存在"});
      break;
    }
  });
});

router.get('/login', function(req, res, next){
  console.log("cookies:");
  console.log(req.cookies);
  if(req.cookies["account"] != null){
    var account = req.cookies["account"];
    var user = account.account;
    var hash = account.hash;
    authenticate(user, hash, function(ret){
      if(ret == 0) res.redirect('/profile?'+Date.now());
      else res.render('login');
    });
  }else{
    res.render('login');
  }
});

router.get('/logout', function(req, res, next){
  res.clearCookie("account");
  res.redirect('/login?'+Date.now());
});

router.get('/profile', function(req, res, next){
  res.render('profile',{
    msg:"您登录为:"+req.cookies["account"].account, 
    title:"登录成功",
    lastTime:"上次登录:"+req.cookies["account"].last
  });
});

module.exports = router;

代码量和原来差不多,但逻辑复杂了一些。主要是mongoose查询数据库都是异步的,原来我们把账号内置在内存里,查询时是同步的。从同步转到异步,代码发生了翻天覆地的变化,如果你细看一下,会发现,哇哦,到处都是callback和callback的嵌套啊。幸好我是C出身,不然真被搞死了。

为了与Mongoose的异步方式配合,users.js几乎全部重写了。我们以authenticate方法为例讲一下。先看原来的authenticate:

function authenticate(userName, hash){

  for(var i = 0; i < userdb.length; ++i){
    var user = userdb[i];
    if(userName === user.userName){
      if(hash === user.hash){
          return 0;
      }else{
          return 1;
      }
    }
  }

  return 2;
}

这是典型的同步方式,简单直接,遍历数组比较。再看新的authenticate:

function authenticate(userName, hash, callback){
  if(!User){ callback(2); return;}
  var query = User.findOne().where('name', userName);
  query.exec(function(err, doc){
    if(err || !doc){ console.log("get user error: " + err); callback(2); return}
    if(doc.hash === hash) callback(0);
    else callback(1);
  });
}

这是异步的方式了。Node.js是事件驱动的,是一个主线程+多个工作者线程(线程池)的模型,耗时的操作都会投递到线程池里来执行,执行完毕再通过事件通知主线程,主线程处理事件,在适当的时候调用回调。Mongoose处理数据库,也是这种逻辑。

在authenticate里,我使用Model.findOne().where构造了一个Query对象,然后调用Query的exec方法来做查询,而exec提交的查询数据库动作,实际上会在线程池里完成。查询结束后,事件通知主线程,回调我们提供的函数。

现在这种写法,当authenticate()被调用时,很快就返回了,但是查询却被投递到线程池去执行,调用方期望的结果并没有立即到来,依赖调用结果展开的逻辑必须被延宕到回调发生。因此调用方必须改造代码,将部分逻辑放到回调函数里,把回调函数提供给新的authenticate方法。

可以参看requireAuthentication方法的代码,对照下面的同步版本来体会其间异同。

router.requireAuthentication = function(req, res, next){
  if(req.path == "/login"){
    next();
    return;
  }

  if(req.cookies["account"] != null){
    var account = req.cookies["account"];
    var user = account.account;
    var hash = account.hash;
    if(authenticate(user, hash)==0){
      console.log(req.cookies.account.account + " had logined.");
      next();
      return;
    }
  }
  console.log("not login, redirect to /login");
  res.redirect('/login?'+Date.now());
};

关于Mongoose操作数据库,CRUD之类的,给两个链接参考下:


其它文章:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

foruok

你可以选择打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值