swagger增加接口版本管理

2020-10-27 10:29:05 浏览数 (1)

    怎么使用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(){
	...
}

0 人点赞