时间过得很快,6月和7月忙的不可开交,糟心的事儿也是不少,杭州大连来回飞,也是呵呵。
希望下个阶段能沉浸下来,接着学自己想学的。记一下上几周用了几天时间写的课设。因为课设的缘故,所以在短时间里了解下express+mongodb的组合,给APP端搭了个简易的服务器,也开了后台网站的web服务。简单总结一下开发过程中遇到的坑。
一、关于express
了解node.js有半年多,第一次用node.js的框架来写server,了解不是很深,简单看了一下文档之后就可以上手了,开发入门难度低。
1.运行
express init之后以为是node app.js命令运行项目,结果却并不是。app.js是项目入口文件不错,但是本身也是一个module模块,app.js的代码里面并没有listenserver的操作,express的脚手架命令把项目启动交给了bin/www文件,需要通过npm start启动应用。
通过npm start的应用好像并不能实时重启[?],所以可以把www文件转移至[new]start.js并运行node start.js,就可通过supervisor或者nodemon来自动监控&重启应用了。
2.中间件
express是基于中间件的。通过引入中间件执行处理函数,例如在处理一个get请求的时候会根据路由情况在app.js里顺序执行函数,app.use('/admin', admin)就会处理所有localhost/admin下的请求,具体的第二级路由处理就交给了route/admin.js。这个课设里我只自己定义了一个中间件cookie-checker,用于登录识别。在java或者php里面可以通过filter或者入口模块检测的方式来检测cookie、判断登录,express里面就刚简化了,直接定义个cookie-checker中间件,放在了app.use('/api',api)和app.use('/admin', admin)中间,这样所有的api请求(app端)无需cookie检测,而admin下的请求全部需要经过登录检测操作,整个中间件的代码如下:
exports = module.exports = function(){ return function(req,res,next){ if( req.cookies.gid == undefined || req.cookies.gid == null ){ if( req.path != "/admin/login" ){ res.redirect("/admin/login"); } else{ next(); } } else{ next(); } } }
3.模块引用
express里面对模块互相引用目测并没有做什么处理,当然产生互相引用肯定是我自己代码的原因。
互相引用具体的位置忘了是哪里了,但是大概是articleModule.js和favoriteModule.js造成的,在处理mongodb的连表查询时候的问题。mongodb的连表也是糟心,后来也没做连表,直接把favorite操作的记录都add进一张大表里了。之前两年一直用的是mysql或者是sqlserver,在实际应用里nosql的精神还是没有领会,设计表的时候觉得还挺简单,实际应用起来就发现跟mysql、sqlserver完全不是一个路数,连表、主键、外键都没有。
4.mvc的模式
现在写个server不基于mvc都不好意思说自己是写server的。express里面做的还是不错的,route、view、modules。
route下是路由文件,用于处理路由请求,并且根据请求调用对应module的函数。所有的请求都是通过req、res、next来操作,内部函数大多是基于回调的。view下所有的模板文件,课设里面使用的是jade,感觉和smarty等模板语言没什么功能区别,映射、分布视图、公共模块、模板继承、if-else等逻辑判断,该有的都有,jade的语法倒是变了,写的不是html标签,节点名称、缩进控制层级。modules下自定义的原型模块,例如课设里面大概有这么几个module:ad、article、word、user等,模块内部引入mongoose文件完成增删改查操作,增删改查数据库的函数都是异步的,所以在文件下大部分函数也都是基于回调的。
例如API路由下的api/wordItem?id=5x23434fa5sk4dhid7a,获取的是某条句子的信息。在api.js里:
/* 单句(id) api/wordItem?id=5x23434fa5sk4dhid7a */ router.get('/wordItem', function(req, res, next) { var _id = req.query.id; var _user = req.query.uid || -1; word.findById(_id,function(err,data){ if( err ){ data._id = -1; } favorite.check({t_id:data._id,user_id:_user},function(_f){ data.flag = _f; res.send( data ); }); }); });
在word.js里面:
//根据ID获取word条目 var Word = mongodb.mongoose.model("word", wordSchema); var wordModel = function(){}; //... wordModel.prototype.findById = function(_id,callback){ var id = _id || 1; Word.findOne({'_id':id}, function(err, obj){ var itemdata = obj || {_id:-1} ; favorite.getCount({t_id:itemdata._id},function(err,count){ itemdata.favorite = count ; callback(err,itemdata); }); }); };
路由判断---调用word原型的findById函数---函数内部执行mongoose封装的WordModel的findOne操作,查询结束后执行回调,将err参数和data参数作为形参传递给回调函数。
5.文件上传
用的是multer中间件,通过json配置直接处理了文件存储位置、上传回调。
app.use(multer({ dest: 'public/upload/', limits: { fieldNameSize: 100, files: 2, fields: 5 }, onFileUploadComplete: function (file, req, res) { res.flag = true; res.fname = file.name; }, onError: function (error, next) { res.flag = false; res.fname = ""; next(error); } }));
把public文件夹设置为静态路由:
app.use(express.static(path.join(__dirname, 'public')));