Egg
前言
Egg.js 为企业级框架和应用而生。基于Koa开发封装,性能优异,内置多进程管理,具有高扩展性,且提供了基于Egg定制上层框架的能力,帮助开发团队降低了开发维护成本。
约定先于配置,相较于express更加灵活可配。
Koa
Koa是Express原班人马导致的,致力于web应用和API领域更小,更丰富的web框架。其和express设计风格类似,底层采用同一套HTTP基础库。
- koa采用中间件洋葱图,所有请求经过一个中间件时均会被执行2次,可以方便进行后置逻辑处理,而express中间件只会执行一次
- koa新增了
Context
对象,其作为上下文贯穿整个请求过程,其上也挂载了Resquest
、Response
对象,而express只有Resquest
、Response
挂载在中间件上。
- 使用
async await
,可以同步的方式书写异步代码,书写便捷,异常捕获错误。
使用
安装egg
npm install -g egg
使用脚手架搭建项目
$ mkdir egg-example && cd egg-example
$ npm init egg --type=simple
$ npm i
npm run dev 启动项目,默认可访问http://localhost:7001
约定目录
egg-project
├── package.json
├── app.js (可选) //项目入口文件
├── agent.js (可选) //进程处理,例如日志打印等
├── app
| ├── router.js //路由定义
│ ├── controller //处理用户输入,返回所需结果
│ | └── home.js
│ ├── service (可选) //处理业务层逻辑,调用后端api
│ | └── user.js
│ ├── middleware (可选) //中间件
│ | └── response_time.js
│ ├── schedule (可选) //定时任务
│ | └── my_task.js
│ ├── public (可选) //静态资源
│ | └── reset.css
│ ├── view (可选) //前端页面,模板文件
│ | └── home.tpl
│ └── 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
文件加载顺序
- 加载 plugin,找到应用和框架,加载
config/plugin.js
- 加载 config,遍历 loadUnit 加载
config/config.{env}.js
- 加载 extend,遍历 loadUnit 加载
app/extend/xx.js
-
自定义初始化,遍历 loadUnit 加载
app.js
和 agent.js
- 加载 service,遍历 loadUnit 加载
app/service
目录
- 加载 middleware,遍历 loadUnit 加载
app/middleware
目录
- 加载 controller,加载应用的
app/controller
目录
- 加载 router,加载应用的
app/router.js
优先级:插件>框架>应用,被依赖的优先加载,越底层的越先加载
生命周期
app.js
// 项目启动时执行
class AppBootHook {
constructor(app) {
this.app = app;
}
configWillLoad() {
console.log("配置文件即将加载,这是最后动态修改配置的时机")
}
configDidLoad() {
console.log("配置文加载完成,可在此处获取配置文件信息")
}
async didLoad() {
console.log("所有文件加载完成")
}
async willReady() {
console.log("插件启动完成")
}
async didReady() {
console.log("worker准备就绪")
}
async serverDidReady() {
console.log("应用启动完成,服务已在监听状态")
}
async beforeClose() {
console.log("应用即将关闭")
}
}
module.exports = AppBootHook;
路由
路由配置在:app/router.js
router.get(routerName, path,middleware,controller);
-
routerName
:路由别名
-
path
:路由可访问的路径
-
middleware
:中间件(可选)
-
controller
:映射到具体的controller逻辑方法
get为路由请求方式,controller
会以.
为区分,查找app/controller
目录下对用的文件,最后一个标识表示controller
中的method
名,如果不存在则默认访问index()
例如:
router.post('/admin', isAdmin, app.controller.admin);
isAdmin
是中间件,访问/admin
时,将执行app/controller/admin
文件下的index()
请求参数获取
//get请求
let name = ctx.query.name
//post请求
let name = ctx.body.name
//路由上动态参数
let name = ctx.params.name
内置对象
-
Application
:全局唯一实例对象,在应用任意地方均可以获取。获取方式:this.app
-
Context
:请求级别对象,包含了用户请求信息响应信息,一般存在于Middleware
、Controller
、Service
中。获取方式:this.ctx
-
Config
:应用配置信息,存在于Controller
、Service
、Helper
。获取方式:app.config
或this.config
-
Logger
:日志对象。使用方式:app.logger
。
Controller
、Service
、Helper
:这三个类,均内置了如下属性:
-
ctx
- 当前请求的 Context 实例。
-
app
- 应用的 Application 实例。
-
config
- 应用的配置。
-
service
- 应用所有的 service。
-
logger
- 为当前 controller 封装的 logger 对象。
-
helper
-公共函数辅助对象
中间件
中间件配置在:app/middleware
下。
写法
module.exports = (options,app)=>{
//每个中间件都接收2个参数,options:中间件属性,app:应用实例对象
//可获取配置信息
return async (ctx, next) => {
//中间件返回一个异步的方法,方法接收2个参数,ctx:请求上下文,next:下一次执行的函数
//中间件逻辑处理
await next()
}
}
每个中间件都接收2个参数,options
:中间件自定义配置,app
:应用实例
其需要导出一个异步函数。
使用
定义好的中间件需要我们手动进行加载配置。
在config/config.default.js
中定义需要加载的中间件,然后配置其所需属性
config.middleware = [
"test" //注册test中间件需要加载
];
config.test = { //test中间件属性配置
name: "中间件测试"
}
每个中间件都有3个默认属性:
- enable:控制中间件是否开启。
- match:设置只有符合某些规则的请求才会经过这个中间件。
- ignore:设置符合某些规则的请求不经过这个中间件。
控制器(Controller)
所有的controller
文件均应放在app/controller
目录下,因为应用约定在该目录下查找路由对应的逻辑处理,支持多级目录。
写法
// app/controller/post.js
const Controller = require('egg').Controller;
class PostController extends Controller {
async create() {
const { ctx, service } = this;
const createRule = {
title: { type: 'string' },
content: { type: 'string' },
};
// 校验参数
ctx.validate(createRule);
// 组装参数
const author = ctx.session.userId;
const req = Object.assign(ctx.request.body, { author });
// 调用 Service 进行业务处理
const res = await service.post.create(req);
// 设置响应内容和响应状态码
ctx.body = { id: res.id };
ctx.status = 201;
}
}
module.exports = PostController;
每个自定义控制器都应该继承egg
的Controller
类,这样才能将内置属性挂载到自定义控制器上。
每个方法都应该是一个async await
函数
每个文件只能存在一个导出类,必须用module.exports
导出。
模板渲染
Context
提供了3个Promise
接口进行模板渲染。
-
render(name, locals)
渲染模板文件, 并赋值给 ctx.body
-
renderView(name, locals)
渲染模板文件, 仅返回不赋值
-
renderString(tpl, locals)
渲染模板字符串, 仅返回不赋值
locals为需要传递给前端的数据,是一个对象
数据返回
ctx.body=data
服务(Service)
所有的controller
文件均应放在app/service
目录下。支持多级目录
// app/service/user.js
const Service = require('egg').Service;
class UserService extends Service {
async find(uid) {
const user = await this.ctx.db.query('select * from user where uid = ?', uid);
return user;
}
}
module.exports = UserService;
每个方法都应该是一个async await
函数,且必须继承egg.Service
每个文件只能存在一个导出类,必须用module.exports
导出。
service的ctx可以发起请求,也可以访问数据库。
-
this.ctx.curl
发起网络调用。
-
this.ctx.service.otherService
调用其他 Service。
-
this.ctx.db
发起数据库调用等, db 可能是其他插件提前挂载到 app 上的模块。
模板渲染
egg支持egg-view-nunjucks
模板引擎,静态资源模板采用egg-view-assets
。
插件下载
npm i egg-view-nunjucks --save
npm i egg-view-assets --save
启动插件
// config/plugin.js
module.exports = {
assets: {
enable: true,
package: 'egg-view-assets',
},
nunjucks: {
enable: true,
package: 'egg-view-nunjucks',
}
};
插件配置
// config/config.default.js
//配置模板根目录,这样在controller中render时则可省略根目录直接在该目录下查找指定文件
config.view = {
root: path.join(appInfo.baseDir, 'app/view/entry'),
mapping: {
".jsx": "assets"
}
}
//指定模板位置,编译后输出位置,本地测试打包配置devServer
config.assets={
templatePath:path.join(appInfo.baseDir, 'app/view/template/index.html'),
templateViewEngine: 'nunjucks',
devServer: {
command: 'roadhog dev',
debug: true,
port: 8000,
timeout:180*1000,
env: {
BROWSER: 'none',
ESLINT: 'none',
SOCKET_SERVER: 'http://127.0.0.1:8000',
PUBLIC_PATH: 'http://127.0.0.1:8000',
},
},
使用
模板文件app/view/template/index.html
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>模板渲染</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit">
{{ helper.assets.getStyle() | safe }}
</head>
<body>
<div id="ReactApp"></div>
{{ helper.assets.getScript() | safe }}
</body>
</html>
helper.assets可动态的获取render参数,并且生成可访问的映射路径,例如:home.js
-> http://127.0.0.1:8000/home.js
默认情况下publicPath:"/"
,如果配置publicPath:"public
,则映射关系也将改变,home.js
-> http://127.0.0.1:8000/public/home.js
webpack配置,.webpackrc
{
"entry": "app/view/entry/*.jsx",
"extraBabelPlugins": [
[ "import", { "libraryName": "antd", "style": true } ]
],
"outputPath": "app/public",
"disableCSSModules": true,
"hash": true,
"manifest": {
"fileName": "../../../config/manifest.json"
}
}
这里引入了antd,所以package.json需含有 @babel/core
、babel-plugin-import
这两个依赖。
roadhog在开发环境下publicPath永远为/
入口文件,app/view/entry/home.jsx
import React from "react";
import ReactDom from "react-dom";
const Home = () => {
return (
<div>我是入口模板</div>
)
}
ReactDom.render(<Home />, document.getElementById('ReactApp'));
模板渲染,app/controller/home.js
const Controller = require('egg').Controller;
class HomeController extends Controller {
async index() {
const { ctx } = this;
await ctx.render("home.jsx");
}
}
module.exports = HomeController;
参考地址
egg官网:https://eggjs.org/zh-cn/basics/structure.html