自已又练习着做了一个例子,个人记账系统。主要是想在手机上用,所以界面做得很简单。
解决了以下一些问题:express使用、bootstrap排版布局、mongodb模糊查询、mongodb统计(group/mapReduce)、session处理、req.flash方法使用等、路由设置等。花了不少的心思。
例子中用到的插件的版本:
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
Bootstrap V3.0.3
贴几张系统运行的图片,有图有真相嘛。
核心代码贴一下:
一、app.js
/** * Module dependencies. */ var express = require('express'); var http = require('http'); var path = require('path'); var util=require('util'); var routes = require('./routes'); //var User = require('./modules/user.js'); var settings=require('./Settings'); var MongoStore = require('connect-mongo')(express); var flash = require('connect-flash'); var app = express(); //app.set('appTitle','老王个人记账系统'); app.locals.gAppTitle = settings.appName; //这个没有使用 app.locals.gPageSize = settings.pageSize; // all environments app.set('port', process.env.PORT || 8484); app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); app.use(express.favicon()); app.use(express.logger('dev')); app.use(express.json()); app.use(express.bodyParser()); app.use(express.urlencoded()); app.use(express.methodOverride()); app.use(express.cookieParser()); app.use(flash()); app.use(express.session({ "secret":settings.cookieSecret, "store":new MongoStore({ db:settings.db }) })); app.use(function(req, res, next){ //跟踪; //console.log("req.method="+req.method); //console.log("req.url="+req.url); //console.log("req.originalUrl="+req.originalUrl); var url = req.originalUrl; //简单地定义一个登录拦截器 if ((url == "/month" || url=="/stat" || url=='/list' || url=='/record') && !req.session.user) { console.log("登录拦截器提示:必须登录,才能执行此项操作。"); req.flash('error', '请先登录。'); return res.redirect("/login"); } res.locals.user = req.session.user; var error = req.flash('error'); res.locals.error = error.length?error:null; //console.log("转移flash中的error值:"+error); var success = req.flash('success'); res.locals.success = success.length?success:null; //console.log("转移flash中的success值:"+success); res.locals.session = req.session; next(); }); app.use(app.router); app.use(express.static(path.join(__dirname, 'public'))); //console.log(util.inspect(app)); // development only if ('development' == app.get('env')) { app.use(express.errorHandler()); } //console.log('注册路由.'); routes(app); http.createServer(app).listen(app.get('port'), function(){ console.log(); console.log(); console.log('/**************************************************/'); console.log('/* 我的第一个NODE.JS例子。BY 隔壁老王 2014-3-29 */'); console.log('/* 欢迎访问我的博客:http://wallimn.iteye.com */'); console.log('/**************************************************/'); console.log('============服务启动成功,监听端口:' + app.get('port')+"============"); });
二、路由处理(routes\index.js)
var crypto = require('crypto'); var User = require('../modules/user.js'); var Consume = require('../modules/consume.js'); /* * GET home page. */ module.exports = function(app) { app.get('/',function(req, res){ res.render('index', { title: '首页' }); /* res.render('login',{ title:'用户登录', }); */ //res.redirect('/login'); }); app.get('/record',function(req,res){ var user = req.session.user; if(!user){ req.flash('error', '用户未登录,请登录。'); return res.redirect('/login'); } Consume.get(user.name,{limit:16}, function(err, records) { if (err) { req.flash('error', err); return res.redirect('/error'); } res.render('record', { title: user.name, consumes: records, }); }); }); //预处理,如果通过,再进行下一个。 app.post('/record',checkLogin); app.post('/record', function(req, res) { var currentUser = req.session.user; var record = new Consume(); record.loadFromReq(currentUser.name, req.body); record.save(function(err) { if (err) { req.flash('error', err); return res.redirect('/'); } req.flash('success', '发表成功'); res.redirect('/record'); }); }); //这个应该去掉。留在这里当个后门吧 //可以查看其他用户的数据 app.get('/u/:user', function(req, res) { var username = req.params.user; if (!username) { req.flash('error', '未指定用户'); return res.redirect('/error'); } Consume.get(username,{limit:0}, function(err, records) { if (err) { req.flash('error', err); return res.redirect('/error'); } console.log(records); res.render('list', { title: username, consumes: records, }); }); }); app.get('/del/:id', function(req, res) { var id = req.params.id; if (!id) { req.flash('error', '未指定要删除的记录ID'); return res.redirect('/error'); } console.log("准备删除记账记录,_id="+id); Consume.del(id, function(err, records) { if (err) { req.flash('error', err); return res.redirect('/error'); } res.redirect('/record');; }); }); app.get('/logout',function(req,res){ req.session.user = null; req.flash('success','登出成功'); res.redirect('/login'); }); app.get('/list',function(req,res){ var user = req.session.user; if(!user){ req.flash('error', "您没有登录,请登录。"); console.log("没有登录,重定向的登录界面。"); return res.redirect('/login'); } Consume.get(user.name,{limit:0}, function(err, records) { if (err) { req.flash('error', err); return res.redirect('/error'); } res.render('list', { title: user.name, consumes: records, }); }); }); app.post('/search',function(req,res){ var user = req.session.user; if(!user){ req.flash('error', "您没有登录,请登录。"); return res.redirect('/login'); } var keyword = req.body.keyword; console.log("搜索关键字:"+keyword); Consume.get(user.name,{limit:0,keyword:keyword}, function(err, records) { if (err) { req.flash('error', err); return res.redirect('/error'); } res.render('record', { title: user.name, consumes: records, }); }); }); app.get('/stat',function(req,res){ var user = req.session.user; Consume.stat(user.name, function(err, results) { if (err) { req.flash('error', err); return res.redirect('/error'); } res.render('stat', { title: user.name, results: results, }); }); }); app.get('/month',function(req,res){ var user = req.session.user; Consume.month(user.name, function(err, results) { if (err) { req.flash('error', err); return res.redirect('/error'); } res.render('stat', { title: user.name, results: results, }); }); }); app.get('/error',function(req,res){ res.render('error'); }); //处理用户登录。 app.post('/login',function(req,res){ var md5=crypto.createHash('md5'); var password = md5.update(req.body.password).digest('hex'); User.get(req.body.username,function(err,user){ if(!user){ req.flash('error','用户不存在'); return res.redirect('/login'); } if(user.password!=password){ req.flash('error','用户口令错误'); return res.redirect('/login'); } req.session.user = user; req.flash('success','登入成功'); //res.redirect('/'); res.redirect('/record'); }); }); app.get('/login',function(req,res){ res.render('login',{ title:'用户登录', }); }); app.get('/reg',function(req,res){ res.render('reg',{ title:'用户注册' }); }); app.post('/reg',function(req,res){ if(req.body['password-repeat']!=req.body['password']){ req.flash('error','两次输入的口令不一致!'); return res.redirect('/reg'); } var md5=crypto.createHash('md5'); var password = md5.update(req.body.password).digest('hex'); var newUser = new User({ name:req.body.username, password:password, }); User.get(newUser.name,function(err,user){ if(user) err='同名用户已经存在,请更换名字.'; if(err){ req.flash('error',err); return res.redirect('/reg'); } newUser.save(function(err){ if(err){ req.error=err; return res.redirect('/reg'); } req.session.user = newUser; req.flash('success','注册成功!'); res.redirect('/record'); }); }); }); //测试函数 app.get('/hello',function(req,res){ res.send('The time is '+new Date().toString()); }); //测试函数 app.get('/sayhello',function(req,res){ res.send('hello '+req.params.username); }); }; //检查是否登入. function checkLogin(req, res, next) { if (!req.session.user) { req.flash('error', '尚未登录,无法操作。'); return res.redirect('/error'); } next(); } function checkNotLogin(req, res, next) { if (req.session.user) { req.flash('error', '已登入'); return res.redirect('/'); } next(); }
三、消费数据处理(modules\consume.js)
var mongodb = require('./db'); var BSON = require('mongodb').BSONPure; var util=require('util'); function toObjectId(id){ console.log("转换值:"+id); if( id=="" || id=="null" || id=="undefined" || id==undefined || id==null)return null; return BSON.ObjectID.createFromHexString(id); } function Consume(username, consumeDate,consumeSubject,consumeAmount,consumeRemark, time) { //加载时要单独赋值 this._id=null; this.userName = username; this.consumeDate = consumeDate; this.consumeSubject = consumeSubject; this.consumeAmount = consumeAmount; this.consumeRemark = consumeRemark; if (time) { this.time = time; } else { this.time = new Date(); } }; module.exports = Consume; Consume.prototype.loadFromReq = function loadFromReq(username,reqBody,time){ //自动进行了ID类型的转换。 this._id = toObjectId(reqBody._id); this.userName = username; this.consumeDate = reqBody.consumeDate; this.consumeSubject = reqBody.consumeSubject; this.consumeAmount = reqBody.consumeAmount; this.consumeRemark = reqBody.consumeRemark; if (time) { this.time = time; } else { this.time = new Date(); } } Consume.prototype.save = function save(callback) { // 存入 Mongodb 的文档 var record = { _id:this._id, userName: this.userName, consumeDate: this.consumeDate, consumeSubject: this.consumeSubject, consumeAmount: this.consumeAmount, consumeRemark: this.consumeRemark, time: this.time, }; console.log('保存,记录日期:'+record.consumeDate); mongodb.open(function(err, db){ if (err) { return callback(err); } // 读取 posts 集合 db.collection('consume', function(err, collection){ if (err) { mongodb.close(); return callback(err); } // 插入 /* collection.insert(record, {safe: true} , function(err, post){ mongodb.close(); callback(err, post); }); */ console.log("插入或更新,判断依据_id="+record._id); if(record._id==null){ delete record._id; console.log("删除_id,record._id="+record._id); } collection.update({_id:(record._id?record._id:'no-record')}, record, {upsert:true,multi:false} , function(err, post){ mongodb.close(); callback(err, post); }); }); }); }; //删除方法 Consume.del = function del(id,callback){ mongodb.open(function(err,db){ if (err){ return callback(err); } var query = {_id:BSON.ObjectID.createFromHexString(id)}; db.collection('consume', function(err, collection){ if (err){ mongodb.close(); return callback(err); } collection.remove(query,{safe:true},function(err,result){ mongodb.close(); if (err){ return callback(err); } console.log("删除成功。"); callback(null); }) ; }); }); }; Consume.get = function get(username,options, callback) { mongodb.open(function(err, db){ if (err){ return callback(err); } // 读取 posts 集合 db.collection('consume', function(err, collection){ if (err){ mongodb.close(); return callback(err); } var query = {}; if(options.keyword){ //var regx = new RegExp("/"+options.keyword+"/"); //注意,不用/ var regx = new RegExp(options.keyword); //限制用户名,科目或者金额与输入关键相等 query={"$and":[{userName:username}, {"$or":[{consumeSubject:regx}, {consumeDate:regx}, {consumeAmount:options.keyword} ] }] }; } else{ query.userName = username; } console.log("搜索条件:"); console.log(query); if(!options.limit){ options.limit=0; } collection.find(query).sort({consumeDate:-1 }).limit(options.limit).toArray(function(err, docs){ mongodb.close(); if (err){ callback(err, null); } var consumes = []; docs.forEach(function(doc, index){ var record = new Consume(doc.userName, doc.consumeDate,doc.consumeSubject,doc.consumeAmount,doc.consumeRemark, doc.time); record._id = doc._id; consumes.push(record); }); callback(null, consumes); }); }); }); }; Consume.stat = function stat(username, callback) { mongodb.open(function(err, db){ if (err){ return callback(err); } // 读取 posts 集合 db.collection('consume', function(err, collection){ if (err){ mongodb.close(); return callback(err); } var reduce = function(obj,prev){ prev.amount += isNaN(obj.consumeAmount)?0:Number(obj.consumeAmount); prev.count++; }; collection.group( [ 'consumeSubject' ], {userName:username}, {count:0,amount:0}, reduce, function(err, result){ mongodb.close(); if (err){ callback(err, null); } else{ console.log(result[0]); var amount = 0,count=0; result.forEach(function (item,index){ amount += item.amount; count += item.count; }); result.push({consumeSubject:'【合计】',count:count,amount:amount}); callback(null, result); } }); }); }); }; Consume.month = function month(username, callback) { mongodb.open(function(err, db){ if (err){ return callback(err); } // 读取 posts 集合 db.collection('consume', function(err, collection){ if (err){ mongodb.close(); return callback(err); } var map = function(){ emit(this.consumeDate.substr(0,7),{amount:this.consumeAmount,count:1}); }; var reduce = function(key,vals){ var val = 0,count=0; for(var i=0; i
全部源码见附件。
另,2014-04-02
系统开发好了之后,找了个服务器部署了一下,老婆用得不错,原来不太喜欢记账,现在记账很积极。我就又把系统完善了一下,增加了翻页、权限控制功能,将几个按钮修改成图标,布局更紧凑了一些,使用全局变量保存系统名称,方便修改。