Egg框架介绍和应用(一)
# 从需求出发
虽然 Flask 在以前的工作中用的比较多,但是后来专门写前端之后就很少写 Python 了,所以后面有写接口的需求都是用 express 写,但是越写越发现 express 适合一个人或者人数不多的项目,一旦项目的人数多了起来,express 就有些力不从心,特别是目录结构和代码规定,这在一个项目中是至关重要的,我个人还是喜欢配置优于代码的框架设计。后面尝试了 koa 之后发现了洋葱模型的好处,但是也还是没有解决我的需求。直到我看到了 Egg,我感觉自己找到了能够解决我的需求的东西。
# Egg 介绍
Egg 是阿里的开源项目,阿里内部开源出来的项目名声一直不怎么好,所以刚开始的时候我还是有点忌惮,一开始他的文档也确实不齐全,后面慢慢官方文档补充的越来越齐全,文档齐全对于一个开源项目是一个很大的加分。
# 初始化项目
$ mkdir egg-example && cd egg-example
$ npm init egg --type=simple
$ npm i
# 启动项目
$ npm run dev
# 目录结构(个人推荐)
egg-project
├── app
├── router.js
│ ├── controller
│ | └── user.js
│ ├── service
│ | └── user.js
│ ├── middleware
│ | └── error_handler.js
│ │── extend
| └── helper.js
|
├── config
| ├── plugin.js
| ├── config.default.js
│ ├── config.prod.js
|── test
│ ├── middleware
│ | └── response_time.test.js
│ └── controller
│ └── home.test.js
── package.json
如果你是用 egg 的 cli 创建的项目,那么目录肯定比上述的要多,但是一个项目首先要能直接上手开发,后面再去了解底层的一些原理和知识。学习永远是正向反馈的话,谁不会爱上学习呢?上面的列出来的目录结构是开发必须的,但是 test 我不敢说一定要,很多项目是少于 3 个人或者单人的,这时候写单元测试不一定是最佳解决方案,保持开发进度需要做取舍。
# app
这个目录是核心目录,如果有过后端开发经验的人看这个目录下的 📂 就知道这些代表这些,不过 Egg 不同的是把路由和控制器的文件分开了,至于这个是好是坏见仁见智。
# router
路由是应用的关键,下面是 router 的代码实例:
// app/router.js
"use strict";
module.exports = app => {
const { router, controller } = app;
router.post("/user/loginRegister/register", controller.user.register);
};
Egg 官方描述上为“Router 主要用来描述请求 URL 和具体承担执行动作的 Controller 的对应关系, 框架约定了 app/router.js 文件用于统一所有路由规则” 如果是有 express 或者 koa 经验的开发者就会明白这跟路由绑定中间件很像,其实 Egg 的路由也支持在控制器前面添加中间件来满足实现业务需求。比如:
// app/router.js
"use strict";
module.exports = app => {
const { router, controller } = app;
router.get("/user/usernameExist/:username", controller.user.usernameExist);
};
注意 user 文件是必须在 controller 文件夹下的,不然 Egg 无法找到对应的方法。
# controller
Egg 官方框架推荐 Controller 层主要对用户的请求参数进行处理(校验、转换),然后调用对应的 service 方法处理业务,得到业务结果后封装并返回:
- 获取用户通过 HTTP 传递过来的请求参数。
- 校验、组装参数。
- 调用 Service 进行业务处理,必要时处理转换 Service 的返回结果,让它适应用户的需求。
- 通过 HTTP 将结果响应给用户。
例子:
// app/controller/user.js
"use strict";
const Controller = require("egg").Controller;
class UserController extends Controller {
async usernameExist() {
const { ctx, service } = this;
const { username } = ctx.params;
const user = await service.user.getUserOne({ username });
if (user) {
ctx.throw(422, "用户已经存在");
} else {
ctx.helper.success({ ctx });
}
}
}
module.exports = UserController;
ctx.helper会放在下面的extend上讲
# service
service 就是对业务逻辑的一层封装,最常见的场景比如对数据库的特定操作和一些计算以及对第三方服务的调用。
// app/service/user.js
"use strict";
const Service = require("egg").Service;
class UserService extends Service {
async getUserOne(conditions) {
return this.ctx.model.User.findOne(conditions);
}
}
module.exports = UserService;
# extend
上面controller的代码中有ctx.helper.success()这样的方法,因为Egg会把 app/extend/helper.js 中定义的对象与内置 helper 的 prototype 对象(看不懂的请复习原型链)进行合并,在处理请求时会基于扩展后的 prototype 生成 helper 对象。
// app/extend/helper.js
exports.success = ({ ctx, data = null, message = "success" }) => {
ctx.body={
code: 0,
data,
message
}
ctx.status = 200
}
我们先介绍Egg的app目录下的这几个目录下的文件,这些也是日常开发主要操作的目录。