Node.js 的框架有很多,常用的几个也有各种各样的难处.怎么使用 Node.js 开发一个属于自己的 Web 框架呢?下面我将带领大家在 Koa2 的基础上开发一款初步具备 Web 功能的框架。
本场 Chat 主要内容:
不会造轮子的程序员不是一个好木匠
以下代码全部放在了 github 上:链接地址
我们要做的是一个轻量级,所以一些基础的功能还是需要的。这里制定一个简单的框架启动流程。
这里包含了几个简单的功能。
通过一个合适的规范可以减少很多开发上的东西。
我们通过在固定的目录中做固定的事情将框架内的不同功能区分开来。
- config //配置文件 - default.js //默认配置 - development.js //开发环境配置 - production.js //生成环境配置- controller //控制器 - index.js //自定义路由地址- schedule //定时任务文件 - test.js //自定义定时任务- app.js //自定义启动文件- index.js //启动文件,引用duck即可- pm2.js //pm2的配置文件-
这里只有根目录下的 index.js
是必须要的,主要是启动整个项目。config
目录下主要是放置配置,这个目录一般情况下省不了。controller
目录下放置路由的方法等,也是这个框架最常用的目录。其余的都是可选择的,不添加不影响功能。
从上面的几点可以看出来,框架基于配置的方式做到动态加载。所以启动的时候会扫一次目录,将有的功能加进去,没有的功能不做处理。
在 lib
目录下新建一个 index.js
文件,作为我们的整个框架的核心——其实就是启动 koa 并将我们要实现的功能加上去:)
const Koa = require("koa");const KoaBody = require("koa-body");//附加一个根目录const rootPath = process.cwd();let app = new Koa();app.root = rootPath;//处理请求对象app.use(KoaBody({ multipart: true, strict: false, jsonLimit: '10mb', formLimit: '10mb', textLimit: '10mb'}));
使用过 koa 的同学可能会非常的熟悉,其实这里就是一个简单的 koa 初始化过程。不同的地方是后面我们要附加的一些操作。
我们给用户留一个可以操作顶层 application 对象的地方。入口就是这里:假如 app.js 文件存在就会将初始化中的 app 对象传进去处理一次再转出来。
//加载appcalitiontry { let application = require(app.root + "/app.js"); application(app);} catch (error) { Logger.info(error.message);}
这里我们判断我们的 controller
目录下的文件。依次加载并将返回的路由对象加载到 applicaiton 上。这里利用的依然是一个动态加载的原理,将内容在启动阶段载入到内存中并执行它。
//添加路由if (fs.existsSync(app.root + "/controller")) { let controller_list = fs.readdirSync(app.root + "/controller"); controller_list.forEach(item => { try { let controller_item = require(app.root + "/controller/" + item); if (controller_item) { app.use(controller_item.routes()).use(controller_item.allowedMethods()); } } catch (error) { Logger.info(error.message); } });}
正常情况下只需要监听一下端口即可。这里稍微处理了一次,假如用户使用命令退出进程或者进程异常退出,这里就会监听到这个操作。
这个地方可以自定义做一个触发事件,触发 applicaiton 的停止事件。比如数据库、redis 等的连接断开操作。
const port = config("port") || 3000;app.listen(port, function () { Logger.info("app已启动:" + port)});process.on('SIGINT', function (a) { process.exit();});process.on('exit', (code) => { Logger.info("app已停止:" + code)});process.on('uncaughtException', (code) => { Logger.info("app已停止:" + code)});module.exports = app;
定时任务比较简单,只需要判断任务文件是否存在,然后执行即可。
//启动定时器if (fs.existsSync(app.root + "/schedule")) { let schedule_list = fs.readdirSync(app.root + "/schedule"); schedule_list.forEach(item => { try { let schedule_item = require(app.root + "/schedule/" + item); if (schedule_item) new schedule_item()._run(); } catch (error) { Logger.info(error.message); } });}
tips:上面的几个方式可以作为一个工具类中的方法存在。
上面的仅仅是一个基础的实现。假如你想现在就跑起来,不好意思,它们的依赖还没有实现呢。
npm 库里已经有一个非常强大的配置加载方式了。这个轮子我们先不造了。npm install --save config
安装一下就好了。
我们这里使用 log4js
库来完成日志的打印输出。这里稍微简单的配置一下内部的日志对象。
在 lib/logger.js
中添加我们自己的日志配置。
const log4js = require('log4js');log4js.configure({ appenders: { stdout: { type: "stdout" }, error: { type: 'dateFile', filename: './logs/error.log', }, info: { type: 'dateFile', filename: './logs/info.log' } }, categories: { default: { appenders: ["info", "stdout"], level: 'info' }, err: { appenders: ['error', "stdout"], level: 'error' } }, pm2: true, pm2InstanceVar: "node-sso-wechat"});const logger = log4js.getLogger('error');const logger2 = log4js.getLogger('info');module.exports = { error() { logger.error(...arguments); }, info() { logger2.info(...arguments); }, warm() { logger2.info(...arguments); }};
这里我们简单的提供 3 中日志形式。
这里将日志分开存主要是为了区分记录日志和错误信息这2中特殊情况。当然,也可以将这些配置放在 config 中,通过使用中去配置日志。
我们给使用者提供一个简单的路由方法,这样就不需要用户去主动实现路由了。用户只需要关注具体的路由和实现即可。
const Router = require("koa-router");module.exports = function (prefix) { let opts = {}; if (prefix) opts.prefix = prefix; return new Router(opts);}
可以看到我们使用的是 koa-router
这个库。用户调用的时候还可以传入 prefix
参数来配置路由的跟目录。
用户只需要使用返回的路由方法即可。
由于定时任务并不需要参数 web 的请求返回等过程,所以在设计的时候只需要继承我们的基础类就能实现了。
const schedule = require('node-schedule');/** * 定时任务的基类 * 设置时间,可执行方法 * 默认执行run方法 */module.exports = class { constructor() { this.time = ""; } start() {} _run() { schedule.scheduleJob(this.time, this.start); }}
我们仅仅做了 3 件事就完成了定时任务。
time
参数,预留给用户做时间间隔配置。start
方法,预留给用户做具体的执行内容。_run
方法就是我们自己的执行定时任务的方法,是不能给用户看到的。(当然还是可以重载了。。。)。一个简单的 demo 已经放在 github 上了:链接地址
在根目录下添加 index.js
文件,引用我们的框架。
const Duck = require("node-duck");
在根目录下新建 config
目录,新建 default.js
默认配置,production.js
等环境配置。
/** * 默认的配置文件 * 不同环境下还会加载不同的配置 */module.exports = { name: "duck.js",//给自己用的配置 port: "3001",//启动的接口 //初始化的body对象参数 body: { multipart: true, strict: false, jsonLimit: '10mb', formLimit: '10mb', textLimit: '10mb' }}
在根目录下新建 controller
目录,将我们的路由写在这个目录中。文件的名字对路由没有影响。
/** * 测试路由 */const Duck = require("node-duck");//默认跟路由const Controller = new Duck.Controller("/");//首页Controller.get("", function (ctx) { ctx.body = "hi,duck!";})//test目录Controller.get("test", function (ctx) { ctx.body = "hi,duck!";})//导出module.exports = Controller;
从这里可以看到,我们的路由天生就配置比较简单。只需要配置一个 prefix,剩下就简单多了。
在根目录下新建 schedule
目录,里面的任何一个文件都必须集成导出的 schedule 对象,不然会报错的^_^。
/** * 测试定时任务 */const Schedule = require("node-duck").Schedule;class testSchedule extends Schedule { constructor() { super(); this.time = "*/1 * * * * *"; } start() { console.log("执行一次", Date.now()); }}module.exports = testSchedule;
这里使用的时间配置是 cron 的格式:
* * * * * *┬ ┬ ┬ ┬ ┬ ┬│ │ │ │ │ ││ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)│ │ │ │ └───── month (1 - 12)│ │ │ └────────── day of month (1 - 31)│ │ └─────────────── hour (0 - 23)│ └──────────────────── minute (0 - 59)└───────────────────────── second (0 - 59, OPTIONAL)
ex:
"*/1 * * * * *"
。"1 * * * * *"
。"2-5 * * * * *"
。到这里的时候一个框架的基础架构以及 demo 展示都已经做完了。如果你还有自己更多的需求,也可以在这个的基础上扩展一下。
写这个框架最初的目的可能就是用不惯其他框架吧。好用的又封装太厉害了。封装不那么厉害的实现又非常的复杂。既然如此,干脆自己写一个好了。反正这玩意也不是一个多难的东西,再说了我还可以自己往上面安装各种奇怪的功能。
我将我自己的想法分享出来,希望有需要的能够从中有一些收获。
本文首发于GitChat,未经授权不得转载,转载需与GitChat联系。
阅读全文: http://gitbook.cn/gitchat/activity/5b5c2676d7a1a133f9f6d63b
您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。