基于SpringBoot整合Swagger2项目API版本控制

基于SpringBoot整合Swagger2项目API版本控制

SpringBoot项目pom配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.aeert</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>2.0.4</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

定义版本号标记注解

package com.loop.demo.annotation;

import org.springframework.web.bind.annotation.Mapping;

import java.lang.annotation.*;

/**
 * @ClassName: ApiVersion
 * @Desc: 接口版本标识注解
 * @version: 1.0
 * @author: loop.fu
 * @date: 2021/8/9 8:46
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface ApiVersion {

    /**
     * 标识版本号
     *
     * @return
     */
    int value();

}

定义版本号标记常量类

package com.loop.demo.constant;

/**
 * @ClassName: ApiVersionConstant
 * @Desc: 类作用描述
 * @version: 1.0
 * @author: loop.fu
 * @date: 2021/8/9 8:46
 */
public interface ApiVersionConstant {
    int VERSION_1 = 1;
    int VERSION_2 = 2;
    int VERSION_3 = 3;
    int VERSION_4 = 4;
    int VERSION_5 = 5;
    int VERSION_6 = 6;
    int VERSION_7 = 7;
    int VERSION_8 = 8;
    int VERSION_9 = 9;
}


定义Swagger接口版本标识注解

package com.loop.demo.annotation;
import org.springframework.web.bind.annotation.Mapping;
import java.lang.annotation.*;

/**
 * @ClassName: SwaggerApiVersion
 * @Desc: Swagger接口版本标识注解
 * @version: 1.0
 * @author: loop.fu
 * @date: 2021/8/9 8:46
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface SwaggerApiVersion {
    /**
     * 分组名(即版本号),可以传入多个
     *
     * @return
     */
    String[] value();

}

Swagger接口版本标识常量类

package com.loop.demo.constant;

/**
 * @ClassName: SwaggerApiVersionConstant
 * @Desc: 类作用描述
 * @version: 1.0
 * @author: loop.fu
 * @date: 2021/8/9 8:46
 */
public interface SwaggerApiVersionConstant {
    String VERSION_1 = "v1";
    String VERSION_2 = "v2";
    String VERSION_3 = "v3";
    String VERSION_4 = "v4";
    String VERSION_5 = "v5";
    String VERSION_6 = "v6";
    String VERSION_7 = "v7";
    String VERSION_8 = "v8";
    String VERSION_9 = "v9";
}

版本号匹配筛选器

package com.loop.demo.interceptor;

import org.springframework.web.servlet.mvc.condition.RequestCondition;

import javax.servlet.http.HttpServletRequest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @ClassName: ApiVersionCondition
 * @Desc: 版本号匹配筛选器
 * @version: 1.0
 * @author: loop.fu
 * @date: 2021/8/9 8:46
 */
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {

    /**
     * 路径中版本的前缀, 这里用 v[1-9]的形式
     */
    private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("v(\\d+)/");

    private int apiVersion;

    public ApiVersionCondition(int apiVersion) {
        this.apiVersion = apiVersion;
    }

    public ApiVersionCondition combine(ApiVersionCondition other) {
        /** 采用最后定义优先原则,则方法上的定义覆盖类上面的定义*/
        return new ApiVersionCondition(other.getApiVersion());
    }

    public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
        Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI());
        if (m.find()) {
            Integer version = Integer.valueOf(m.group(1));
            /** 如果请求的版本号大于配置版本号,则满足*/
            if (version >= this.apiVersion)
                return this;
        }
        return null;
    }

    @Override
	public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
        /** 优先匹配最新的版本号*/
        return other.getApiVersion() - this.apiVersion;
    }

    public int getApiVersion() {
        return apiVersion;
    }

}

版本号匹配拦截器

package com.loop.demo.interceptor;

import com.loop.demo.annotation.ApiVersion;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Method;

/**
 * @ClassName: CustomRequestMappingHandlerMapping
 * @Desc: 版本号匹配拦截器
 * @version: 1.0
 * @author: loop.fu
 * @date: 2021/8/9 8:46
 */
public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

    @Override
    protected RequestCondition<ApiVersionCondition> getCustomTypeCondition(Class<?> handlerType) {
        ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
        return createCondition(apiVersion);
    }

    @Override
    protected RequestCondition<ApiVersionCondition> getCustomMethodCondition(Method method) {
        ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
        return createCondition(apiVersion);
    }

    private RequestCondition<ApiVersionCondition> createCondition(ApiVersion apiVersion) {
        return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value());
    }
}

application.yml

server:
    port: 8080
    servlet:
        context-path: /

SpringBoot 启动类

package com.loop.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @ClassName: DemoApplication
 * @Desc: SpringBoot 启动类
 * @version: 1.0
 * @author: loop.fu
 * @date: 2021/8/9 8:46
 */
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

Swagger2Config配置

package com.loop.demo.config;

import com.loop.demo.annotation.SwaggerApiVersion;
import com.loop.demo.constant.SwaggerApiVersionConstant;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
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 springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.lang.reflect.Field;
import java.util.Arrays;

/**
 * @ClassName: Swagger2Config
 * @Desc: Swagger配置
 * @version: 1.0
 * @author: loop.fu
 * @date: 2021/8/9 8:46
 */
@Configuration
@EnableSwagger2
public class Swagger2Config implements InitializingBean {

    @Autowired
    private ApplicationContext applicationContext;

    // 默认版本的分组
    @Bean
    public Docket docket() {
        // basePackage 需要扫描注解生成文档的路径
        return new Docket(DocumentationType.SWAGGER_2)
                // 分组名用aaa开头以便排在最前面
                .groupName("默认分组(所有接口)")
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Swagger Demo")
                .description("这是展示Swagger怎么用的例子")
                .version("1.0").build();
    }

    private Docket buildDocket(String groupName) {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .groupName(groupName)
                .select()
                .apis(method -> {
                    // 每个方法会进入这里进行判断并归类到不同分组,**请不要调换下面两段代码的顺序,在方法上的注解有优先级**

                    // 该方法上标注了版本
                    if (method.isAnnotatedWith(SwaggerApiVersion.class)) {
                        SwaggerApiVersion apiVersion = method.getHandlerMethod().getMethodAnnotation(SwaggerApiVersion.class);
                        if (apiVersion.value() != null && apiVersion.value().length != 0) {
                            if (Arrays.asList(apiVersion.value()).contains(groupName)) {
                                return true;
                            }
                        }
                    }

                    // 方法所在的类是否标注了?
                    SwaggerApiVersion annotationOnClass = method.getHandlerMethod().getBeanType().getAnnotation(SwaggerApiVersion.class);
                    if (annotationOnClass != null) {
                        if (annotationOnClass.value() != null && annotationOnClass.value().length != 0) {
                            if (Arrays.asList(annotationOnClass.value()).contains(groupName)) {
                                return true;
                            }
                        }
                    }
                    return false;
                })
                .paths(PathSelectors.any())
                .build();
    }

    /**
     * 动态得创建Docket bean
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        // ApiConstantVersion 里面定义的每个变量会成为一个docket
        Class<SwaggerApiVersionConstant> clazz = SwaggerApiVersionConstant.class;
        Field[] declaredFields = clazz.getDeclaredFields();

        // 动态注入bean
        AutowireCapableBeanFactory autowireCapableBeanFactory = applicationContext.getAutowireCapableBeanFactory();
        if (autowireCapableBeanFactory instanceof DefaultListableBeanFactory) {
            DefaultListableBeanFactory capableBeanFactory = (DefaultListableBeanFactory) autowireCapableBeanFactory;
            for (Field declaredField : declaredFields) {

                // 要注意 "工厂名和方法名",意思是用这个bean的指定方法创建docket
                AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
                        .genericBeanDefinition()
                        .setFactoryMethodOnBean("buildDocket", "swagger2Config")
                        .addConstructorArgValue(declaredField.get(SwaggerApiVersionConstant.class)).getBeanDefinition();
                capableBeanFactory.registerBeanDefinition(declaredField.getName(), beanDefinition);
            }
        }
    }
}

Web配置

package com.loop.demo.config;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.loop.demo.interceptor.CustomRequestMappingHandlerMapping;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName: WebMvcConfig
 * @Desc: Web配置
 * @version: 1.0
 * @author: loop.fu
 * @date: 2021/8/9 8:46
 */
@SpringBootConfiguration
public class WebMvcConfig extends WebMvcConfigurationSupport {

    /**
     * 重写请求过处理的方法
     *
     * @return
     */
    @Override
    public RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
        RequestMappingHandlerMapping handlerMapping = new CustomRequestMappingHandlerMapping();
        handlerMapping.setOrder(0);
        return handlerMapping;
    }

    /**
     * JACKSON格式化处理
     */
    @Override
    protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        // 设置日期格式
        ObjectMapper objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
                false);
        SimpleDateFormat smt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        objectMapper.setDateFormat(smt);
        mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
        // 设置中文编码格式
        List<MediaType> list = new ArrayList<MediaType>();
        list.add(MediaType.APPLICATION_JSON_UTF8);
        mappingJackson2HttpMessageConverter.setSupportedMediaTypes(list);
        converters.add(mappingJackson2HttpMessageConverter);
    }

    /**
     * swagger静态文件路径过滤
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");

        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
        super.addResourceHandlers(registry);
    }

}

测试类

package com.loop.demo.controller.test;

import com.loop.demo.annotation.ApiVersion;
import com.loop.demo.annotation.SwaggerApiVersion;
import com.loop.demo.constant.ApiVersionConstant;
import com.loop.demo.constant.SwaggerApiVersionConstant;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName: OrderController
 * @Desc: 注解放在类上,则所有接口都生效
 * @version: 1.0
 * @author: loop.fu
 * @date: 2021/8/9 8:48
 */
@RestController
@Api(tags = "订单模块")
@RequestMapping("/api/{version}/order")
public class OrderController {

    // 放在方法上,可以有多个版本
    @ApiOperation("新增订单")
    @PostMapping("/order/add")
    @SwaggerApiVersion({
            SwaggerApiVersionConstant.VERSION_1})
    @ApiVersion(ApiVersionConstant.VERSION_1)  // v1
    public String addOrder1(@PathVariable String version, Long userId1) {
        return "addOrder1";
    }

    @ApiOperation("新增订单")
    @PostMapping("/order/add")
    @SwaggerApiVersion({
            SwaggerApiVersionConstant.VERSION_2})
    @ApiVersion(ApiVersionConstant.VERSION_2)  // v2
    public String addOrder2(@PathVariable String version, Long userId2) {
        return "addOrder2";
    }

    @ApiOperation("新增订单")
    @PostMapping("/order/add")
    @SwaggerApiVersion({
            SwaggerApiVersionConstant.VERSION_3})
    @ApiVersion(ApiVersionConstant.VERSION_3)  // v1
    public String addOrder3(@PathVariable String version, Long userId3) {
        return "addOrder3";
    }

}
package com.loop.demo.controller.test;

import com.loop.demo.annotation.ApiVersion;
import com.loop.demo.annotation.SwaggerApiVersion;
import com.loop.demo.constant.ApiVersionConstant;
import com.loop.demo.constant.SwaggerApiVersionConstant;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName: TestControler
 * @Desc: 测试接口
 * @version: 1.0
 * @author: loop.fu
 * @date: 2021/8/2 15:44
 */
@RestController
@RequestMapping("/api/{version}/test")
@Api(tags = "测试模块")
public class TestControler {

    // v1-v4
    @GetMapping("/fun")
    @ApiVersion(ApiVersionConstant.VERSION_1)
    @SwaggerApiVersion({
            SwaggerApiVersionConstant.VERSION_1,
            SwaggerApiVersionConstant.VERSION_2,
            SwaggerApiVersionConstant.VERSION_3,
            SwaggerApiVersionConstant.VERSION_4})
    public String fun1() {
        return "fun 1";
    }

    // v5-v8
    @ApiVersion(ApiVersionConstant.VERSION_5)
    @GetMapping("/fun")
    @SwaggerApiVersion({
            SwaggerApiVersionConstant.VERSION_5,
            SwaggerApiVersionConstant.VERSION_6,
            SwaggerApiVersionConstant.VERSION_7,
            SwaggerApiVersionConstant.VERSION_8})
    public String fun2() {
        return "fun 2";
    }

    // v9以上
    @ApiVersion(ApiVersionConstant.VERSION_9)
    @GetMapping("/fun")
    @SwaggerApiVersion({
            SwaggerApiVersionConstant.VERSION_9})
    public String fun3() {
        return "fun 5";
    }
}

测试结果

rn “fun 1”;
}

// v5-v8
@ApiVersion(ApiVersionConstant.VERSION_5)
@GetMapping("/fun")
@SwaggerApiVersion({
        SwaggerApiVersionConstant.VERSION_5,
        SwaggerApiVersionConstant.VERSION_6,
        SwaggerApiVersionConstant.VERSION_7,
        SwaggerApiVersionConstant.VERSION_8})
public String fun2() {
    return "fun 2";
}

// v9以上
@ApiVersion(ApiVersionConstant.VERSION_9)
@GetMapping("/fun")
@SwaggerApiVersion({
        SwaggerApiVersionConstant.VERSION_9})
public String fun3() {
    return "fun 5";
}

}


## 测试结果![在这里插入图片描述](https://img-blog.csdnimg.cn/51057b93c7094965a8ab8a045cca9f18.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIxNjgzNjQz,size_16,color_FFFFFF,t_70#pic_center)
源码地址:[添加链接描述](https://gitee.com/fxbar/SpringBootSwagger2Demo.git)

版权声明:本文为qq_21683643原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。