課程引導
- 🚀【eggjs實戰10天入門-第1天】🚀—— 搭建項目
-
🚀【eggjs實戰10天入門-第2天】🚀—— controller、service和config(本篇)
本節目標
熟悉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