spring boot: 设计接口站api的版本号,支持次版本号(spring boot 2.3.2)
一,为什么接口站的api要使用版本号?
1,当服务端接口的功能发生改进后,
客户端如果不更新版本,
3,版本号需要能向下兼容,
如果访问一个高的版本不存在时,应该能访问到比它小的离它最近的版本,
因为如果每个入口或每个功能在有新版本时都要重新写一遍,
则和把代码复制一份没有两样了,
这样才可以方便的管理
4,版本号可以放到请求头和url中,
我们采用比较直观的放到url中的形式演示,
形如: /v1.2/home/page?
5,说明:生产环境中没发现有必要在次版本号后面再加一级,
所以有次版本号后已经够用了,
大家如果认为有必要增加一级的话,可以修改代码中VERSION_PREFIX_PATTERN的正则:
说明:刘宏缔的架构森林是一个专注架构的博客,
网站:https://blog.imgtouch.com
本文: https://blog.imgtouch.com/index.php/2023/05/24/springboot-she-ji-jie-kou-zhan-api-de-ban-ben-hao-zhi-chi-ci-ban-ben-hao-springboot232/
对应的源码可以访问这里获取: https://github.com/liuhongdi/
说明:作者:刘宏缔 邮箱: 371125307@qq.com
二,演示项目的相关信息
1,项目地址:
https://github.com/liuhongdi/apiversion
2,项目说明:
我们给接口站的接口增加api的版本号
支持整数方式(例:v1)和次版本号形式(例:v1.3)
3,项目结构:如图:
三,java代码说明
1,ApiVersion.java
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ApiVersion { //版本号的值,从1开始 String value() default "1"; }
用来添加版本号的一个注解
2,ApiVersionCondition.java
//实现RequestCondition public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> { //api版本号 private String apiVersion; //版本号的格式,如: /v[1-n]/api/test or /v1.5/home/api private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("/v((\\d+\\.\\d+)|(\\d+))/"); public ApiVersionCondition(String apiVersion) { this.apiVersion = apiVersion; } //将不同的筛选条件进行合并 @Override public ApiVersionCondition combine(ApiVersionCondition other) { // 采用最后定义优先原则,则方法上的定义覆盖类上面的定义 return new ApiVersionCondition(other.getApiVersion()); } //版本比对,用于排序 @Override public int compareTo(ApiVersionCondition other, HttpServletRequest request) { //优先匹配最新版本号 return compareTo(other.getApiVersion(),this.apiVersion)?1:-1; } //获得符合匹配条件的ApiVersionCondition @Override public ApiVersionCondition getMatchingCondition(HttpServletRequest request) { Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI()); if (m.find()) { String version = m.group(1); if (compareTo(version,this.apiVersion)){ return this; } } return null; } //compare version private boolean compareTo(String version1,String version2){ if (!version1.contains(".")) { version1 += ".0"; } if (!version2.contains(".")) { version2 += ".0"; } String[] split1 = version1.split("\\."); String[] split2 = version2.split("\\."); for (int i = 0; i < split1.length; i++) { if (Integer.parseInt(split1[i])<Integer.parseInt(split2[i])){ return false; } } return true; } public String getApiVersion() { return apiVersion; } }
实现查找和比较版本号的功能
3,ApiVersionRequestMappingHandlerMapping.java
//扩展RequestMappingHandlerMapping public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping { //类上有 @ApiVersion注解时生效 @Override protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) { ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class); return createRequestCondition(apiVersion); } //方法上有 @ApiVersion注解时生效 @Override protected RequestCondition<?> getCustomMethodCondition(Method method) { ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class); return createRequestCondition(apiVersion); } //返回ApiVersionCondition private RequestCondition<ApiVersionCondition> createRequestCondition(ApiVersion apiVersion) { return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value()); } }
继承RequestMappingHandlerMapping,在类上和方法上有注解时,
使用ApiVersionCondition进行处理
4,WebMvcConfig.java
@Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { //在获取RequestMappingHandlerMapping时 //返回我们自定义的ApiVersionRequestMappingHandlerMapping @Override protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() { return new ApiVersionRequestMappingHandlerMapping(); } }
把createRequestMappingHandlerMapping方法返回时使用我们自定义的ApiVersionRequestMappingHandlerMapping
5,HomeController.java
@RestController @RequestMapping("/{version}/home") public class HomeController { //匹配版本v1的访问 @ApiVersion("1") @GetMapping @RequestMapping("/home") public String home01(@PathVariable String version) { return "home v1 : version:" + version; } //匹配版本v2的访问 @ApiVersion("2.0") @GetMapping @RequestMapping("/home") public String home02(@PathVariable String version) { return "home v2 version: " + version; } //匹配版本v1.5-2.0的访问 @ApiVersion("1.5") @GetMapping @RequestMapping("/home") public String home15(@PathVariable String version) { return "home v1.5 version: " + version; } }
6,GoodsV1Controller和GoodsV2Controller
把版本号加到controller上,
和加在方法上的使用一样,
为节省篇幅,不再贴代码,大家可以自己去github上访问
四,测试效果
1,访问
http://127.0.0.1:8080/v1.0/home/home
返回:
home v1 : version:v1.0
访问
http://127.0.0.1:8080/v2/home/home
返回:
home v2 version: v2
可见版本号带不带点,不妨碍它访问到对应的版本
2,访问:
http://127.0.0.1:8080/v3.0/home/home
返回:
home v2 version: v3.0
访问:
http://127.0.0.1:8080/v1.14/home/home
返回:
home v1.5 version: v1.14
可见版本号可以自动访问到距它最近的版本的功能
五, 查看spring boot的版本
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.3.2.RELEASE)