这些天有点儿空闲,学习了一下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
-
-
-
-
- var express = require('express');
- var http = require('http');
- var path = require('path');
- var util=require('util');
-
- var routes = require('./routes');
-
- var settings=require('./Settings');
-
- var MongoStore = require('connect-mongo')(express);
- var flash = require('connect-flash');
-
- var app = express();
-
-
- app.locals.gAppTitle = settings.appName;
-
- app.locals.gPageSize = settings.pageSize;
-
- 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){
-
-
-
-
- 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;
-
-
- var success = req.flash('success');
- res.locals.success = success.length?success:null;
-
-
- res.locals.session = req.session;
- next();
- });
-
- app.use(app.router);
- app.use(express.static(path.join(__dirname, 'public')));
-
-
-
- if ('development' == app.get('env')) {
- app.use(express.errorHandler());
- }
-
-
-
- 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');
-
-
-
-
- module.exports = function(app) {
- app.get('/',function(req, res){
- res.render('index', { title: '首页' });
-
-
-
-
-
-
- });
-
-
- 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('/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){
-
- 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)
- {
-
- 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);
- }
-
- db.collection('consume', function(err, collection){
- if (err)
- {
- mongodb.close(); return callback(err);
- }
-
-
-
-
-
-
-
-
- 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);
- }
-
- db.collection('consume', function(err, collection){
- if (err){
- mongodb.close();
- return callback(err);
- }
-
- var query = {};
- if(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);
- }
-
- 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);
- }
-
- 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<vals.length;i++){
- val += isNaN(vals[i].amount)?0:Number(vals[i].amount);
- count++;
- }
- return {amount:val,count:count};
- }
-
- console.log("统计:"+username);
- collection.mapReduce(
- map,
- reduce,
- {out: {replace : 'temp', readPreference : 'secondary', query:{userName:username}}},
- function(err, collection){
- if (err){
- mongodb.close();
- callback(err, null);
- }
- else{
-
-
-
-
-
-
-
-
-
-
- var results = [];
- collection.find().toArray(function(err,docs){
- docs.forEach(function(item,idx){
- console.log(item);
-
- results.push({consumeSubject:item._id,amount:item.value.amount,count:item.value.count});
- });
- mongodb.close();
- callback(null, results);
- });
- }
- });
- });
- });
- };
全部源码见附件。 另,2014-04-02
系统开发好了之后,找了个服务器部署了一下,老婆用得不错,原来不太喜欢记账,现在记账很积极。我就又把系统完善了一下,增加了翻页、权限控制功能,将几个按钮修改成图标,布局更紧凑了一些,使用全局变量保存系统名称,方便修改。