Java Swagger实现前端不同版本api显示、切换,后端自定义多包扫描

由于开发迭代导致api接口越来越多,前端对接时,或者想查看某个版本接口时带来很大麻烦,因此有了实现swagger-ui前端界面能手动选择要显示特定版本的想法。话不多说,先看效果(原理就是利用swagger的分组功能):

前端版本切换展示

一、实现步骤
(一). Controller层ApiOperation注解中接入特定标示版本的信息,比如我的:
    @ApiOperation(value = "上传文件@(v1.0.0)", notes = "上传文件")
    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public Result<Object> getUpload(@NotNull(message = "上传文件不能为空") @RequestParam(value = "file") MultipartFile file) {
        return null;
    }

其中@(v1.0.0)就是版本标记,如果出现版本迭代可以用逗号隔开,比如:@(v1.0.0,v1.1.0),这就标记了改api出现过迭代,在v1.1.0中出现过更改

(二). 修改swagger配置使其能解析自己定义的版本信息:
  1. 现在就默认组,即全部api
    @Bean
    public Docket allGroup() {
        Set<String> setProtocols = new HashSet<>(Arrays.asList(protocols));
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo("all"))
                .groupName("all")
                .select()
                .apis(basePackage(BASE_PACKAGES))
                .apis(input -> matchVersion(input, null))
                .paths(PathSelectors.any())
                .build()
                .protocols(setProtocols)
                .securitySchemes(this.securitySchemes())
                .securityContexts(this.securityContexts());
    }
  1. 加载特定版本分组,这里我们先解析所有可能的版本标记(通过ApplicationContext获取Controller层ApiOperation的value),在通过DefaultListableBeanFactory动态批量创建不同版本的Docket bean,具体代码如下:
    @Bean
    public void otherGroups() {
        Map<String, String> versions = new HashMap<>(4);
        Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {
            HandlerMethod handlerMethod = infoEntry.getValue();
            ApiOperation apiOperation = handlerMethod.getMethodAnnotation(ApiOperation.class);
            if (Objects.nonNull(apiOperation)) {
                String value = apiOperation.value();
                if (StringUtils.isNotBlank(apiOperation.value())) {
                    String[] versionInfos = getVersions(value);
                    if (ArrayUtils.getLength(versionInfos) > 0) {
                        for (String versionInfo : versionInfos) {
                            versions.put(versionInfo.replace(".", ""), versionInfo);
                        }
                    }
                }
            }
        }
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
        for (Map.Entry<String, String> version : versions.entrySet()) {
            Docket docket = createDocket(version.getValue());
            defaultListableBeanFactory.registerSingleton(version.getKey(), docket);
        }
    }
  1. 解析api部分代码,利用正则获取@()括号中的值(为啥要价格@符号了,是为了避免与有时value直接有括号说明但不是版本的问题)
    private static String[] getVersions(String version) {
        String reg = "(.)*(@[\\((].*[\\))])(.)*";
        if (version.matches(reg)) {
            version = version.replaceAll(reg, "$2");
            reg = "(@[\\((])(.*)([\\))])";
            version = version.replaceAll(reg, "$2");
            return version.split("[,,]");
        }
        return new String[]{};
    }
二、完整代码
package com.example.swagger.config;


import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.*;

/**
 * @author zxiaozhou
 * @date 2020-07-06 18:00
 * @since JDK1.8
 */
@Slf4j
@Configuration
@EnableSwagger2
@RequiredArgsConstructor
public class Swagger2Config implements WebMvcConfigurer {
    /**
     * 指定要扫描的路径
     */
    private final static String[] BASE_PACKAGES = new String[]{"com.example.swagger"};
    private final ApplicationContext applicationContext;
    /**
     * 支持协议
     */
    private final static String[] protocols = new String[]{"http"};
    /**
     * 请求头token键
     */
    private final static String ACCESS_TOKEN = "Authorization";


    /**
     * 所有api分组信息
     *
     * @return Docket ${@link Docket}
     * @author zxiaozhou
     * @date 2020-07-06 18:52
     */
    @Bean
    public Docket allGroup() {
        Set<String> setProtocols = new HashSet<>(Arrays.asList(protocols));
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo("all"))
                .groupName("all")
                .select()
                .apis(basePackage(BASE_PACKAGES))
                .apis(input -> matchVersion(input, null))
                .paths(PathSelectors.any())
                .build()
                .protocols(setProtocols)
                .securitySchemes(this.securitySchemes())
                .securityContexts(this.securityContexts());
    }


    /**
     * 其他api分组信息
     *
     * @author zxiaozhou
     * @date 2020-07-06 18:52
     */
    @Bean
    public void otherGroups() {
        Map<String, String> versions = new HashMap<>(4);
        Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {
            HandlerMethod handlerMethod = infoEntry.getValue();
            ApiOperation apiOperation = handlerMethod.getMethodAnnotation(ApiOperation.class);
            if (Objects.nonNull(apiOperation)) {
                String value = apiOperation.value();
                if (StringUtils.isNotBlank(apiOperation.value())) {
                    String[] versionInfos = getVersions(value);
                    if (ArrayUtils.getLength(versionInfos) > 0) {
                        for (String versionInfo : versionInfos) {
                            versions.put(versionInfo.replace(".", ""), versionInfo);
                        }
                    }
                }
            }
        }
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
        for (Map.Entry<String, String> version : versions.entrySet()) {
            Docket docket = createDocket(version.getValue());
            defaultListableBeanFactory.registerSingleton(version.getKey(), docket);
        }
    }


    /**
     * 重写basePackage方法,使能够实现自定义多包扫描
     *
     * @param basePackages ${@link String[]}
     * @return Predicate<RequestHandler> ${@link Predicate<RequestHandler>}
     * @author zxiaozhou
     * @date 2020-07-06 18:01
     */
    public static Predicate<RequestHandler> basePackage(final String[] basePackages) {
        return input -> declaringClass(input).transform(handlerPackage(basePackages)).or(true);
    }


    private static Function<Class<?>, Boolean> handlerPackage(final String[] basePackages) {
        return input -> {
            for (String basePackage : basePackages) {
                boolean isMatch = input.getPackage().getName().startsWith(basePackage);
                if (isMatch) {
                    return true;
                }
            }
            return false;
        };
    }


    private static Optional<? extends Class<?>> declaringClass(RequestHandler input) {
        return Optional.fromNullable(input.declaringClass());
    }


    private List<ApiKey> securitySchemes() {
        List<ApiKey> list = new ArrayList<>();
        list.add(new ApiKey(ACCESS_TOKEN, ACCESS_TOKEN, "header"));
        return list;
    }


    private List<SecurityContext> securityContexts() {
        List<SecurityContext> list = new ArrayList<>();
        list.add(SecurityContext.builder()
                .securityReferences(this.defaultAuth())
                .forPaths(PathSelectors.regex("^(?!auth).*$"))
                .build());
        return list;
    }

    private List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        List<SecurityReference> list = new ArrayList<>();
        list.add(new SecurityReference(ACCESS_TOKEN, authorizationScopes));
        return list;
    }


    /**
     * 构建版本信息
     *
     * @param version ${@link String} 需要构建的版本
     * @return Docket ${@link Docket} 版本Docket信息
     * @author zxiaozhou
     * @date 2020-07-08 18:47
     */
    protected Docket createDocket(String version) {
        Set<String> setProtocols = new HashSet<>(Arrays.asList(protocols));
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo(version))
                .groupName(version)
                .select()
                .apis(basePackage(BASE_PACKAGES))
                .apis(input -> matchVersion(input, version))
                .paths(PathSelectors.any())
                .build()
                .protocols(setProtocols)
                .securitySchemes(this.securitySchemes())
                .securityContexts(this.securityContexts());

    }


    /**
     * 匹配swagger版本
     *
     * @param input   ${@link String} 注解解析
     * @param version ${@link String} 版本信息
     * @return String[] ${@link String[]}
     * @author zxiaozhou
     * @date 2020-07-06 18:49
     */
    protected static boolean matchVersion(RequestHandler input, String version) {
        if (StringUtils.isBlank(version) && !isControllerHidden(input)) {
            return true;
        }
        String[] versions = getVersions(input);
        for (String v : versions) {
            if (version.equalsIgnoreCase(v)) {
                return true;
            }
        }
        return false;
    }


    /**
     * 获取版本信息
     *
     * @param input ${@link RequestHandler}
     * @return String[] ${@link String[]} 获取的接口信息
     * @author zxiaozhou
     * @date 2020-07-06 18:11
     */
    protected static String[] getVersions(RequestHandler input) {
        if (Objects.isNull(input) || isControllerHidden(input)) {
            return new String[]{};
        }
        Optional<ApiOperation> methodAnnotation = input.findAnnotation(ApiOperation.class);
        if (methodAnnotation.isPresent()) {
            ApiOperation apiOperation = methodAnnotation.get();
            return getVersions(apiOperation.value());

        }
        return new String[]{};
    }


    /**
     * 解析版本信息
     *
     * @param version ${@link String} 匹配版本
     * @return String[] ${@link String[]} 匹配后的接口
     * @author zxiaozhou
     * @date 2020-07-06 18:02
     */
    private static String[] getVersions(String version) {
        String reg = "(.)*(@[\\((].*[\\))])(.)*";
        if (version.matches(reg)) {
            version = version.replaceAll(reg, "$2");
            reg = "(@[\\((])(.*)([\\))])";
            version = version.replaceAll(reg, "$2");
            return version.split("[,,]");
        }
        return new String[]{};
    }


    /**
     * 查询controller是否隐藏
     *
     * @param input ${@link RequestHandler}
     * @author zxiaozhou
     * @date 2020-07-20 12:48
     */
    protected static boolean isControllerHidden(RequestHandler input) {
        Optional<Api> controllerAnnotation = input.findControllerAnnotation(Api.class);
        if (controllerAnnotation.isPresent()) {
            Api api = controllerAnnotation.get();
            return api.hidden();
        }
        return false;
    }


    /**
     * api标题信息
     *
     * @param version ${@link String} 版本
     * @return ApiInfo ${@link ApiInfo} api标题信息
     * @author zxiaozhou
     * @date 2020-07-08 18:45
     */
    protected static ApiInfo apiInfo(String version) {
        return new ApiInfoBuilder()
                .title("Swagger API配置")
                .description("接口详细说明")
                .contact(new Contact("zxiaozhou", "", "z7630853@163.com"))
                .version(version)
                .build();
    }
}

三、自定义多包扫描

代码中BASE_PACKAGES就是定义要扫描的包路径,这个是一个数组变量

四、项目地址

Gitee代码仓库地址:git@gitee.com:zxiaozhou/swagger.git

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值