eggjs自动加载路由

刚接触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那样的路由配置风格。
做这个东西就是为了偷懒,写好之后,可以不用再去管路由配置问题,嗯,少了一些事,未对性能这块进行测试,读取加载应该影响不大,下次再看看是否可以封装成插件,有时间再来研究~~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容