首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >swagger增加接口版本管理

swagger增加接口版本管理

原创
作者头像
并发笔记
修改2020-10-27 10:29:05
2.3K0
修改2020-10-27 10:29:05
举报
文章被收录于专栏:并发笔记并发笔记

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 解决方案
    • 定义注解
      • 重写group生成规则
      • 使用
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档