当前位置: 移动技术网 > IT编程>开发语言>JavaScript > Egg 中 Controller 最佳实践

Egg 中 Controller 最佳实践

2018年08月21日  | 移动技术网IT编程  | 我要评论

得益于 javascript 加入的 decorator 特性,可以使我们跟 java/c# 一样,更加直观自然的,做面向切面编程。而随着 typescript 的成熟,类型系统也让我们增强了信心,面对复杂的业务逻辑,也更有底气。

是集合了一些在 controller 层开发中常见问题解决方案的插件。

controller 路由定义

export class homecontroller {
  @route('/api/xxx', { name: '获取xxx数据' })
  async getxxx(size: number, page: number) {
    return 'homeindex';
  }
}

可以看到,使用 decorator 的形式来声明 controller 非常直观,而且方便扩展,添加/修改 controller 规则直接修改 decorator 的类型定义就好。这种形式也是 java/c# 的常规操作。

这里的改进除了使用 decorator 替代了 router.js 来进行 controller 声明以外,还添加了出入参支持,省去了需要手动读写 ctx 的过程,非常直观的声明 controller 函数的参数需求,以及返回数据类型。

基于 decorator 的写法与之前最大的区别是,在 controller 这个横切面,之前只有 loader 可以掌控,而现在可以在 decorator 中加以集中控制,再结合 typescript 的元信息,可以做出很多扩展,比如:

参数格式化

在 eggjs 中,因为没有类型信息的原因,从 params 和 query 中获取的信息都会是字符串类型,都需要在 controller 中手动转换。而改造之后的写法,参数直接暴露在函数入参里,我们就可以直接拿写在入参的类型定义作为格式化的依据,根据类型尝试转换,保证参数类型正确,可以初步防止类型不符的参数进入到 controller,省去手动判断、转换的逻辑。

参数校验

参数格式化只能保证参数的类型一致性,而我们的需求不止这些,比如必选参数为空时需要拦截,有时参数是复杂对象为了防止恶意构造数据,需要对数据格式做深度检测,所以这里引入了参数校验库,,通过它来解决复杂的校验问题。

export class homecontroller {
  @route('/api/xxx', { name: '获取xxx数据', validatemetainfo: [{
    name: 'data',
    rule: {
      type: 'object',
      str: { type: 'string', max: 20 },
      count: { type: 'number', max: 10, required: false },
    },
   }] })
  async getxxx(data: { str: string, count?: number }>) {
    return data.str;
  }
}

这里有个问题,在类型是复杂类型时,typescript 默认生成的元数据里,类型一律为object,所以,想要在定义类型的同时,复用类型的定义,只能在编译时做工作,typescript 也开放出了编译时插件api,在不用编译时插件的情况下,就需要单独写一份规则的数据。

有插件后:

export class homecontroller {
  @route('/api/xxx', { name: '获取xxx数据' })
  async getxxx(data: basevalidaterule<{
    type: 'object',
    rule: {
      str: { type: 'string', max: 20 },
      count: { type: 'number', max: 10, required: false },
    },
  }>) {
    return data.str;
  }
}

路由级中间件

函数类型跟 egg 定义稍有不同:

(app: application, typeinfo: routetype) => (ctx: any, next: any) => any

egg 已经定义了中间件,为什么在路由上还定义一个?在路由定义的中间件跟全局的中间件区别在于范围,全局中间件更适合大规模的统一处理,用来统一处理特定业务功能接口就大材小用了,还需要设置过滤逻辑,甚至需要在 config 中设置黑白名单。而路由级中间件适合只有部分接口需要的统一处理,配合从 @route 上收集的类型信息处理更佳。

api文档 & 前端sdk

既然已经收集到了那么多元数据,根据这些数据生成api文档就很简单了无非就是前端的展示,也可以把数据转换对接其他的api文档平台。

更近一步,直接生成前端调用sdk?当然没问题。本插件支持通过模板生成,如果没有找到模板,会在sdk生成目录生成默认模板。

// controller
export class metacontroller {

  @route({ url: '/meta/index', name: '首页' })
  async index(id: string, n: number, e: 'enuma' | 'enumb', d: date) {
    return 'metaindex';
  }

}

// 生成代码
export class metaservice extends base {

  /** 首页  */
  async index(id: string, n: number, e: string, d: date) {
    const __data = { id, n, e, d };
    return await this.request({
      method: `get`,
      url: `/meta/index`,
      data: __data,
    });
  }

}

export const metaservice = new metaservice();
export default new metaservice();

开发时

在配置中开启即可,根据需要自定义其他配置。当 controller 中文件修改时,会同时重新生成对应的前端sdk文件。

构建打包时

在 前端打包流程前 可以使用 egg-controller gensdk 命令生成前端sdk,需要注意,如果为 typescript 项目,需要先将 typescript 编译,然后执行生成命令。

controller 作为请求的起点,这只是个开始。

详细文档

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网