上海发乐门网站建设公司,推广策划方案范文,汕头seo外包公司,网站支付页面怎么做springboot如何通过注解优雅实现接口多版本管理
背景
一个web服务一旦上线后接口往往对接很多下游#xff0c;下游应用可能强依赖这个接口#xff0c;因此如果对接口进行版本更新最好是要兼容过去版本#xff0c;提供一个相同功能的新版本。
接口版本管理的方式
通过请求…springboot如何通过注解优雅实现接口多版本管理
背景
一个web服务一旦上线后接口往往对接很多下游下游应用可能强依赖这个接口因此如果对接口进行版本更新最好是要兼容过去版本提供一个相同功能的新版本。
接口版本管理的方式
通过请求参数实现
yunfei.group/user?versionv1 表示 v1版本的接口, 保持原有接口不动
yunfei.group/user?versionv2 表示 v2版本的接口更新新的接口通过域名实现新版本接口在新实例通过域名路由过去
v1.yunfei.group/user
v2.yunfei.group/user注解实现通过重定义Handler Mapping过程实现。
注解方式实现接口版本管理
要实现的效果
当没有匹配上版本时走默认接口当有完全对应的版本时走完全匹配的接口当没有完全对应的版本时走对应的最新版本
版本的定义
v1.1.1 大版本.小版本.补丁版本v1.1 (等同于v1.1.0)v1 等同于v1.0.0)
代码实现
pom.xml
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.6.3/versionrelativePath//parentgroupIdorg.example/groupIdartifactIdApiVersionDemo/artifactIdversion1.0-SNAPSHOT/versionpropertiesmaven.compiler.source8/maven.compiler.sourcemaven.compiler.target8/maven.compiler.targetproject.build.sourceEncodingUTF-8/project.build.sourceEncoding/propertiesdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-devtools/artifactIdscoperuntime/scopeoptionaltrue/optional/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependencydependencygroupIdorg.springframework/groupIdartifactIdspring-aop/artifactId/dependencydependencygroupIdorg.slf4j/groupIdartifactIdslf4j-api/artifactId/dependencydependencygroupIdorg.slf4j/groupIdartifactIdslf4j-simple/artifactId/dependency/dependencies/project自定义注解
Target({ElementType.METHOD, ElementType.TYPE})
Retention(RetentionPolicy.RUNTIME)
Documented
Mapping
public interface ApiVersion {String value();
}这里value就是接口的版本号。
配置注册HandlerMapping
Configuration
public class CustomWebMvcConfiguration extends WebMvcConfigurationSupport {Overridepublic RequestMappingHandlerMapping createRequestMappingHandlerMapping() {return new ApiVersionRequestMappingHandlerMapping();}
}定义ApiVersionRequestMappingHandlerMapping
public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {/*** add ApiVersion to controller class.** param handlerType handlerType* return RequestCondition*/Overrideprotected RequestCondition? getCustomTypeCondition(NonNull Class? handlerType) {ApiVersion apiVersion AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);return null apiVersion ? super.getCustomTypeCondition(handlerType) : new ApiVersionCondition(apiVersion.value());}/*** add ApiVersion to controller method.** param method method* return RequestCondition*/Overrideprotected RequestCondition? getCustomMethodCondition(NonNull Method method) {ApiVersion apiVersion AnnotationUtils.findAnnotation(method, ApiVersion.class);return null apiVersion ? super.getCustomMethodCondition(method) : new ApiVersionCondition(apiVersion.value());}}springboot会在启动的时候分别针对controller和其中的method分别调用上述getCustomTypeCondition和getCustomMethodCondition方法当没有注解在controller或者method上时RequestCondition为默认否则为ApiVersionCondition并将对应注解的版本号传入。
定义版本匹配ApiVersionCondition
这里compareTo函数是在有多个符合条件的接口时用于比较接口匹配度的方法这里定义为符合条件里最新的版本为匹配版本。compareVersion函数主要定义了两个版本比较谁大谁小的逻辑。getMatchingCondition为核心函数用来判断请求和当前接口是否匹配。
Slf4j
public class ApiVersionCondition implements RequestConditionApiVersionCondition {/*** support v1.1.1, v1.1, v1; three levels .*/private static final Pattern VERSION_PREFIX_PATTERN_1 Pattern.compile(/v\\d\\.\\d\\.\\d/);private static final Pattern VERSION_PREFIX_PATTERN_2 Pattern.compile(/v\\d\\.\\d/);private static final Pattern VERSION_PREFIX_PATTERN_3 Pattern.compile(/v\\d/);private static final ListPattern VERSION_LIST Collections.unmodifiableList(Arrays.asList(VERSION_PREFIX_PATTERN_1, VERSION_PREFIX_PATTERN_2, VERSION_PREFIX_PATTERN_3));Getterprivate final String apiVersion;public ApiVersionCondition(String apiVersion) {this.apiVersion apiVersion;}/*** method priority is higher then class.** param other other* return ApiVersionCondition*/Overridepublic ApiVersionCondition combine(ApiVersionCondition other) {return new ApiVersionCondition(other.apiVersion);}Overridepublic ApiVersionCondition getMatchingCondition(HttpServletRequest request) {for (int vIndex 0; vIndex VERSION_LIST.size(); vIndex) {Matcher m VERSION_LIST.get(vIndex).matcher(request.getRequestURI());if (m.find()) {String version m.group(0).replace(/v, ).replace(/, );if (vIndex 1) {version version .0;} else if (vIndex 2) {version version .0.0;}if (compareVersion(version, this.apiVersion) 0) {log.info(version{}, apiVersion{}, version, this.apiVersion);return this;}}}return null;}Overridepublic int compareTo(ApiVersionCondition other, HttpServletRequest request) {return compareVersion(other.getApiVersion(), this.apiVersion);}private int compareVersion(String version1, String version2) {if (version1 null || version2 null) {throw new RuntimeException(compareVersion error:illegal params.);}String[] versionArray1 version1.split(\\.);String[] versionArray2 version2.split(\\.);int idx 0;int minLength Math.min(versionArray1.length, versionArray2.length);int diff 0;while (idx minLength (diff versionArray1[idx].length() - versionArray2[idx].length()) 0 (diff versionArray1[idx].compareTo(versionArray2[idx])) 0) {idx;}diff (diff ! 0) ? diff : versionArray1.length - versionArray2.length;return diff;}
}定义一个controller测试
RestController
RequestMapping(api/{v}/user)
public class UserController {RequestMapping(get)public User getUser() {return User.builder().age(18).name(pdai, default).build();}ApiVersion(1.0.0)RequestMapping(get)public User getUserV1() {return User.builder().age(18).name(pdai, v1.0.0).build();}ApiVersion(1.1.0)RequestMapping(get)public User getUserV11() {return User.builder().age(19).name(pdai, v1.1.0).build();}ApiVersion(1.1.2)RequestMapping(get)public User getUserV112() {return User.builder().age(19).name(pdai2, v1.1.2).build();}
}定义User
Builder
Data
public class User {private Integer age;private String name;
}
测试
http://localhost:8080/api/v1/user/get
// {name:pdai, v1.0.0,age:18}http://localhost:8080/api/v1.1/user/get
// {name:pdai, v1.1.0,age:19}http://localhost:8080/api/v1.1.1/user/get
// {name:pdai, v1.1.0,age:19} 匹配比1.1.1小的中最大的一个版本号http://localhost:8080/api/v1.1.2/user/get
// {name:pdai2, v1.1.2,age:19}http://localhost:8080/api/v1.2/user/get
// {name:pdai2, v1.1.2,age:19} 匹配最大的版本号v1.1.2