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.js
const isJSON = require('koa-is-json');
const zlib = require('zlib');
async function gzip(ctx, next) {
await next();
// 后续中间件执行完成后将响应体转换成 gzip
let 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
:这个方法会在应用程序关闭时调用,可以在这个方法中进行一些清理操作。