课程引导
- 🚀【eggjs实战10天入门-第1天】🚀—— 搭建项目
- 🚀【eggjs实战10天入门-第2天】🚀—— controller、service和config(本篇)
- 🚀【eggjs实战10天入门-第3天】🚀—— 操作mysql
本节目标
熟悉config/router/controller/service各自的作用
1、路由(Router)
1.1、路由和controller的关系
// router.js
module.exports = app => {
const { router, controller } = app;
router.get('/hello', controller.base.index);
};
// base.js
const { Controller } = require('egg')
class BaseController extends Controller {
async index() {
this.ctx.body = {
data: "hello it's me"
}
}
}
module.exports = BaseController
再上一节中我们在router.js中写了这样的代码,他代表的含义就是当用户执行 GET /hello
时,app/controller文件夹下的base.js 这个里面的 index 方法就会执行。
那么既然有get方法,肯定就有post方法,del方法等。
但是在真实的场景中,我们肯定不会只用/hello这种比较中二的请求方式。我们需要接收前端传递过来的参数。比如获取某个id的具体信息,那我们如何获取参数呢
1.2、参数的传递
1.2.1、Query String 方式
也就是常用的xxx?a=1&b=2这种Query传参方式
我们新增一条路由,/getInfo, 这时当访问/getInfo时,就会触发base.js的getInfo方法
// app/router.js
module.exports = app => {
const { router, controller } = app;
router.get('/hello', controller.base.index);
app.router.get('/getInfo', controller.base.getInfo);
};
我们新增一个getInfo方法。当请求走到getInfo函数时,我们从this中获取ctx,也是本次请求的一个上下文context,里面包含着本次请求的数据,如参数,header,中间件挂载的一些常用数据
const { Controller } = require('egg')
class BaseController extends Controller {
index() {
this.ctx.body = {
data: "hello it's me"
}
}
getInfo () {
const { ctx } = this
console.log('ctx.query', ctx.query)
ctx.body = {
query: ctx.query
}
}
}
module.exports = BaseController
当我们发起请求,
http://127.0.0.1:7001/getInfo?a=1&b=1
,这时通过ctx.query就能拿到参数对象,{ a: '1', b: '1' }
1.2.1、Query String 方式
当参数只有一到2个的时候,我们更趋向于使用
我们新增第三条路由,getUser/:name, 这时当访问/getUser/:name时,就会触发base.js的getUser方法
module.exports = app => {
const { router, controller } = app;
router.get('/hello', controller.base.index);
app.router.get('/getInfo', controller.base.getInfo);
app.router.get('/getUser/:name', controller.base.getUser);
};
const { Controller } = require('egg')
class BaseController extends Controller {
index() {
this.ctx.body = {
data: "hello it's me"
}
}
getInfo () {
const { ctx } = this
console.log('ctx.query', ctx.query)
ctx.body = {
query: ctx.query
}
}
getUser () {
const { ctx } = this
console.log('ctx.params', ctx.params)
ctx.body = {
params: ctx.params
}
}
}
module.exports = BaseController
当我们发起请求,
http://127.0.0.1:7001/getUser/100
,这时通过ctx.params就能拿到参数,{ name: '100' }
2、控制器(Controller)
2.1、再次梳理router和Controller的关系
所有的 Controller 文件都必须放在app/controller
目录下,层级可以是多级。
eggjs就可以根据这个约定,把对应的文件名会转换为驼峰格式。 所以当我们声明路由
router.get('/hello', controller.base.index)
时, controller.base就是对应的app/controller/base.js文件
app/controller/base.js => app.controller.base
module.exports = app => {
const { router, controller } = app;
router.get('/hello', controller.base.index);
app.router.get('/getInfo', controller.base.getInfo);
app.router.get('/getUser/:name', controller.base.getUser);
};
2.2、controller的功能
官方文档有一句话,Controller负责解析用户的输入,处理后返回相应的结果
换句话说, Controller层不处理具体的业务逻辑
我们项目中的controller类继承于 egg.Controller
,
this.ctx
: 当前请求的上下文Context的实例,可以拿到各种便捷属性和方法。this.app
: 当前应用Application的实例,可以拿到全局对象和方法。this.service
:应用定义的Service,可以调用业务逻辑层。this.config
:应用运行时的配置。
2.3、controller的承担的责任
- 获取用户通过 HTTP 传递过来的请求参数。
- 校验、组装参数
- 调用 Service 进行业务处理,处理转换 Service 的返回结果,让它适应用户的需求。
- 通过 HTTP 将结果响应给用户。
2.4、controller调用service方法
同controller的约定一样,service需要在app下创建service文件夹,然后在这个文件夹里面我们创建文件base_info.js(我们验证下eggjs是不是真的会把对应的文件名会转换为驼峰格式。)
在controller调用service下baseInfo的方法
const { Controller } = require('egg')
class BaseController extends Controller {
getUser () {
const { ctx, service } = this
const { name } = ctx.params;
const userInfo = service.baseInfo.getUserInfo({
name
})
ctx.body = userInfo
}
}
module.exports = BaseController
service文件夹下的base_info, eggjs会将该文件挂载到service.baseInfo下
const { Service } = require('egg')
class BaseService extends Service {
getUserInfo({name}) {
const userInfo = {
name: `我的name是${name}`
}
return userInfo
}
}
module.exports = BaseService;
我们执行
http://127.0.0.1:7001/getUser/100
, 得到{ "name": "我的name是100" }
3、服务(Service)
- 可以让Controller中的比较纯粹
- service可以被多个Controller重复调用, 可以使更多的业务逻辑
官网的说法是
Service
不是单例,是 请求级别 的对象,它挂载在Context
上的。
Service
是延迟实例化的,仅在每一次请求中,首次调用到该Service
的时候,才会实例化。
因此,无需担心实例化的性能损耗,经过我们大规模的实践证明,可以忽略不计。
4、config配置
在我们开发的时候,经常会遇到一些配置。比如如果我们对接七牛云的图片上传,我们就需要用到七牛云提供的cdn配置 然后我们重新创建新的router,controller和service,正好复习下前面讲到的东西
4.1、读取config配置
app/config/config.default.js 我们增加一个cdn配置
module.exports = appInfo => {
const config = {}
config.keys = appInfo.name + '_1672833991623_8554';
config.cdn = {
AK: 'test',
SK: 'test',
BucketName: 'xxx-xxx',
DoMain: 'https://xxx.xxx.com'
}
config.security = {
csrf: {
enable: true,
headerName: 'token',
},
};
return {
...config
};
};
app/router.js,新增uploadImg路由,指向到qiniu.js的upload方法
module.exports = app => {
const { router, controller } = app;
router.get('/hello', controller.base.index);
router.get('/getInfo', controller.base.getInfo);
router.get('/getUser/:name', controller.base.getUser);
router.post('/uploadImg', controller.qiniu.upload);
};
app/controller/qiniu.js
const { Controller } = require('egg')
class QiniuController extends Controller {
upload () {
const { ctx, service } = this
const info = service.qiniu.upload()
ctx.body = info
}
}
module.exports = QiniuController
app/service/qiniu.js
const { Service } = require('egg')
class QiniuService extends Service {
upload() {
const { app } = this;
const { cdn } = app.config
return {
data: cdn
}
}
}
module.exports = QiniuService;
在这个例子中,我们就可以看到,app.config可就可以读取到我们的配置文件的信息
4.2、不同环境下的配置
在上面我们读取到了配置文件,但是大家有没有想过,不同环境下的配置文件很可能是不同的
比如开发环境下的cdn和测试环境/生产环境的cdn配置是完全不同的。
如果我们去手动的更改配置文件显然显得有些中二,eggjs为我们提供了多环境配置,也就是在不同的环境下会加载不同的配置文件,我们具体演示下
package.json的脚本配置,通过--env可以指定环境,就会自动加载对应的文件,比如我们改为
egg-bin dev --env=prod
,这时就会加载config.prod.js。这样我们只需要修改环境就可以使用该环境对应的配置了。
config.default.js 为默认的配置文件,所有环境都会加载这个配置文件,一般也会作为开发环境的默认配置文件。
当指定 env 时会同时加载默认配置和对应的配置(具名配置)文件,具名配置和默认配置将合并(使用extend2深拷贝)成最终配置,具名配置项会覆盖默认配置文件的同名配置。如 prod 环境会加载 config.prod.js 和 config.default.js 文件,config.prod.js 会覆盖 config.default.js 的同名配置。
整个配置类似于webpack的配置的merge操作
config
|- config.default.js
|- config.test.js
|- config.prod.js
"scripts": {
"dev": "egg-bin dev --env=prod"
}
本节源码github
tag为v1.1.0
下节预告
下节课开始对接mysql