Egg.js 为企业级框架和应用而生。基于Koa开发封装,性能优异,内置多进程管理,具有高扩展性,且提供了基于Egg定制上层框架的能力,帮助开发团队降低了开发维护成本。
约定先于配置,相较于express更加灵活可配。
Koa
Koa是Express原班人马导致的,致力于web应用和API领域更小,更丰富的web框架。其和express设计风格类似,底层采用同一套HTTP基础库。
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
config/plugin.js
config/config.{env}.js
app/extend/xx.js
app.js
和 agent.js
app/service
目录app/middleware
目录app/controller
目录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
文件均应放在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.bodyrenderView(name, locals)
渲染模板文件, 仅返回不赋值renderString(tpl, locals)
渲染模板字符串, 仅返回不赋值locals为需要传递给前端的数据,是一个对象
数据返回
ctx.body=data
所有的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
模板渲染
{{ helper.assets.getStyle() | safe }}
{{ helper.assets.getScript() | safe }}
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 (
我是入口模板
)
}
ReactDom.render( , 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