博客 / 詳情

返回

🚀【eggjs實戰10天入門-第2天】🚀—— 項目目錄的作用

課程引導

  1. 🚀【eggjs實戰10天入門-第1天】🚀—— 搭建項目
  2. 🚀【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"

}`

image.png

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可就可以讀取到我們的配置文件的信息

image.png

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
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.