JianghuJS项目结构
目录结构
在JianghuJS中,目录结构沿用 eggjs 的结构规范。
jianghujs-project├── package.json├── app.js (可选)├── agent.js (可选)├── app| ├── router.js│ ├── controller│ | └── home.js│ ├── service (可选)│ | └── user.js│ ├── middleware (可选)│ | └── response_time.js│ ├── schedule (可选)│ | └── my_task.js│ ├── public (可选)│ | └── reset.css│ ├── view (可选)│ | └── page/│ └── extend (可选)│ ├── helper.js (可选)│ ├── request.js (可选)│ ├── response.js (可选)│ ├── context.js (可选)│ ├── application.js (可选)│ └── agent.js (可选)├── config| ├── plugin.js| ├── config.default.js│ ├── config.prod.js| ├── config.test.js (可选)| ├── config.local.js (可选)| └── config.unittest.js (可选)└── test├── middleware| └── response_time.test.js└── controller└── home.test.js
如上,由框架约定的目录:
app/router.js用于配置 URL 路由规则,具体参见 Router。app/controller/**用于解析用户的输入,处理后返回相应的结果,具体参见 Controller。app/service/**用于编写业务逻辑层,可选,建议使用,具体参见 Service。app/middleware/**用于编写中间件,可选,具体参见 Middleware。app/public/**用于放置静态资源,可选,具体参见内置插件 egg-static。app/extend/**用于框架的扩展,可选,具体参见框架扩展。config/config.{env}.js用于编写配置文件,具体参见配置。config/plugin.js用于配置需要加载的插件,具体参见插件。test/**用于单元测试,具体参见单元测试。app.js和agent.js用于自定义启动时的初始化工作,可选,具体参见启动自定义。关于agent.js的作用参见Agent 机制。
由内置插件约定的目录:
app/public/**用于放置静态资源,可选,具体参见内置插件 egg-static。app/schedule/**用于定时任务,可选,具体参见定时任务。
【jianghujs核心】中间件-链式调用
中间件本质上是一个函数,它可以对请求进行拦截,处理,以及传递给下一个中间件或者路由处理函数。中间件的使用使得我们可以将请求处理流程进行拆分,将处理逻辑进行模块化,便于代码的复用和维护。
在 Egg.js 中,中间件是通过 app 和 ctx 这两个对象进行传递的。在每次请求中,Egg.js 会按照中间件的顺序将 ctx 对象传递给一个个中间件函数,并执行它们的逻辑。在中间件的执行过程中,中间件函数可以根据需要对 ctx 对象进行修改,也可以决定是否将请求传递给下一个中间件或者路由处理函数。这样就可以形成一个链式的请求处理过程,最终得到我们期望的结果。
除了链式调用的核心思想外,Egg.js 中间件还具有以下特点:
- 可以定义多个中间件,形成一个中间件数组。
- 中间件可以通过
app对象进行注册。 - 中间件的执行顺序可以通过定义中间件的顺序进行控制。
- 中间件可以通过
ctx对象进行状态共享,便于数据的传递和处理。 - 中间件可以根据需要对请求进行拦截、修改和终止等操作。
我们先来通过编写一个简单的 gzip 中间件,来看看中间件的写法。
// app/middleware/gzip.jsconst isJSON = require('koa-is-json');const zlib = require('zlib');async function gzip(ctx, next) {await next();// 后续中间件执行完成后将响应体转换成 gziplet body = ctx.body;if (!body) return;if (isJSON(body)) body = JSON.stringify(body);// 设置 gzip body,修正响应头const stream = zlib.createGzip();stream.end(body);ctx.body = stream;ctx.set('Content-Encoding', 'gzip');}
定时任务
虽然我们通过框架开发的 HTTP Server 是请求响应模型的,但是仍然还会有许多场景需要执行一些定时任务,例如:
- 定时上报应用状态。
- 定时从远程接口更新本地缓存。
- 定时进行文件切割、临时文件删除。
框架提供了一套机制来让定时任务的编写和维护更加优雅。
编写简写模式的定时任务
所有的定时任务都统一存放在 app/schedule 目录下,每一个文件都是一个独立的定时任务,可以配置定时任务的属性和要执行的方法。
一个简单的例子,我们定义一个更新远程数据到内存缓存的定时任务,就可以在 app/schedule 目录下创建一个 update_cache.js 文件。
module.exports = {schedule: {interval: '1m', // 1 分钟间隔type: 'all', // 指定所有的 worker 都需要执行},async task(ctx) {const res = await ctx.curl('http://www.api.com/cache', {dataType: 'json',});ctx.app.cache = res.data;},};
这个定时任务会在每一个 Worker 进程上每 1 分钟执行一次,将远程数据请求回来挂载到 app.cache 上。
江湖应用启动的生命周期
在江湖应用程序的启动过程中,以下是应用程序的启动生命周期方法的详细列表(参考项目根目录 app.js ):
configWillLoad:这个方法会在配置文件即将加载时调用,可以在这个方法中加载一些自定义的配置文件。configDidLoad:这个方法会在配置文件加载完成后调用,可以在这个方法中对加载好的配置文件进行一些操作。didLoad:这个方法会在应用程序启动时调用,可以在这个方法中进行一些初始化操作。willReady:这个方法会在应用程序启动时调用,可以在这个方法中加载一些插件或者其他依赖项。didReady:这个方法会在应用程序启动时调用,可以在这个方法中进行一些初始化完毕后的操作。serverDidReady:这个方法会在应用程序启动时调用,可以在这个方法中进行一些服务端启动完毕后的操作。beforeClose:这个方法会在应用程序关闭时调用,可以在这个方法中进行一些清理操作。