0
这些天有点儿空闲,学习了一下node.js。先把《Node.js开发指南》书中的博客例子调通了,费了挺大的劲儿,因为好多插件出了新的版本,而新版本中有的函数、方法又失效了。 
  自已又练习着做了一个例子,个人记账系统。主要是想在手机上用,所以界面做得很简单。 
  解决了以下一些问题:express使用、bootstrap排版布局、mongodb模糊查询、mongodb统计(group/mapReduce)、session处理、req.flash方法使用等、路由设置等。花了不少的心思。 
  例子中用到的插件的版本: 
  connect-flash@0.1.1 
  connect-mongo@0.4.0 
  ejs@0.8.5 
  express@3.4.8 
  mongodb@1.3.23 

  Bootstrap V3.0.3 

  贴几张系统运行的图片,有图有真相嘛。 

 

 

 

 

 



  核心代码贴一下: 
一、app.js 
Javascript代码  收藏代码
  1. /** 
  2.  * Module dependencies. 
  3.  */  
  4.   
  5. var express = require('express');  
  6. var http = require('http');  
  7. var path = require('path');  
  8. var util=require('util');  
  9.   
  10. var routes = require('./routes');  
  11. //var User = require('./modules/user.js');    
  12. var settings=require('./Settings');  
  13.   
  14. var MongoStore = require('connect-mongo')(express);  
  15. var flash = require('connect-flash');  
  16.   
  17. var app = express();  
  18.   
  19. //app.set('appTitle','老王个人记账系统');  
  20. app.locals.gAppTitle = settings.appName;  
  21. //这个没有使用  
  22. app.locals.gPageSize = settings.pageSize;  
  23. // all environments  
  24. app.set('port', process.env.PORT || 8484);  
  25. app.set('views', path.join(__dirname, 'views'));  
  26. app.set('view engine''ejs');  
  27. app.use(express.favicon());  
  28. app.use(express.logger('dev'));  
  29. app.use(express.json());  
  30. app.use(express.bodyParser());  
  31. app.use(express.urlencoded());  
  32. app.use(express.methodOverride());  
  33. app.use(express.cookieParser());  
  34. app.use(flash());  
  35. app.use(express.session({  
  36.     "secret":settings.cookieSecret,  
  37.     "store":new MongoStore({  
  38.         db:settings.db  
  39.     })  
  40. }));  
  41.   
  42. app.use(function(req, res, next){  
  43.   //跟踪;  
  44.   //console.log("req.method="+req.method);  
  45.   //console.log("req.url="+req.url);  
  46.   //console.log("req.originalUrl="+req.originalUrl);  
  47.   var url = req.originalUrl;  
  48.   //简单地定义一个登录拦截器  
  49.   if ((url == "/month" || url=="/stat" || url=='/list' || url=='/record') && !req.session.user) {  
  50.       console.log("登录拦截器提示:必须登录,才能执行此项操作。");  
  51.       req.flash('error''请先登录。');  
  52.       return res.redirect("/login");  
  53.   }  
  54.     
  55.   res.locals.user = req.session.user;  
  56.     
  57.   var error = req.flash('error');  
  58.   res.locals.error = error.length?error:null;  
  59.   //console.log("转移flash中的error值:"+error);  
  60.     
  61.   var success = req.flash('success');  
  62.   res.locals.success = success.length?success:null;  
  63.   //console.log("转移flash中的success值:"+success);  
  64.     
  65.   res.locals.session = req.session;  
  66.   next();  
  67. });  
  68.   
  69. app.use(app.router);  
  70. app.use(express.static(path.join(__dirname, 'public')));  
  71. //console.log(util.inspect(app));  
  72.   
  73. // development only  
  74. if ('development' == app.get('env')) {  
  75.   app.use(express.errorHandler());  
  76. }  
  77.   
  78. //console.log('注册路由.');  
  79.   
  80. routes(app);  
  81.   
  82.   
  83. http.createServer(app).listen(app.get('port'), function(){  
  84.     console.log();  
  85.     console.log();  
  86.     console.log('/**************************************************/');  
  87.     console.log('/*  我的第一个NODE.JS例子。BY 隔壁老王 2014-3-29  */');  
  88.     console.log('/*   欢迎访问我的博客:http://wallimn.iteye.com   */');  
  89.     console.log('/**************************************************/');  
  90.     console.log('============服务启动成功,监听端口:' + app.get('port')+"============");  
  91. });  


二、路由处理(routes\index.js) 
Javascript代码  收藏代码
  1. var crypto = require('crypto');  
  2. var User = require('../modules/user.js');     
  3. var Consume = require('../modules/consume.js');  
  4. /* 
  5.  * GET home page. 
  6.  */  
  7.   
  8. module.exports = function(app) {  
  9.     app.get('/',function(req, res){  
  10.       res.render('index', { title: '首页' });  
  11.       /* 
  12.         res.render('login',{ 
  13.             title:'用户登录', 
  14.         }); 
  15.       */  
  16.       //res.redirect('/login');  
  17.     });  
  18.   
  19.       
  20.     app.get('/record',function(req,res){  
  21.         var user = req.session.user;  
  22.         if(!user){  
  23.             req.flash('error''用户未登录,请登录。');  
  24.             return res.redirect('/login');  
  25.         }  
  26.   
  27.         Consume.get(user.name,{limit:16}, function(err, records) {  
  28.             if (err) {  
  29.                 req.flash('error', err);  
  30.                 return res.redirect('/error');  
  31.             }  
  32.             res.render('record', {  
  33.                 title: user.name,  
  34.                 consumes: records,  
  35.             });  
  36.         });  
  37.     });  
  38.       
  39.     //预处理,如果通过,再进行下一个。  
  40.     app.post('/record',checkLogin);  
  41.   
  42.     app.post('/record'function(req, res) {  
  43.         var currentUser = req.session.user;  
  44.         var record = new Consume();  
  45.         record.loadFromReq(currentUser.name, req.body);  
  46.         record.save(function(err) {  
  47.             if (err) {  
  48.                 req.flash('error', err);  
  49.                 return res.redirect('/');  
  50.             }  
  51.             req.flash('success''发表成功');  
  52.             res.redirect('/record');  
  53.         });  
  54.     });  
  55.   
  56.     //这个应该去掉。留在这里当个后门吧  
  57.     //可以查看其他用户的数据  
  58.     app.get('/u/:user'function(req, res) {  
  59.         var username = req.params.user;  
  60.         if (!username) {  
  61.             req.flash('error''未指定用户');  
  62.             return res.redirect('/error');  
  63.         }  
  64.   
  65.         Consume.get(username,{limit:0}, function(err, records) {  
  66.             if (err) {  
  67.                 req.flash('error', err);  
  68.                 return res.redirect('/error');  
  69.             }  
  70.             console.log(records);  
  71.             res.render('list', {  
  72.                 title: username,  
  73.                 consumes: records,  
  74.             });  
  75.         });  
  76.     });  
  77.       
  78.     app.get('/del/:id'function(req, res) {  
  79.         var id = req.params.id;  
  80.         if (!id) {  
  81.             req.flash('error''未指定要删除的记录ID');  
  82.             return res.redirect('/error');  
  83.         }  
  84.           
  85.         console.log("准备删除记账记录,_id="+id);  
  86.         Consume.del(id, function(err, records) {  
  87.             if (err) {  
  88.                 req.flash('error', err);  
  89.                 return res.redirect('/error');  
  90.             }  
  91.             res.redirect('/record');;  
  92.         });  
  93.     });  
  94.       
  95.     app.get('/logout',function(req,res){  
  96.         req.session.user = null;  
  97.         req.flash('success','登出成功');  
  98.         res.redirect('/login');  
  99.     });  
  100.       
  101.     app.get('/list',function(req,res){  
  102.         var user = req.session.user;  
  103.         if(!user){  
  104.                 req.flash('error'"您没有登录,请登录。");  
  105.                 console.log("没有登录,重定向的登录界面。");  
  106.                 return res.redirect('/login');  
  107.         }  
  108.         Consume.get(user.name,{limit:0}, function(err, records) {  
  109.             if (err) {  
  110.                 req.flash('error', err);  
  111.                 return res.redirect('/error');  
  112.             }  
  113.             res.render('list', {  
  114.                 title: user.name,  
  115.                 consumes: records,  
  116.             });  
  117.         });  
  118.     });  
  119.       
  120.     app.post('/search',function(req,res){  
  121.         var user = req.session.user;  
  122.         if(!user){  
  123.                 req.flash('error'"您没有登录,请登录。");  
  124.                 return res.redirect('/login');  
  125.         }  
  126.         var keyword = req.body.keyword;  
  127.         console.log("搜索关键字:"+keyword);  
  128.         Consume.get(user.name,{limit:0,keyword:keyword}, function(err, records) {  
  129.             if (err) {  
  130.                 req.flash('error', err);  
  131.                 return res.redirect('/error');  
  132.             }  
  133.             res.render('record', {  
  134.                 title: user.name,  
  135.                 consumes: records,  
  136.             });  
  137.         });  
  138.     });  
  139.       
  140.     app.get('/stat',function(req,res){  
  141.         var user = req.session.user;  
  142.         Consume.stat(user.name, function(err, results) {  
  143.             if (err) {  
  144.                 req.flash('error', err);  
  145.                 return res.redirect('/error');  
  146.             }  
  147.             res.render('stat', {  
  148.                 title: user.name,  
  149.                 results: results,  
  150.             });  
  151.         });  
  152.     });  
  153.       
  154.     app.get('/month',function(req,res){  
  155.         var user = req.session.user;  
  156.         Consume.month(user.name, function(err, results) {  
  157.             if (err) {  
  158.                 req.flash('error', err);  
  159.                 return res.redirect('/error');  
  160.             }  
  161.             res.render('stat', {  
  162.                 title: user.name,  
  163.                 results: results,  
  164.             });  
  165.         });  
  166.     });  
  167.       
  168.     app.get('/error',function(req,res){  
  169.         res.render('error');  
  170.     });  
  171.   
  172.     //处理用户登录。  
  173.     app.post('/login',function(req,res){  
  174.         var md5=crypto.createHash('md5');  
  175.         var password = md5.update(req.body.password).digest('hex');  
  176.           
  177.         User.get(req.body.username,function(err,user){  
  178.             if(!user){  
  179.                 req.flash('error','用户不存在');  
  180.                 return res.redirect('/login');  
  181.             }  
  182.               
  183.             if(user.password!=password){  
  184.                 req.flash('error','用户口令错误');  
  185.                 return res.redirect('/login');  
  186.             }  
  187.               
  188.             req.session.user = user;  
  189.             req.flash('success','登入成功');  
  190.             //res.redirect('/');  
  191.             res.redirect('/record');  
  192.         });  
  193.     });  
  194.   
  195.     app.get('/login',function(req,res){  
  196.         res.render('login',{  
  197.             title:'用户登录',  
  198.         });  
  199.     });  
  200.   
  201.     app.get('/reg',function(req,res){  
  202.           
  203.         res.render('reg',{  
  204.             title:'用户注册'  
  205.         });  
  206.     });  
  207.   
  208.     app.post('/reg',function(req,res){  
  209.         if(req.body['password-repeat']!=req.body['password']){  
  210.             req.flash('error','两次输入的口令不一致!');  
  211.             return res.redirect('/reg');  
  212.         }  
  213.           
  214.         var md5=crypto.createHash('md5');  
  215.         var password = md5.update(req.body.password).digest('hex');  
  216.           
  217.         var newUser = new User({  
  218.             name:req.body.username,  
  219.             password:password,  
  220.         });  
  221.           
  222.         User.get(newUser.name,function(err,user){  
  223.             if(user)  
  224.                 err='同名用户已经存在,请更换名字.';  
  225.             if(err){  
  226.                 req.flash('error',err);  
  227.                 return res.redirect('/reg');  
  228.             }  
  229.               
  230.             newUser.save(function(err){  
  231.                 if(err){  
  232.                     req.error=err;  
  233.                     return res.redirect('/reg');  
  234.                 }     
  235.                 req.session.user = newUser;  
  236.                 req.flash('success','注册成功!');  
  237.                 res.redirect('/record');  
  238.             });  
  239.         });  
  240.     });  
  241.   
  242.     //测试函数  
  243.     app.get('/hello',function(req,res){  
  244.         res.send('The time is '+new Date().toString());  
  245.     });  
  246.   
  247.     //测试函数  
  248.     app.get('/sayhello',function(req,res){  
  249.         res.send('hello '+req.params.username);  
  250.     });  
  251.       
  252. };  
  253.   
  254. //检查是否登入.  
  255. function checkLogin(req, res, next) {  
  256.     if (!req.session.user) {  
  257.         req.flash('error''尚未登录,无法操作。');  
  258.         return res.redirect('/error');  
  259.     }  
  260.     next();  
  261. }  
  262.   
  263. function checkNotLogin(req, res, next) {  
  264.     if (req.session.user) {  
  265.         req.flash('error''已登入');  
  266.         return res.redirect('/');  
  267.     }  
  268.     next();  
  269. }  


三、消费数据处理(modules\consume.js) 
Javascript代码  收藏代码
  1. var mongodb = require('./db');  
  2. var BSON = require('mongodb').BSONPure;  
  3. var util=require('util');  
  4.   
  5.   
  6. function toObjectId(id){  
  7.     console.log("转换值:"+id);  
  8.     if( id=="" || id=="null" || id=="undefined" || id==undefined || id==null)return null;  
  9.     return BSON.ObjectID.createFromHexString(id);  
  10. }  
  11.   
  12. function Consume(username, consumeDate,consumeSubject,consumeAmount,consumeRemark, time)  
  13. {  
  14.     //加载时要单独赋值  
  15.     this._id=null;  
  16.       
  17.     this.userName = username;  
  18.     this.consumeDate = consumeDate;  
  19.     this.consumeSubject = consumeSubject;  
  20.     this.consumeAmount = consumeAmount;  
  21.     this.consumeRemark = consumeRemark;  
  22.   
  23.     if (time)  
  24.     {  
  25.         this.time = time;  
  26.     }  
  27.     else  
  28.     {  
  29.         this.time = new Date();  
  30.     }  
  31.       
  32. };  
  33. module.exports = Consume;  
  34.   
  35. Consume.prototype.loadFromReq = function loadFromReq(username,reqBody,time){  
  36.     //自动进行了ID类型的转换。  
  37.     this._id = toObjectId(reqBody._id);  
  38.     this.userName = username;  
  39.     this.consumeDate = reqBody.consumeDate;  
  40.     this.consumeSubject = reqBody.consumeSubject;  
  41.     this.consumeAmount = reqBody.consumeAmount;  
  42.     this.consumeRemark = reqBody.consumeRemark;  
  43.   
  44.     if (time)  
  45.     {  
  46.         this.time = time;  
  47.     }  
  48.     else  
  49.     {  
  50.         this.time = new Date();  
  51.     }  
  52. }  
  53.   
  54. Consume.prototype.save = function save(callback)  
  55. {  
  56.     // 存入 Mongodb 的文档  
  57.     var record =   
  58.     {  
  59.         _id:this._id,  
  60.         userName: this.userName,   
  61.         consumeDate: this.consumeDate,   
  62.         consumeSubject: this.consumeSubject,   
  63.         consumeAmount: this.consumeAmount,   
  64.         consumeRemark: this.consumeRemark,   
  65.         time: this.time,   
  66.     };  
  67.     console.log('保存,记录日期:'+record.consumeDate);  
  68.     mongodb.open(function(err, db){  
  69.         if (err)  
  70.         {  
  71.             return callback(err);   
  72.         }  
  73.         // 读取 posts 集合  
  74.         db.collection('consume'function(err, collection){  
  75.             if (err)  
  76.             {  
  77.                 mongodb.close(); return callback(err);   
  78.             }  
  79.   
  80.             // 插入  
  81.             /* 
  82.             collection.insert(record, {safe: true} , function(err, post){ 
  83.                 mongodb.close(); callback(err, post);  
  84.             });  
  85.             */  
  86.              
  87.            console.log("插入或更新,判断依据_id="+record._id);  
  88.            if(record._id==null){  
  89.                delete record._id;  
  90.                console.log("删除_id,record._id="+record._id);  
  91.            }  
  92.            collection.update({_id:(record._id?record._id:'no-record')}, record, {upsert:true,multi:false} , function(err, post){  
  93.                 mongodb.close(); callback(err, post);   
  94.             });   
  95.         });   
  96.     });  
  97. };  
  98.   
  99. //删除方法  
  100. Consume.del = function del(id,callback){  
  101.     mongodb.open(function(err,db){  
  102.         if (err){  
  103.             return callback(err);   
  104.         }  
  105.           
  106.         var query = {_id:BSON.ObjectID.createFromHexString(id)};  
  107.           
  108.         db.collection('consume'function(err, collection){  
  109.             if (err){  
  110.                 mongodb.close();   
  111.                 return callback(err);   
  112.             }  
  113.   
  114.             collection.remove(query,{safe:true},function(err,result){  
  115.                 mongodb.close();  
  116.                 if (err){  
  117.                     return callback(err);   
  118.                 }  
  119.                 console.log("删除成功。");  
  120.                 callback(null);   
  121.             }) ;  
  122.         });      
  123.     });  
  124. };  
  125.   
  126.   
  127.   
  128. Consume.get = function get(username,options, callback)  
  129. {  
  130.     mongodb.open(function(err, db){  
  131.         if (err){  
  132.             return callback(err);   
  133.         }  
  134.         // 读取 posts 集合  
  135.         db.collection('consume'function(err, collection){  
  136.             if (err){  
  137.                 mongodb.close();  
  138.                 return callback(err);   
  139.             }  
  140.   
  141.             var query = {};  
  142.             if(options.keyword){  
  143.                 //var regx = new RegExp("/"+options.keyword+"/");  
  144.                 //注意,不用/  
  145.                 var regx = new RegExp(options.keyword);  
  146.                 //限制用户名,科目或者金额与输入关键相等  
  147.                 query={"$and":[{userName:username},  
  148.                                {"$or":[{consumeSubject:regx},  
  149.                                        {consumeDate:regx},  
  150.                                        {consumeAmount:options.keyword}  
  151.                                       ]  
  152.                                }]  
  153.                        };  
  154.             }  
  155.             else{  
  156.                 query.userName = username;   
  157.             }  
  158.             console.log("搜索条件:");  
  159.             console.log(query);  
  160.             if(!options.limit){  
  161.                 options.limit=0;  
  162.             }  
  163.             collection.find(query).sort({consumeDate:-1 }).limit(options.limit).toArray(function(err, docs){  
  164.                 mongodb.close();   
  165.                 if (err){  
  166.                     callback(err, null);   
  167.                 }  
  168.   
  169.                 var consumes = [];   
  170.                 docs.forEach(function(doc, index){  
  171.                     var record = new Consume(doc.userName, doc.consumeDate,doc.consumeSubject,doc.consumeAmount,doc.consumeRemark, doc.time);  
  172.                     record._id = doc._id;  
  173.                     consumes.push(record);   
  174.                 });   
  175.                 callback(null, consumes);   
  176.             });   
  177.         });   
  178.     });  
  179. };  
  180.   
  181.   
  182.   
  183. Consume.stat = function stat(username, callback)  
  184. {  
  185.     mongodb.open(function(err, db){  
  186.         if (err){  
  187.             return callback(err);   
  188.         }  
  189.         // 读取 posts 集合  
  190.         db.collection('consume'function(err, collection){  
  191.             if (err){  
  192.                 mongodb.close();  
  193.                 return callback(err);   
  194.             }  
  195.   
  196.             var reduce = function(obj,prev){  
  197.                     prev.amount += isNaN(obj.consumeAmount)?0:Number(obj.consumeAmount);  
  198.                     prev.count++;  
  199.             };  
  200.           
  201.             collection.group(  
  202.                 [ 'consumeSubject' ],  
  203.                 {userName:username},  
  204.                 {count:0,amount:0},  
  205.                 reduce,  
  206.                 function(err, result){  
  207.                     mongodb.close();   
  208.                     if (err){  
  209.                         callback(err, null);   
  210.                     }  
  211.                     else{  
  212.                         console.log(result[0]);  
  213.                         var amount = 0,count=0;  
  214.                         result.forEach(function (item,index){  
  215.                             amount += item.amount;  
  216.                             count += item.count;  
  217.                         });  
  218.                         result.push({consumeSubject:'【合计】',count:count,amount:amount});  
  219.                         callback(null, result);   
  220.                     }  
  221.             });  
  222.         });   
  223.     });  
  224. };  
  225.   
  226. Consume.month = function month(username, callback)  
  227. {  
  228.     mongodb.open(function(err, db){  
  229.         if (err){  
  230.             return callback(err);   
  231.         }  
  232.         // 读取 posts 集合  
  233.         db.collection('consume'function(err, collection){  
  234.             if (err){  
  235.                 mongodb.close();  
  236.                 return callback(err);   
  237.             }  
  238.               
  239.             var map = function(){  
  240.                 emit(this.consumeDate.substr(0,7),{amount:this.consumeAmount,count:1});  
  241.             };  
  242.               
  243.             var reduce = function(key,vals){  
  244.                 var val = 0,count=0;  
  245.                 for(var i=0; i<vals.length;i++){  
  246.                     val += isNaN(vals[i].amount)?0:Number(vals[i].amount);  
  247.                     count++;  
  248.                 }  
  249.                 return {amount:val,count:count};  
  250.             }  
  251.               
  252.             console.log("统计:"+username);  
  253.             collection.mapReduce(  
  254.                 map,  
  255.                 reduce,  
  256.                 {out: {replace : 'temp', readPreference : 'secondary', query:{userName:username}}},  
  257.                 function(err, collection){  
  258.                     if (err){  
  259.                         mongodb.close();   
  260.                         callback(err, null);   
  261.                     }  
  262.                     else{  
  263.                         //console.log(collection);  
  264.                         /* 
  265.                         var amount = 0,count=0; 
  266.                         result.forEach(function (item,index){ 
  267.                             amount += item.amount; 
  268.                             count += item.count; 
  269.                         }); 
  270.                         result.push({consumeSubject:'【合计】',count:count,amount:amount}); 
  271.                         */  
  272.                           
  273.                         var results = [];  
  274.                         collection.find().toArray(function(err,docs){  
  275.                             docs.forEach(function(item,idx){  
  276.                                 console.log(item);  
  277.                                 //为了共用结果页面,统一使用consumeSubject  
  278.                                 results.push({consumeSubject:item._id,amount:item.value.amount,count:item.value.count});  
  279.                             });  
  280.                             mongodb.close();  
  281.                             callback(null, results);   
  282.                         });  
  283.                     }  
  284.             });  
  285.         });   
  286.     });  
  287. };  


  全部源码见附件。 

  另,2014-04-02 
  系统开发好了之后,找了个服务器部署了一下,老婆用得不错,原来不太喜欢记账,现在记账很积极。我就又把系统完善了一下,增加了翻页、权限控制功能,将几个按钮修改成图标,布局更紧凑了一些,使用全局变量保存系统名称,方便修改。 
  • account.rar (297 KB)
  • 描述: 个人记账系统源码
  • 下载次数: 982
  • microblog.rar (250.8 KB)
  • 描述: 调试通过的书中例子
  • 下载次数: 518

转自:http://wallimn.iteye.com/blog/2036876
关闭 返回顶部
联系我们
Copyright © 2011. 聚财吧. All rights reserved.