快速开发一个 Web 框架

Node.js 的框架有很多,常用的几个也有各种各样的难处.怎么使用 Node.js 开发一个属于自己的 Web 框架呢?下面我将带领大家在 Koa2 的基础上开发一款初步具备 Web 功能的框架。

本场 Chat 主要内容:

  1. Web 框架需要那些功能。
  2. 完成一个简单的框架雏形。
  3. 添砖加瓦,构成一个轻量级框架。
  4. 后续的开发。

不会造轮子的程序员不是一个好木匠

以下代码全部放在了 github 上:链接地址

制定 web 框架流程

我们要做的是一个轻量级,所以一些基础的功能还是需要的。这里制定一个简单的框架启动流程。

快速开发一个 Web 框架_第1张图片

这里包含了几个简单的功能。

  1. 定制化的配置管理,根据环境变量自动加载。
  2. 提供基础的工具库,可以自由加入需要的功能(加密,远程请求等)。
  3. 一个简单使用的日志类,按照日期存储在项目根目录下的 logs 目录中。
  4. 可以使用的路由基础方法,通过这个方法注入自定义的路由。
  5. 一个简单实用的定时任务基础类,继承即可启动。
  6. 可选择的 app.js 文件,可以对 application 对象注入一些自己的东西。

定制目录规范

通过一个合适的规范可以减少很多开发上的东西。

我们通过在固定的目录中做固定的事情将框架内的不同功能区分开来。

- 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 初始化过程。不同的地方是后面我们要附加的一些操作。

执行 app.js

我们给用户留一个可以操作顶层 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 中日志形式。

  1. 一直存在的输出到命令行。
  2. 输出到 info.log 中 info 和 warm 方法。
  3. 输出到 error.js 的 error 方法。

这里将日志分开存主要是为了区分记录日志和错误信息这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 件事就完成了定时任务。

  1. 初始化 time 参数,预留给用户做时间间隔配置。
  2. 初始化 start 方法,预留给用户做具体的执行内容。
  3. _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. 每分钟的第一秒执行一次 "1 * * * * *"
  3. 第 2 到 5 秒执行一次 "2-5 * * * * *"
  4. time 参数也可以传 Date 对象。

扩展

到这里的时候一个框架的基础架构以及 demo 展示都已经做完了。如果你还有自己更多的需求,也可以在这个的基础上扩展一下。

  1. 扩展 utils,给自己添加更多的工具
  2. 扩展插件,添加数据库操作类等方法
  3. 扩展事件,将 application 的各种事件扩展出来

总结

写这个框架最初的目的可能就是用不惯其他框架吧。好用的又封装太厉害了。封装不那么厉害的实现又非常的复杂。既然如此,干脆自己写一个好了。反正这玩意也不是一个多难的东西,再说了我还可以自己往上面安装各种奇怪的功能。

我将我自己的想法分享出来,希望有需要的能够从中有一些收获。


本文首发于GitChat,未经授权不得转载,转载需与GitChat联系。

阅读全文: http://gitbook.cn/gitchat/activity/5b5c2676d7a1a133f9f6d63b

您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。

FtooAtPSkEJwnW-9xkCLqSTRpBKX

你可能感兴趣的:(快速开发一个 Web 框架)