怎么使用swagger,这里就不说了,本站已经跟各大搜索引擎达成合作,只要你在各大搜索引擎中输入关键词springboot swagger
,就会在第一页返回给你集成教程。
背景
swagger确实很不错,可以自动生成接口文档,省去另外写文档的工作量,但是毕竟自动生成,肯定有不适合我们自己需求的地方。比如所有的接口文档没有分类,放在一起,前端很难找到所需的接口。还有接口文档有更新,没有任何地方提现处理。需要口头通知前端修改,如果前端忘了,后续还会怪后端没有通知到,以及发生各种扯皮。
我这里通过swagger提供的group功能进行增强,对接口文档进行分类、和版本管理。原生提供的group功能需要硬编码,生成Docket,使用起来极其不友好。如下:
代码语言:txt复制@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分支做为版本管理。
定义注解
定义注解,用于在标注接口所属哪个版本。内部枚举,用来定义分支。
代码语言:txt复制@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。
代码语言:txt复制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();
}
}
代码语言:txt复制@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的哪一次提交了。
代码语言:txt复制@ApiVersion(ApiVersion.Version.INTERESTING)
@RequestMapping(value = "interesting")
public void hello(){
...
}