基于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://gitee.com/fxbar/SpringBootSwagger2Demo.git)
版权声明:本文为qq_21683643原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。