🚀【Nodejs之eggjs实战】🚀—— controller、service和config


highlight: a11y-dark
theme: fancy

本节目标

熟悉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的承担的责任

  1. 获取用户通过 HTTP 传递过来的请求参数。
  2. 校验、组装参数
  3. 调用 Service 进行业务处理,处理转换 Service 的返回结果,让它适应用户的需求。
  4. 通过 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

© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容