刚接触nodejs不久,在后端的web框架中,有接触过express,koa,sails等一些框架。
express简单,易上手,扩展也很强,但是写法规范很多。
koa这次支持了es7的异步,终于可以摆脱回调的烦恼。
sails作为api服务端,功能很强大,但是文档不是太多,当时候接触的时候因为插件的版本号踩过很多坑,所以不是太喜欢。
eggjs,阿里出品,基于koa,整理了解之后感觉不错选择,文档也比较齐全。
主要记录eggjs中实际应用可能碰到的问题
快速开始
见官方文档,就不再重复
eggjs官网文档
路由
eggjs路由是通过router.js这个文件来配置信息。
其中源码中有一段restful router api的代码:
resources(...args) {
const splited = spliteAndResolveRouterParams({ args, app: this.app });
const middlewares = splited.middlewares;
// last argument is Controller object
const controller = splited.middlewares.pop();
let name = '';
let prefix = '';
if (splited.prefix.length === 2) {
// router.get('users', '/users')
name = splited.prefix[0];
prefix = splited.prefix[1];
} else {
// router.get('/users')
prefix = splited.prefix[0];
}
for (const key in REST_MAP) {
const action = controller[key];
if (!action) continue;
const opts = REST_MAP[key];
let formatedName;
if (opts.member) {
formatedName = inflection.singularize(name);
} else {
formatedName = inflection.pluralize(name);
}
if (opts.namePrefix) {
formatedName = opts.namePrefix + formatedName;
}
prefix = prefix.replace(/\/$/, '');
const path = opts.suffix ? `${prefix}/${opts.suffix}` : prefix;
const method = Array.isArray(opts.method) ? opts.method : [ opts.method ];
this.register(path, method, middlewares.concat(action), { name: formatedName });
}
return this;
}
会去自动挂在restful router api的映射。
在学习sails中,会发现,它的路由配置是通过配置文件控制的,如果不配置,也会自动去加载controllers下的控制器映射到路由中。所以在当时候自己用express去尝试写了一个简单自动加载的机制。
代码如下:
var _initGlobals = function (app) {
var models = app.get('models');
for (var i in models) {
global[tools.firstUpperCase(i)] = models[i]
}
};
var _initAction = function (name, cObj, router) {
var keys = Object.keys(cObj)
if (actions) {// 映射普通action路由
_initActionApi(cObj, keys, router)
}
if (rest) {// 映射rest 路由
_initRestApi(cObj, keys, router)
}
};
var _initActionApi = function (cObj, keys, router) {
keys.forEach(function (key) {
['get', 'post', 'put', 'delete'].forEach(function (m) {
router[m]('/' + key, function (req, res) {
cObj[key](req, res)
})
})
})
};
var _initRestApi = function (cObj, keys, router) {
router.get('/', function (req, res) {
// cObj.find(req, res)
_.bind(cObj.find, this, req, res)();
})
router.post('/', function (req, res) {
_.bind(cObj.create, this, req, res)();
})
router.get('/:id', function (req, res) {
_.bind(cObj.findOne, this, req, res)();
})
router.put('/:id', function (req, res) {
_.bind(cObj.update, this, req, res)();
})
router.delete('/:id', function (req, res) {
_.bind(cObj.destroy, this, req, res)();
})
};
var _init = function (app) {
_initWSRouter(app);
_initUpload(app);// 初始化通用的上传
var controllerPath = path.join(process.cwd(), '/api', '/controllers');// 控制器的基础路径
var files = fs.readdirSync(controllerPath)// 读取文件
var name, cObj, router;
files.forEach(function (file) {
name = file.replace('Controller.js', '')// 在controllers目录下都是以Controller.js结尾命名的控制器,例如:UserController.js
cObj = require(controllerPath + '/' + file.replace('.js', '')) // 动态加载控制器js
router = express.Router()
_initAction(name, cObj, router)// 初始化路由
if (prefix) {// 是否统一添加路由前缀
app.use('/' + prefix + '/' + name.toLowerCase(), router)
// app.use('/' + name.toLowerCase(), router)
} else {
app.use('/' + name.toLowerCase(), router)
}
})
}
//api/controller/UserController.js
module.exports = {
find: function (req, res) {
res.json({type: 'find'})
},
findOne: function (req, res) {
res.json({type: 'findOne'})
},
create: function (req, res) {
res.json({type: '创建'})
},
update: function (req, res) {
res.json({type: 'update'})
},
destroy: function (req, res) {
res.json({type: 'destroy'})
}
}
基于以上方案,所以正对于eggjs路由,一个想到的采用同样方式
简单自动装载
修改router.js使用上面方式。好,开始码代码...
...
终于码完了,测试完成,话不多说,代码如下
'use strict'
const fs = require('fs')
const path = require('path')
const _initAction = function (reqPath, obj, router, controller) {
const keys = Object.getOwnPropertyNames(obj.prototype)
let c = controller
const l = reqPath.split('/')
l.splice(0, 1)
l.forEach(v => {// 循环获取到对应的路由控制器对象
c = c[v]
})
keys.forEach(function (key) {
if (key !== 'constructor') {// 去除掉构造函数
const controllerMethod = c[key]
if (controllerMethod) {// 在eggjs中,得到的keys会多出pathName,fullPath,所以过滤下
['get', 'post'].forEach(function (m) {// 定义路由的get,post,也可以扩展put,delete等
router[m](`${reqPath}/${key}`, controllerMethod)
})
}
}
})
// 这个地方,挂在restful api,但是没有验证,是否会和上面action绑定有没有冲突,理论上命名不冲突,就不会有问题
router.resources(l[l.length - 1], `${reqPath}`, c)
}
/**
* @param {Egg.Application} app - egg application
*/
module.exports = app => {
const {router, controller} = app
const controllerPath = path.join(process.cwd(), '/app', '/controller/sys') // 这个地方应该去做递归把所有js文件遍历出来
const files = fs.readdirSync(controllerPath)
let reqPath
let cObj
files.forEach(function (file) {
reqPath = controllerPath + '/' + file.replace('.js', '')
/**
*reqPath,用来通过controller中的路径作为请求路径
*例如:文件路径:/app/controller/sys/user.js 那么得到就是/sys/user
*/
reqPath = reqPath.substr(reqPath.indexOf('controller') + 10).replace(/\\/, '/')
cObj = require(controllerPath + '/' + file.replace('.js', ''))
_initAction(reqPath, cObj, router, controller)
})
// require('./router/sys/user')(app)
// require('./router/sys/permissions')(app)
}
总结
基本上可以通过这个方式实现自动加载路由配置实现。从这个中间还可以去扩展配置出类似与sails那样的路由配置风格。
做这个东西就是为了偷懒,写好之后,可以不用再去管路由配置问题,嗯,少了一些事,未对性能这块进行测试,读取加载应该影响不大,下次再看看是否可以封装成插件,有时间再来研究~~~~