標籤:node-js mongodb mongoose angular express
這次的樣本基於之前的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之類的,給兩個連結參考下:
- https://cnodejs.org/topic/51ff720b44e76d216afe34d9
- http://www.cnblogs.com/aaronjs/p/4489354.html
其它文章:
- Node.js開發入門——MongoDB與Mongoose
- Node.js開發入門——使用cookie保持登入
- Node.js開發入門——使用AngularJS內建服務
- Node.js開發入門——Angular簡單樣本
- Node.js開發入門——使用AngularJS
- Node.js開發入門——使用jade模板引擎
- Node.js開發入門——Express裡的路由和中介軟體
- Node.js開發入門——Express安裝與使用
- Node.js開發入門——HTTP檔案伺服器
- Node.js開發入門——HelloWorld再分析
- Node.js開發入門——環境搭建與HelloWorld
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
Node.js入門—用MongoDB改造LoginDemo