天穹-Api接口自动化管理系列2:MiApi- 多协议接口扫描器详解

本文主要是介绍天穹-Api接口自动化管理系列2:MiApi- 多协议接口扫描器详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

开源地址

https://github.com/XiaoMi/mone/tree/master/miapi-all/mi-api 欢迎对云原生技术/研发效能感兴趣的小伙伴加入(Fork、Star)我们。

概述

在上一篇对平台的整体介绍中我们介绍了平台提供的核心能力与流程,并且在服务接口信息的加载生成中大致描述了我们获取生成接口数据信息的主要逻辑。本文将深入到源码层面为大家介绍我们是如何实现接口文档数据的扫描解析与生成的。

业务依赖包

在前文中我们提到了获取业务项目数据的关键组件/模块,即注解、扫描器、缓存

为了获取上述我们所需的业务项目接口的基本数据,我们需要业务方引入我们提供的一套依赖包,针对不同协议接口的服务我们提供了针对性的依赖包,例如对提供 Http 接口、Dubbo接口的项目,我们分别设计提供了适配 Http、适配 Dubbo 的依赖包。这些包将用于不同协议接口数据的解析、数据推送、服务注册等等。

注解定义
作用

注解用于在项目中做一些特定的标记,例如模块层标记、接口层标记、字段层标记等,用于后续的扫描器扫描解析过程。

实现

在设计之初我们首先基于我们定义的接口交付流程,明确我们需要哪些数据。我们希望业务项目在运行之后,研发人员即可在平台中搜索自身项目模块的信息,并根据选择的模块信息加载生成相应的接口数据。这里的模块我们希望是研发人员习惯的、在代码中明确定义的类。

例如对于一个Http接口,基于传统的 mvc 架构,通常该接口入口将实现于 xxxController 类下,例如:

@RestController
@RequestMapping()
public class HelloController {@RequestMapping(path = "/hello",method = RequestMethod.POST)
public Response<String> getToken() {Response<String> r = new Response<>();r.setData("hello");return r;
}
}

那么我们希望用户可以在搜索框直接搜索关键字 HelloController,即可看到该项目下所有模块、接口信息,并针对性选择加载生成文档。

而对于一个Dubbo接口如下:

public interface DubboHealthService {Result<Health> health2(AaReq aaReq);
}@DubboService(timeout = 1000, group = "staging",version = "1.0")
@Slf4j
public class DubboHealthServiceImpl implements DubboHealthService {@Overridepublic Result<Health> health(AaReq aaReq) {Health health = new Health();health.setVersion("1.0");return Result.success(health);}

我们希望可搜索的模块为接口定义,即 DubboHealthService ,用户只需要搜索该接口,即可获取到该项目下所有 interface及其方法列表,并基于这两者进行筛选加载生成文档。

综上针对我们需要获取的接口数据,在业务依赖包中我们提供了几项基础注解:@EnableApiDocs、@ApiModule、@ApiDoc、@ParamDefine。(对于不同协议接口,注解命名上略有不同)

  • @EnableApiDocs

  • @EnableApiDocs用于启动类 Bootstrap上,用作开关,用户可以根据填不添加该开关,决定是否启用数据扫描推送功能。

@EnableAutoConfiguration
@ComponentScan(basePackages = {"com.xxx.xxx.hello", "com.xxx.xxx"})
@DubboComponentScan(basePackages = "com.xxx.xxx.hello")
@ServletComponentScan
@EnableDubboApiDocspublic class HelloBootstrap {private static final Logger logger = LoggerFactory.getLogger(HelloBootstrap.class);public static void main(String... args) {try {SpringApplication.run(HelloBootstrap.class, args);} catch (Throwable throwable) {logger.error(throwable.getMessage(), throwable);System.exit(-1);}}
}

例如,如上一个提供 dubbo 接口的项目只需要在启动类上添加 @EnableDubboApiDocs 注解,即可启用该功能。

这个启动类注解的实现也很简单:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Inherited
@Import({DubboApiDocsAnnotationScanner.class})
public @interface EnableDubboApiDocs {
}

实际上我们只是在这个注解中 @Import 入依赖包中的扫描器类,那么只要添加该注解,spring便会帮我们把扫描器类初始化进容器中,后续的行为都将由扫描器执行。

  • @ApiModule

  • 该注解用于标注模块类,其实现如下:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface ApiModule {/*** 用于自定义模块名*/    String value();/*** 用于定位模块类的类型* dubbo api interface class* 若为http接口,则该选项为 apiController*/    Class<?> apiInterface();}

该注解将在扫描器的扫描过程中用于从spring容器中筛选需要解析的类信息,同时也提供关于这些类的基本信息。

对于Http接口通常用法如下:

@RestController
@RequestMapping()
@HttpApiModule(value = "这是一个controller类HelloController", apiController = HelloController.class)
public class HelloController {
}

对于Dubbo接口通常用法如下:

@DubboService(timeout = 1000, group = "staging",version = "1.0")
@Slf4j
@ApiModule(value = "健康检测服务", apiInterface = DubboHealthService.class)public class DubboHealthServiceImpl implements DubboHealthService {
}
  • @ApiDoc

  • 该注解用于标注具体需要生成文档的接口,实现如下:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface ApiDoc {/*** api name.用于自定义接口名*/    String value();/*** api description.自定义接口描述文档*/    String description() default "";}

通常用法如下:

@Override
@ApiDoc(value = "健康监测方法",description = "这是一个用于健康监测的方法")
public Result<Health> health(AaReq aaReq) {Health health = new Health();health.setVersion("1.0");return Result.success(health);
}
  • @ParamDefine

  • 该注解用于具体参数字段的定义,实现如下:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Documented
@Inherited
public @interface ParamDefine {/*** 参数名*/String value();/*** 该参数是否必填*/boolean required() default false;/*** 是否忽略该字段,若是,在生成文档时该字段将被忽略*/boolean ignore() default false;/*** 字段说明描述*/String description() default "";/*** 默认值,若有默认值,将以该值生成mock数据*/String defaultValue() default "";}

通常用法如下:

@Data
public class AaReq implements Serializable {@ApiDocClassDefine(value = "用户名a",required = true,description = "这里是用户名参数",defaultValue = "dongzhenixng")private String username;@ApiDocClassDefine(value ="年龄",required = false,description = "用户年龄",defaultValue = "23")private Integer age;/**
* 也可以不使用该字段,平台将默认提取该字段基本信息,如参数名、类型等*/    private double age21;}
扫描器
作用

扫描器(scanner)为本项目的核心,我们基于jdk的反射能力,在运行时获取项目的服务、接口信息数据。

实现

扫描器由 @EnableApiDocs 开关导入 spring容器,这里由于我们需要基于 spring 容器初始化后的bean数据作为解析目标,因此扫描解析的动作必须发生在spring完成基本初始化操作后,因此这里实现了 spring 开放的 ApplicationListener 接口,该接口能够接收 spring 的项目触发的一系列事件。这里我们接收 ApplicationReadyEvent,即在 spring 框架初始化完成项目基本信息后触发。

public class ApiDocsScanner implements ApplicationListener<ApplicationReadyEvent> {@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {//扫描解析逻辑......
}
}

spring 初始化完成后,项目的 bean 信息等都在spring的上下文 ApplicationContext 中维护,那么在扫描器中即可从中获取标识了 @ApiModule 的模块集合,之后通过反射逐一获取标识了该注解的某块类信息,构造模块数据结构,再通过反射获取每个模块下的方法列表,筛选带有 @ApiDoc 注解的方法,构造接口方法层的数据结构,再对标识了该注解的方法进行进一步处理。

在方法层级的处理中,同样使用反射的方式获取具体参数字段信息,这里根据字段的类型递归构造字段级的数据结构。当然,这里的解析操作较为复杂繁琐,我们需要区分针对不同的参数类型做针对性的解析,例如对于基本类型的参数怎么处理?对于对象object、List、Map、Set、Queue甚至是嵌套的泛型参数怎么处理?哪些类型不能循环递归?哪些需要针对性忽略......这些细节我们在开发以及测试的过程中做了大量的斟酌与兼容处理,这里我们不做详细介绍,感兴趣的朋友可以翻看我们开源的代码。

完成以上几个步骤的数据扫描与解析后,根据一定的规则聚合所有数据,再获取项目运行时本地 ip及使用的端口,调用平台提供的开放接口,将以上数据统一推送到平台,平台将以 ip:port 为唯一索引将数据存入平台数据库中。这里之所以使用 ip:port 作为唯一索引,是由于一般微服务业务项目不管是在开发过程中或者发布到测试环境、生成环境,它们大概率都将拥有多个实例,即同一个项目在多方、多处运行,不同实例的代码版本、开发进度可能不完全一致,因此我们希望用户在平台上可以选择指定的实例,针对性加载生成该实例的接口数据。

扫描器执行流程图

这篇关于天穹-Api接口自动化管理系列2:MiApi- 多协议接口扫描器详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/713789

相关文章

PHP轻松处理千万行数据的方法详解

《PHP轻松处理千万行数据的方法详解》说到处理大数据集,PHP通常不是第一个想到的语言,但如果你曾经需要处理数百万行数据而不让服务器崩溃或内存耗尽,你就会知道PHP用对了工具有多强大,下面小编就... 目录问题的本质php 中的数据流处理:为什么必不可少生成器:内存高效的迭代方式流量控制:避免系统过载一次性

MySQL的JDBC编程详解

《MySQL的JDBC编程详解》:本文主要介绍MySQL的JDBC编程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、前置知识1. 引入依赖2. 认识 url二、JDBC 操作流程1. JDBC 的写操作2. JDBC 的读操作总结前言本文介绍了mysq

Redis 的 SUBSCRIBE命令详解

《Redis的SUBSCRIBE命令详解》Redis的SUBSCRIBE命令用于订阅一个或多个频道,以便接收发送到这些频道的消息,本文给大家介绍Redis的SUBSCRIBE命令,感兴趣的朋友跟随... 目录基本语法工作原理示例消息格式相关命令python 示例Redis 的 SUBSCRIBE 命令用于订

使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解

《使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解》本文详细介绍了如何使用Python通过ncmdump工具批量将.ncm音频转换为.mp3的步骤,包括安装、配置ffmpeg环... 目录1. 前言2. 安装 ncmdump3. 实现 .ncm 转 .mp34. 执行过程5. 执行结

Python中 try / except / else / finally 异常处理方法详解

《Python中try/except/else/finally异常处理方法详解》:本文主要介绍Python中try/except/else/finally异常处理方法的相关资料,涵... 目录1. 基本结构2. 各部分的作用tryexceptelsefinally3. 执行流程总结4. 常见用法(1)多个e

PHP应用中处理限流和API节流的最佳实践

《PHP应用中处理限流和API节流的最佳实践》限流和API节流对于确保Web应用程序的可靠性、安全性和可扩展性至关重要,本文将详细介绍PHP应用中处理限流和API节流的最佳实践,下面就来和小编一起学习... 目录限流的重要性在 php 中实施限流的最佳实践使用集中式存储进行状态管理(如 Redis)采用滑动

SpringBoot实现不同接口指定上传文件大小的具体步骤

《SpringBoot实现不同接口指定上传文件大小的具体步骤》:本文主要介绍在SpringBoot中通过自定义注解、AOP拦截和配置文件实现不同接口上传文件大小限制的方法,强调需设置全局阈值远大于... 目录一  springboot实现不同接口指定文件大小1.1 思路说明1.2 工程启动说明二 具体实施2

SpringBoot日志级别与日志分组详解

《SpringBoot日志级别与日志分组详解》文章介绍了日志级别(ALL至OFF)及其作用,说明SpringBoot默认日志级别为INFO,可通过application.properties调整全局或... 目录日志级别1、级别内容2、调整日志级别调整默认日志级别调整指定类的日志级别项目开发过程中,利用日志

Java中的抽象类与abstract 关键字使用详解

《Java中的抽象类与abstract关键字使用详解》:本文主要介绍Java中的抽象类与abstract关键字使用详解,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、抽象类的概念二、使用 abstract2.1 修饰类 => 抽象类2.2 修饰方法 => 抽象方法,没有

SpringBoot 多环境开发实战(从配置、管理与控制)

《SpringBoot多环境开发实战(从配置、管理与控制)》本文详解SpringBoot多环境配置,涵盖单文件YAML、多文件模式、MavenProfile分组及激活策略,通过优先级控制灵活切换环境... 目录一、多环境开发基础(单文件 YAML 版)(一)配置原理与优势(二)实操示例二、多环境开发多文件版