swagger增加接口版本管理
时间:2022-07-28
本文章向大家介绍swagger增加接口版本管理,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
怎么使用swagger,这里就不说了,本站已经跟各大搜索引擎达成合作,只要你在各大搜索引擎中输入关键词springboot swagger
,就会在第一页返回给你集成教程。
背景
swagger确实很不错,可以自动生成接口文档,省去另外写文档的工作量,但是毕竟自动生成,肯定有不适合我们自己需求的地方。比如所有的接口文档没有分类,放在一起,前端很难找到所需的接口。还有接口文档有更新,没有任何地方提现处理。需要口头通知前端修改,如果前端忘了,后续还会怪后端没有通知到,以及发生各种扯皮。
我这里通过swagger提供的group功能进行增强,对接口文档进行分类、和版本管理。原生提供的group功能需要硬编码,生成Docket,使用起来极其不友好。如下:
@Bean
public Docket app_api() {
return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.any())
.paths(PathSelectors.ant("/api/**")).build().groupName("APP接口文档V4.4").pathMapping("/")
.apiInfo(apiInfo("APP接口文档V4.4及之前","文档中可以查询及测试接口调用参数和结果","4.4"));
}
@Bean
public Docket wap_api() {
return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.any())
.paths(PathSelectors.ant("/web/**")).build().groupName("WEB接口文档V4.4").pathMapping("/")
.apiInfo(apiInfo("WEB接口文档V4.4及之前","文档中可以查询及测试接口调用参数和结果","4.4"));
}
解决方案
本篇记录的是,swagger自动生成group,实现对接口版本管理。这里我们公司习惯使用git分支进行管理,所有接口文档也跟着git分支做为版本管理。
定义注解
定义注解,用于在标注接口所属哪个版本。内部枚举,用来定义分支。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ApiVersion {
Version[] value();
enum Version {
MASTER("master"),
INTERESTING("20190701intersting");
private String display;
Version(String display) {
this.display = display;
}
public String getDisplay() {
return display;
}
}
}
重写group生成规则
这里代码看似又臭又长,其实不然,就是找到group生成的入口,然后遍历我们自定义的注解,生成多个group。
public class SwaggerPluginRegistry extends OrderAwarePluginRegistry<DocumentationPlugin, DocumentationType> implements PluginRegistry<DocumentationPlugin, DocumentationType> {
protected SwaggerPluginRegistry(List<Docket> plugins, Comparator<? super DocumentationPlugin> comparator) {
super(plugins, comparator);
}
@Override
public List<DocumentationPlugin> getPlugins() {
return super.getPlugins();
}
}
@Component
@Primary
@ConditionalOnProperty(prefix = "swagger", value = {"enable"}, havingValue = "true")
@EnableSwagger2
public class SwaggerDocumentationPluginsManager extends DocumentationPluginsManager {
@Override
public Iterable<DocumentationPlugin> documentationPlugins() throws IllegalStateException {
List<DocumentationPlugin> plugins = registry().getPlugins();
ensureNoDuplicateGroups(plugins);
if (plugins.isEmpty()) {
return newArrayList(defaultDocumentationPlugin());
}
return plugins;
}
private void ensureNoDuplicateGroups(List<DocumentationPlugin> allPlugins) throws IllegalStateException {
Multimap<String, DocumentationPlugin> plugins = Multimaps.index(allPlugins, byGroupName());
Iterable<String> duplicateGroups = from(plugins.asMap().entrySet()).filter(duplicates()).transform(toGroupNames());
if (Iterables.size(duplicateGroups) > 0) {
throw new IllegalStateException(String.format("Multiple Dockets with the same group name are not supported. "
+ "The following duplicate groups were discovered. %s", Joiner.on(',').join(duplicateGroups)));
}
}
private Function<? super DocumentationPlugin, String> byGroupName() {
return new Function<DocumentationPlugin, String>() {
@Override
public String apply(DocumentationPlugin input) {
return Optional.fromNullable(input.getGroupName()).or("default");
}
};
}
private Function<? super Map.Entry<String, Collection<DocumentationPlugin>>, String> toGroupNames() {
return new Function<Map.Entry<String, Collection<DocumentationPlugin>>, String>() {
@Override
public String apply(Map.Entry<String, Collection<DocumentationPlugin>> input) {
return input.getKey();
}
};
}
private static Predicate<? super Map.Entry<String, Collection<DocumentationPlugin>>> duplicates() {
return new Predicate<Map.Entry<String, Collection<DocumentationPlugin>>>() {
@Override
public boolean apply(Map.Entry<String, Collection<DocumentationPlugin>> input) {
return input.getValue().size() > 1;
}
};
}
private DocumentationPlugin defaultDocumentationPlugin() {
return new Docket(DocumentationType.SWAGGER_2);
}
private SwaggerPluginRegistry registry() {
List<Docket> list = new ArrayList<>();
for (ApiVersion.Version version : ApiVersion.Version.values()) {
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.groupName(version.getDisplay())
.select()
.apis(input -> {
if (ApiVersion.Version.MASTER.equals(version)) {
return true;
}
ApiVersion apiVersion = input.getHandlerMethod().getMethodAnnotation(ApiVersion.class);
if (apiVersion != null && Arrays.asList(apiVersion.value()).contains(version)) {
return true;
}
return false;
})
.paths(PathSelectors.any())
.build()
.securitySchemes(securitySchemes())
.securityContexts(securityContexts());
list.add(docket);
}
SwaggerPluginRegistry registry = new SwaggerPluginRegistry(list, new AnnotationAwareOrderComparator());
return registry;
}
private ApiInfo apiInfo() {
Contact contact = new Contact("技术部", "", "");
return new ApiInfoBuilder()
.title("ofcoder接口文档")
.description("ofcoder接口文档")
.version("1.0.0")
.contact(contact)
.build();
}
private List<ApiKey> securitySchemes() {
List<ApiKey> arrayList = new ArrayList<>();
arrayList.add(new ApiKey("Authorization", "token", "header"));
return arrayList;
}
private List<SecurityContext> securityContexts() {
List<SecurityContext> arrayList = new ArrayList<>();
arrayList.add(SecurityContext.builder()
.securityReferences(defaultAuth())
.build());
return arrayList;
}
private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
List<SecurityReference> arrayList = new ArrayList<>();
arrayList.add(new SecurityReference("Authorization", authorizationScopes));
return arrayList;
}
}
使用
只需要对所要进行管理的接口上,增加该注解,value的值支持多个,也就是说你可以同时标注多个分支。我觉某个接口每修改一次,value的值则增加一个分支,方便后续追述在那些分支上做了修改,就可以定位到git的哪一次提交了。
@ApiVersion(ApiVersion.Version.INTERESTING)
@RequestMapping(value = "interesting")
public void hello(){
...
}
- android viewgroup事件分发机制
- Rafy 领域实体框架示例(1) - 转换传统三层应用程序
- android view事件分发机制
- Rafy 领域实体框架演示(2) - 新功能展示
- ormlite介绍一
- 从Encoder到Decoder实现Seq2Seq模型(算法+代码)
- 【Java SE】Java NIO系列教程(六) Selector
- Rafy 领域实体框架演示(3) - 快速使用 C/S 架构部署
- Rafy 领域实体框架演示(4) - 使用本地文件型数据库 SQLCE 绿色部署
- spring 的OpenSessionInViewFilter简介
- Android TextView中文字通过SpannableString来设置超链接、颜色、字体等属性
- 【Java SE】Java NIO系列教程(三) Buffer
- android混淆
- 一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](三)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Nginx | Nginx增加模块
- springboot之整合druid并配置数据源监控
- c++之程序流程控制
- linux使用 source etc/profile
- c++之数组
- c++之指针
- c++之函数
- kafka的生产者分区机制原理(二)
- (17)Bash别名与快捷键
- (16)Bash历史命令与补全
- 【python-leetcode325-滑动窗口法】最大子数组之和为k
- 爬取51job出现can only concatenate str (not “NoneType“) to str
- springboot之基于注解整合mybatis
- springboot之基于配置文件整合mybatis
- springboot之整合JPA