用到的模塊或者技術:
Express: http://www.expressjs.com.cn/4x/api.html#express
Easyui: http://www.jeasyui.com/documentation/index.php#
express-session:https://www.npmjs.com/package/express-session#resave
node-mssql: http://csdoc.org/
Redis: http://redis.io/
art-Template:http://www.jq22.com/jquery-info1097(一種高性能js模板引擎)
connect-redis:https://www.npmjs.com/package/connect-redis(用來將服務端session同步至分布式redis緩存)
先來看下整體效果:

圖1 登錄頁面

圖2 用戶管理主界面

圖3 系統設置-密碼修改

圖4 系統設置-退出系統
1、項目結構

routes下user.js作為控制器負責請求分發處理,views下存放user文件夾存放user模塊全部視圖html資源,系統入口為login.html文件,進行登錄操作。
2、數據庫模塊
數據庫連接使用node-mssql實現sqlserver數據庫連接,對增刪改查做了簡單封裝,沒有統一整理顯得比較凌亂,詳細代碼如下:
var node_mssql = require('node-mssql'); var express = require('express'); /* add configuration to query object */ var queryObj = new node_mssql.Query({ host: 'localhost', port: 1433, username: 'sa', password: 'sa123456', db: 'mydatabase' }); var insert = function (data, insertTable,callback) { queryObj.table(insertTable); queryObj.data(data); queryObj.insert(function (results) { callback(results); }, function (err, sql) { if (err) { console.log(err); } }); }; var list = function (successCallback, option, listTable) { queryObj.table(listTable); queryObj.where(option); queryObj.select(function (results) { successCallback(results); }, function (err, sql) { if (err) console.log(err); }); }; var update = function (data, option, updateTable,successCallback) { queryObj.table(updateTable); queryObj.data(data); queryObj.where(option); queryObj.update(function (results) { successCallback(results); }, function (err, sql) { if (err) console.log(err); //console.log(sql); }); }; var del = function (ids, table,successCallback,failedCallback) { queryObj.query("delete from " + table + " where id in ( " + ids + " )",successCallback,failedCallback); }; var list_sql = function(sql,successCallback){ queryObj.query(sql, successCallback,function(err){ console.log(err); }); }; var total = function(sql,callback){ queryObj.query(sql,callback,function(err){ console.log(err); }); }; var findUserById = function(sql,successCallback,failedCallback){ queryObj.query(sql,successCallback,failedCallback); }; exports.list = list; exports.list_sql = list_sql; exports.insert = insert; exports.update = update; exports.del = del; exports.total = total; exports.findUserById = findUserById;
3、修改默認引擎
修改默認引擎為html而非ejs,修改操作如下,具體可查詢官網說明:
// view engine setup var template = require('art-template'); template.config('base', ''); template.config('extname', '.html'); app.engine('.html', template.__express); app.set('view engine', 'html'); app.set('views', __dirname + '/views');
4、connect-redis的使用
var express = require('express'); var session = require("express-session"); var cookieParser = require('cookie-parser'); var RedisStore = require("connect-redis")(session); //用於將session保存至redis中 var app = express(); app.use(session({ store: new RedisStore({ host: 'localhost', port: 6379 }), secret: '1234567890QWERTY' }));
在app.js中加入該設置后,在js代碼中通過req.session.key= value,可以直接保存key:value的數值對保存至session從而直接同步至redis中,方便分布式下實現session會話的統一管理不丟失。
5、登錄處理
router.post("/user/login", function (req, res) {
var username = req.param("username");
var password = req.param("password");
var obj = {"username": username, "password": password};
dbConn.list(function (results) {
if (results == "") {
res.render("login", {"err": "用戶名或密碼錯誤"});
} else {
dbConn.findUserById("select * from dbo.t_user where id = " + results[0].id, function(recordset){
req.session.user = recordset[0];
console.log(recordset[0]);
res.render("user/userMain",{"currentUser":recordset[0]});
},function(err,sql){
console.log(err);
});
}
},obj, "dbo.t_user");
});
判斷results是否為空,如果為空則表示用戶名或者密碼錯誤(前台已進行非空校驗),直接跳轉至login界面,顯示:用戶名或密碼錯誤;
如果results非空,一是將用戶信息存至session,二是將用戶信息render至跳轉頁面,這樣直接在頁面中可以顯示當前用戶登錄信息。
6、url控制攔截
app.use('/', user);
app.use(function (req, res, next) {
var url = req.originalUrl;
console.log("############" + url);
if (url != "/" && !req.session.user) {
return res.redirect("/");
}
next();
});
在業務攔截處理后面加入以上代碼片段進行url攔截,防止非法登錄訪問。如果請求的url非根目錄並且當且用戶沒有session(表示未登錄訪問),直接redirect至登錄頁面。
7、user.js請求處理模塊
/** * Created by Administrator on 2015/8/28. */ var express = require('express'); var dbConn = require("./dbConn.js"); var router = express.Router(); /* GET home page. */ //路由分發登錄請求 router.get('/', function (req, res, next) { res.render('login'); }); //登錄處理 router.post("/user/login", function (req, res) { var username = req.param("username"); var password = req.param("password"); var obj = {"username": username, "password": password}; dbConn.list(function (results) { if (results == "") { res.render("login", {"err": "用戶名或密碼錯誤"}); } else { dbConn.findUserById("select * from dbo.t_user where id = " + results[0].id, function(recordset){ req.session.user = recordset[0]; console.log(recordset[0]); res.render("user/userMain",{"currentUser":recordset[0]}); },function(err,sql){ console.log(err); }); } },obj, "dbo.t_user"); }); router.get("/user/userManage.html", function (req, res) { res.render("user/userManage", {"msg": "hello userManage"}); }); //查詢 router.post("/user/list", function (req, res) { console.dir(req.session.user); var count = 0; var page = req.param("page"); var rows = req.param("rows"); var username = req.body.username; if(typeof(username) == "undefined"){ username = ""; } console.log(page + ">" + rows + ">" + username); var options = {page:page,rows:rows,username:username}; var jsonArray,sql; if (options != "") { if (options.username != "") { //sql = "select top(" +rows+ ") * from dbo.t_user where username like '%"+options.username+"%' and id not in(select top(" + (page-1) * rows + ") id from dbo.t_user ) "; /*最后一頁查詢數據有誤,還需改進*/ sql = "select top(" +rows+ ") * from (select ROW_NUMBER() over( order by id) as row,* from dbo.t_user where username like '%"+options.username+"%' ) a where a.row > " + (page-1)*rows; console.log(sql); dbConn.list_sql(sql,function(recordset){ dbConn.list_sql("select * from dbo.t_user where username like '%" + options.username + "%'",function(result){ jsonArray = {rows:recordset,total:result.length}; res.json(jsonArray); }); }); } if (options.username == "") { sql = "select top(" +rows+ ") * from dbo.t_user where id not in(select top(" + (page-1) * rows + ") id from dbo.t_user )"; //sql = "select top(" +rows+ ") * from (select ROW_NUMBER() over( order by id) as row,* from dbo.t_user) a where a.row > " + (page-1)*rows; console.log(sql); dbConn.list_sql(sql,function(recordset){ dbConn.list_sql("select * from dbo.t_user where username like '%" + options.username + "%'",function(result){ jsonArray = {rows:recordset,total:result.length}; res.json(jsonArray); }); }); } } }); //添加或者修改 router.post("/user/add/:id(\\d+)",function(req,res){ var obj = req.body; dbConn.update(obj,{id:req.params.id},"dbo.t_user",function(results){ res.json({success:true}); }); }); router.post("/user/add",function(req,res){ var obj = req.body; dbConn.insert(obj,"dbo.t_user",function(results){ res.json({success:true}); }); }); //刪除 router.post("/user/delete",function(req,res){ var ids = req.param("ids"); console.log(ids); dbConn.del(ids,"dbo.t_user",function(recordset){ res.json({success:true}); },function(err,sql){ console.log(err); }); }); router.get("/user/exit",function(req,res){ //清除系統session req.session.destroy(function(err) { console.log(err); }); res.redirect("/"); }); router.post("/user/modifyPass",function(req,res){ var user = req.session.user; var oldPwd = req.body.oldPwd; dbConn.list(function(results){ if(results.length == 0){ res.json({success:false}); }else{ var id = user.id; var data = {password:req.body.rPwd}; //console.log(req.body); dbConn.update(data,{id:id},"dbo.t_user",function(results){ res.json({success:true}); }); } },{username:user.username,password:oldPwd},"dbo.t_user"); }); module.exports = router;
8、userMain.html用戶頁面主模塊
<!DOCTYPE html> <html lang="en"> <meta charset="UTF-8"> <title>Full Layout - jQuery EasyUI Demo</title> <link rel="stylesheet" type="text/css" href="/javascripts/jquery-easyui-1.4.3/themes/default/easyui.css"> <link rel="stylesheet" type="text/css" href="/javascripts/jquery-easyui-1.4.3/themes/icon.css"> <link rel="stylesheet" type="text/css" href="/javascripts/jquery-easyui-1.4.3/demo/demo.css"> <script type="text/javascript" src="/javascripts/jquery-easyui-1.4.3/jquery.min.js"></script> <script type="text/javascript" src="/javascripts/jquery-easyui-1.4.3/jquery.easyui.min.js"></script> <script type="text/javascript" src="/javascripts/jquery-easyui-1.4.3/locale/easyui-lang-zh_CN.js"></script> <script type="text/javascript" src="/javascripts/disableMove.js"></script> <script> var url; function openTab(text,url,iconCls){ if($("#tabs").tabs("exists",text)){ $("#tabs").tabs("select",text); }else{ var content = "<iframe frameborder=0 scrolling='auto' style='width:100%;height:100%;' src='/user/"+url+"'></iframe>"; $("#tabs").tabs("add",{ title:text, iconCls:iconCls, closable:true, content:content }); } } function openModifyPassDlg(){ $("#fm").form("reset"); $("#dlg").dialog("open").dialog("setTitle","修改密碼"); } function savePass(){ //save處理 $("#fm").form("submit",{ url: "/user/modifyPass", onSubmit:function(){ if($("#oldPwd").val() == ""){ $.messager.alert("系統提示","請輸入您的原密碼"); return false; } if($("#newPwd").val() == ""){ $.messager.alert("系統提示","請輸入新密碼"); return false; } if($("#rPwd").val() == ""){ $.messager.alert("系統提示","請再次確認密碼"); return false; } if($("#newPwd").val() != $("#rPwd").val()){ $.messager.alert("系統提示","兩次輸入密碼不匹配,請重新輸入"); return false; } return $(this).form("validate"); }, success:function(result){ var result = eval('('+result+')'); if(result.success){ $.messager.alert("系統提示","密碼修改成功,下次重啟生效"); $("#dlg").dialog("close"); $("#fm").form("reset"); }else{ $.messager.alert("系統提示","密碼修改失敗"); return; } } }); } function userExit(){ $.messager.confirm('確認退出','您確定退出系統?',function(r){ if (r){ window.location.href = "/user/exit"; //跳轉 } return false; }); } function closePassModifyDialog(){ $("#dlg").dialog("close"); $("#fm").form("reset"); } </script> </head> <body class="easyui-layout"> <div region="north" style="height:78px;background-color: #E0ECFF;"> <table style="padding:5px;width:100%;"> <tr> <td> </td> </tr> <tr> <td valign="bottom" align="right" width="80%"> <strong>歡迎 {{currentUser.username}}</strong> </td> </tr> </table> </div> <div region="center"> <div class="easyui-tabs" fit="true" id="tabs"> <div title="首頁" data-options="iconCls:'icon-home'"> <iframe frameborder=0 scrolling='auto' style='width:100%;height:100%;' src='http://www.cnblogs.com/caiya928/'></iframe> </div> </div> </div> <div region="west" style="width:200px;" title="導航菜單" split="true"> <div class="easyui-accordion" data-options="fit:true,border:false"> <div title="用戶管理" data-options="iconCls:'icon-user',selected:true" style="padding:5px;"> <a href="javascript:openTab('用戶管理','userManage.html','icon-user_comment')" class="easyui-linkbutton" data-options="plain:true,iconCls:'icon-user_comment'">用戶管理</a> </div> <div title="系統設置" data-options="iconCls:'icon-wrench',selected:true" style="padding:5px;"> <a href="javascript:openModifyPassDlg()" class="easyui-linkbutton" data-options="plain:true,iconCls:'icon-vcard_key'">密碼修改</a></br> <a href="javascript:userExit()" class="easyui-linkbutton" data-options="plain:true,iconCls:'icon-2012080412301'">退出系統</a> </div> </div> </div> <div region="south" style="height:25px;padding:5px;" align="center"> @copyright 博客園 <a href="http://www.cnblogs.com/caiya928/">http://www.cnblogs.com/caiya928/</a> </div> <div id="dlg" class="easyui-dialog" style="width: 400px;height:300px;padding: 10px 20px" closed="true" buttons="#dlg-buttons"> <form id="fm" method="post"> <table cellspacing="10px"> <tr> <td>用戶名:</td> <td><input type="text" id="username" name="username" value="{{currentUser.username}}" class="easyui-validatebox" readonly="readonly" style="width: 230px"/></td> </tr> <tr> <td>當前密碼:</td> <td><input type="password" id="oldPwd" name="oldPwd" class="easyui-validatebox" required="true" style="width: 100%;"/></td> </tr> <tr> <td>新密碼:</td> <td><input type="password" id="newPwd" name="newPwd" class="easyui-validatebox" required="true" style="width: 100%;"/></td> </tr> <tr> <td>確認密碼:</td> <td> <input id="rPwd" name="rPwd" type="password" class="easyui-validatebox" data-options="" required="required" style="width: 100%"> </td> </tr> </table> </form> </div> <div id="dlg-buttons"> <a href="javascript:savePass()" class="easyui-linkbutton" iconCls="icon-ok">保存</a> <a href="javascript:closePassModifyDialog()" class="easyui-linkbutton" iconCls="icon-cancel">關閉</a> </div> </body> </html>
9、userManage.html用戶管理模塊詳細
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="stylesheet" type="text/css" href="/javascripts/jquery-easyui-1.4.3/themes/default/easyui.css"> <link rel="stylesheet" type="text/css" href="/javascripts/jquery-easyui-1.4.3/themes/icon.css"> <link rel="stylesheet" type="text/css" href="/javascripts/jquery-easyui-1.4.3/demo/demo.css"> <script type="text/javascript" src="/javascripts/jquery-easyui-1.4.3/jquery.min.js"></script> <script type="text/javascript" src="/javascripts/jquery-easyui-1.4.3/jquery.easyui.min.js"></script> <script type="text/javascript" src="/javascripts/jquery-easyui-1.4.3/locale/easyui-lang-zh_CN.js"></script> <script type="text/javascript" src="/javascripts/disableMove.js"></script> <script type="text/javascript"> var url; function saveUser(){ $('#fm').form('submit', { url:url, onSubmit:function(){ if($("#username").val() == ""){ $.messager.alert("系統提示","請填寫用戶名"); return false; } if($("#password").val() == ""){ $.messager.alert("系統提示","請填寫密碼"); return false; } if($("#sex").combobox("getValue") == ""){ $.messager.alert("系統提示","請選擇性別"); return false; } if($("#birthday").datebox("getValue") == ""){ $.messager.alert("系統提示","請選擇出生日期"); return false; } if($("#email").val() == ""){ $.messager.alert("系統提示","請輸入您的郵箱"); return false; } if($("#address").val() == ""){ $.messager.alert("系統提示","請輸入您的地址"); return false; } return $(this).form("validate"); }, success: function(result){ var result = eval('('+result+')'); if(result.success){ $.messager.alert("系統提示","保存成功"); $("#fm").form("reset"); $("#dlg").dialog("close"); $("#dg").datagrid("reload"); }else{ $.messager.alert("系統提示","保存失敗"); return; } } }); } function openUserAddDialog(){ $("#fm").form("reset"); //打開之前先清空數據 $("#dlg").dialog("open").dialog("setTitle","添加用戶"); url = "/user/add"; } function searchUser(){ $("#dg").datagrid("load",{ "username":$("#s_userName").val() }); } function deleteUser(){ var selectedRows=$("#dg").datagrid('getSelections'); if(selectedRows.length==0){ $.messager.alert("系統提示","請選擇要刪除的數據!"); return; } var strIds=[]; for(var i=0;i<selectedRows.length;i++){ strIds.push(selectedRows[i].id); } var ids=strIds.join(","); $.messager.confirm("系統提示","您確認要刪除這<font color=red>"+selectedRows.length+"</font>條數據嗎?",function(r){ if(r){ $.post("/user/delete",{ids:ids},function(result){ //result直接返回Object,所以無需轉換為json if(result.success){ $.messager.alert("系統提示","數據已成功刪除!"); $("#dg").datagrid("reload"); }else{ $.messager.alert("系統提示","數據刪除失敗!"); } },"json"); } }); } function openUserModifyDialog(){ var selectedRows=$("#dg").datagrid('getSelections'); if(selectedRows.length != 1){ $.messager.alert("系統提示","請選擇一條數據進行修改"); return; } var row = selectedRows[0]; $("#dlg").dialog("open").dialog("setTitle","修改用戶信息"); dispValue(row); url = "/user/add/"+row.id; } function dispValue(row){ $("#username").val(row.username); $("#password").val(row.password); $("#sex").combobox("setValue",row.sex); $("#birthday").datebox("setValue",row.birthday); $("#email").val(row.email); $("#address").val(row.address); } function closeUserDialog(){ $("#dlg").dialog("close"); $("#fm").form("reset"); } function formatEmail(value,rowData,rowIndex){ return value.substr(0,10); } </script> <title>用戶管理</title> </head> <body style="margin: 1px"> <table id="dg" title="用戶管理" class="easyui-datagrid" fitColumns="true" pagination="true" rownumbers="true" url="/user/list" fit="true" toolbar="#tb"> <thead> <tr> <th field="cb" checkbox="true" align="center"></th> <th field="id" width="50" align="center">編號</th> <th field="username" width="100" align="center">用戶名</th> <th field="sex" width="50" align="center">性別</th> <th field="birthday" width="100" align="center" formatter="formatEmail">出生日期</th> <th field="email" width="100" align="center">郵箱</th> <th field="address" width="100" align="center">收貨地址</th> </tr> </thead> </table> <div id="tb"> <div> <a href="javascript:openUserAddDialog()" class="easyui-linkbutton" data-options="plain:true,iconCls:'icon-user_add'">添加用戶</a> <a href="javascript:openUserModifyDialog()" class="easyui-linkbutton" data-options="plain:true,iconCls:'icon-user_edit'">修改用戶</a> <a href="javascript:deleteUser()" class="easyui-linkbutton" data-options="plain:true,iconCls:'icon-user_delete'">刪除用戶</a> </div> <div> 用戶名:<input type="text" id="s_userName" size="20" onkeydown="if(event.keyCode == 13) searchUser();"> <a href="javascript:searchUser()" class="easyui-linkbutton" data-options="plain:true,iconCls:'icon-search'">查詢</a> </div> </div> <div class="easyui-dialog" id="dlg" style="width:480px;height:330px;padding:10px 10px;" closed="true" buttons="#dlg-buttons" data-options="" > <form action="" method="post" id="fm"> <table cellspacing="8px" align="center"> <tr> <td>用戶名:</td> <td><input type="text" id="username" name="username" class="easyui-validatebox" required="true" style="width: 300px;"/></td> <td> </td> </tr> <tr> <td>密 碼:</td> <td><input type="password" id="password" name="password" class="easyui-validatebox" required="true"/></td> <td> </td> </tr> <tr> <td>性 別:</td> <td colspan="2"> <select class="easyui-combobox" id="sex" name="sex" style="width:30%" editable="false" panelHeight="auto"> <option value="">請選擇性別</option> <option value="男">男</option> <option value="女">女</option> </select> </td> </tr> <tr> <td>出生日期:</td> <td><input type="text" id="birthday" name="birthday" class="easyui-datebox" editable="false" required="true"/></td> <td> </td> </tr> <tr> <td>郵 箱:</td> <td><input type="text" id="email" name="email" class="easyui-validatebox" required="true" data-options="validType:'email'" style="width: 300px;"/></td> <td> </td> </tr> <tr> <td>收貨地址:</td> <td> <input type="text" valign="top" id="address" name="address" class="easyui-validatebox" required="true" style="width: 306px;"/> </td> </tr> </table> </form> </div> <div id="dlg-buttons"> <a href="javascript:saveUser();" class="easyui-linkbutton" iconCls="icon-ok">保存</a> <a href="javascript:closeUserDialog();" class="easyui-linkbutton" iconCls="icon-cancel">取消</a> </div> </body> </html>
10、處理easyui中dialog、window、panel等窗體拖動時超出父窗體不能拖回的問題
/** * Created by Administrator on 2015/8/31. */ var easyuiPanelOnMove = function(left, top) { var parentObj = $(this).panel('panel').parent(); if (left < 0) { $(this).window('move', { left : 1 }); } if (top < 0) { $(this).window('move', { top : 1 }); } var width = $(this).panel('options').width; var height = $(this).panel('options').height; var right = left + width; var buttom = top + height; var parentWidth = parentObj.width(); var parentHeight = parentObj.height(); if(parentObj.css("overflow")=="hidden"){ if(left > parentWidth-width){ $(this).window('move', { "left":parentWidth-width }); } if(top > parentHeight-height){ $(this).window('move', { "top":parentHeight-height }); } } }; $.fn.panel.defaults.onMove = easyuiPanelOnMove; $.fn.window.defaults.onMove = easyuiPanelOnMove; $.fn.dialog.defaults.onMove = easyuiPanelOnMove;
將以上代碼保存為任意js文件,在需要處理的頁面中直接引入即可。
詳細代碼設計已完畢,改進的地方還有很多,比如:
1、數據庫操作代碼凌亂,不是很方便后期維護
2、沒有加入登錄時的驗證碼輸入操作,具體可以參考這篇博客https://cnodejs.org/topic/50f90d8edf9e9fcc58a5ee0b
