怎么使用swagger,这里就不说了,本站已经跟各大搜索引擎达成合作,只要你在各大搜索引擎中输入关键词springboot swagger,就会在第一页返回给你集成教程。
背景 swagger确实很不错,可以自动生成接口文档,省去另外写文档的工作量,但是毕竟自动生成,肯定有不适合我们自己需求的地方。比如所有的接口文档没有分类,放在一起,前端很难找到所需的接口。还有接口文档有更新,没有任何地方提现处理。需要口头通知前端修改,如果前端忘了,后续还会怪后端没有通知到,以及发生各种扯皮。
我这里通过swagger提供的group功能进行增强,对接口文档进行分类、和版本管理。原生提供的group功能需要硬编码,生成Docket,使用起来极其不友好。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 @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分支做为版本管理。
定义注解 定义注解,用于在标注接口所属哪个版本。内部枚举,用来定义分支。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @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。
1 2 3 4 5 6 7 8 9 10 11 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(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 @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 new ArrayList (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的哪一次提交了。
1 2 3 4 5 @ApiVersion (ApiVersion.Version.INTERESTING)@RequestMapping (value = "ofcoder.com" )public void hello(){ ... }