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

egg-controller 是集合了一些在 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,省去手动判断、转换的逻辑。

参数校验

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

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 默认生成的元数据里,获取不到类型的具体字段属性信息,而且前端直接引入复用后端的类型定义也比较麻烦。所以,想要在定义类型的同时,复用类型的定义,只能在编译时做工作,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 作为请求的起点,这只是个开始。

egg 框架

egg-controller 详细文档

Egg 中 Controller 最佳实践的更多相关文章

  1. Spring Batch在大型企业中的最佳实践

    在大型企业中,由于业务复杂.数据量大.数据格式不同.数据交互格式繁杂,并非所有的操作都能通过交互界面进行处理.而有一些操作需要定期读取大批量的数据,然后进行一系列的后续处理.这样的过程就是" ...

  2. Require.js中的路径在IDEA中的最佳实践

    本文主要讲述require.js在IDEA中路径智能感知的办法和探索中遇到的问题. 测试使用的目录结构:一种典型的thinkphp 6的目录结构,如下图. 现在我通过在 vue-a.js 中运用不同的 ...

  3. C#异步编程中的最佳实践(做法)

    原文地址Stephen Cleary 写得很详细,尤其讲到了 GUI 上下文调用,在APS.NET中它会阻塞 GUI 线程,从而导致死锁.而控制台中却不存在这个问题. 比如开发过程中本地写控制台程序测 ...

  4. 【活动】美团技术沙龙第49期:AI在外卖场景中的最佳实践

    美团技术沙龙第49期开始啦! 本次沙龙,美团外卖技术部专家会深入介绍AI在对话系统.图像处理.个性化推荐.智能营销等方向在外卖业务中的实践,希望与业界技术同学一起交流学习. 无论你从事智能搜索,或是算 ...

  5. Git 在团队中的最佳实践--如何正确使用Git Flow

    我们已经从SVN 切换到Git很多年了,现在几乎所有的项目都在使用Github管理, 本篇文章讲一下为什么使用Git, 以及如何在团队中正确使用. Git的优点 Git的优点很多,但是这里只列出我认为 ...

  6. Android应用中MVP最佳实践

    转自:http://www.jianshu.com/p/ed2aa9546c2c 文/Jude95(简书作者)原文链接:http://www.jianshu.com/p/ed2aa9546c2c著作权 ...

  7. GIT在团队中的最佳实践

    我们已经从SVN 切换到Git很多年了,现在几乎所有的项目都在使用Github管理, 本篇文章讲一下为什么使用Git, 以及如何在团队中正确使用. Git的优点 Git的优点很多,但是这里只列出我认为 ...

  8. 一文搞懂 Python 的模块和包,在实战中的最佳实践

    最近公司有个项目,我需要写个小爬虫,将爬取到的数据进行统计分析.首先确定用 Python 写,其次不想用 Scrapy,因为要爬取的数据量和频率都不高,没必要上爬虫框架.于是,就自己搭了一个项目,通过 ...

  9. Git 在团队中的最佳实践--如何正确使用Git Flow[转]

    原文地址:http://www.cnblogs.com/cnblogsfans/p/5075073.html Git的优点 Git的优点很多,但是这里只列出我认为非常突出的几点. 由于是分布式,所有本 ...

随机推荐

  1. java的方法重写 ,多态和关键字 instanceof和final

    package cn.pen; /*final 是一个java的关键字,用于修饰局部变量.属性.方法.类,表示最终的意思. final修饰类表示最终类,无法被继承.public final class ...

  2. MapReduce词频统计

    自定义Mapper实现 import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; impor ...

  3. 使用javaScript操作页面元素

    from selenium import webdriver import time import unittest from selenium.common.exceptions import We ...

  4. SQLAlchemy使用介绍

    SQLAlchemy is the Python SQL toolkit and Object Relational Mapper that gives application developers ...

  5. js 去掉数组对象中的重复对象

    export function deteleObject(obj) { var uniques = []; var stringify = {}; for (var i = 0; i < obj ...

  6. 使用 Vscode +PlantUml 画uml图

    什么是PlantUML PlantUML是一个快速创建UML图形的组件,官网上之所以称它是一个组件,主要是因为多数情况下我们都是在Eclipse.NetBenas.Intellijidea. Emac ...

  7. XMLHttpRequest中常用的方法

    a.) abort() 停止当前请求b.) getAllResponseHeaders() 把HTTP请求的所有响应首部作为键值对返回c.) open("method", &quo ...

  8. DDD - 概述 - 模块 (二)

    首先,你必须知道以下DDD构建块: Entities - 实体 Value objects - 值对象 Aggregate roots - 聚合跟 Repositories - 仓储对象 Factor ...

  9. dbus-launch

    NAME dbus-launch - Utility to start a message bus from a shell script dbus-launch - 从shell脚本启动一个消息总线 ...

  10. SpringBoot热部署

    SpringBoot的热部署方式分为两种  1.SpringLoader 方式一:以maven插件方式使用SpringLoader <!-- Springloader插件 --> 1.1 ...