spring boot: 设计接口站api的版本号,支持次版本号(spring boot 2.3.2)
一,为什么接口站的api要使用版本号?
1,当服务端接口的功能发生改进后,
客户端如果不更新版本,
3,版本号需要能向下兼容,
如果访问一个高的版本不存在时,应该能访问到比它小的离它最近的版本,
因为如果每个入口或每个功能在有新版本时都要重新写一遍,
则和把代码复制一份没有两样了,
这样才可以方便的管理
4,版本号可以放到请求头和url中,
我们采用比较直观的放到url中的形式演示,
形如: /v1.2/home/page?
5,说明:生产环境中没发现有必要在次版本号后面再加一级,
所以有次版本号后已经够用了,
大家如果认为有必要增加一级的话,可以修改代码中VERSION_PREFIX_PATTERN的正则:
说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest
对应的源码可以访问这里获取: 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)
spring boot: 设计接口站api的版本号,支持次版本号(spring boot 2.3.2)的更多相关文章
- Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(一)
标题 Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(一) 技术 Spring Boot 2.Spring Security 5.JWT 运行环境 ...
- 品味Spring Cache设计之美
最近负责教育类产品的架构工作,两位研发同学建议:"团队封装的Redis客户端可否适配Spring Cache,这样加缓存就会方便多了" . 于是边查阅文档边实战,收获颇丰,写这篇文 ...
- spring boot:接口站增加api版本号后的安全增强(spring boot 2.3.3)
一,接口站增加api版本号后需要做安全保障? 1,如果有接口需要登录后才能访问的, 需要用spring security增加授权 2,接口站需要增加api版本号的检验,必须是系统中定义的版本号才能访问 ...
- spring boot使用swagger生成api接口文档
前言 在之前的文章中,使用mybatis-plus生成了对应的包,在此基础上,我们针对项目的api接口,添加swagger配置和注解,生成swagger接口文档 具体可以查看本站spring boot ...
- Spring Boot实战:Restful API的构建
上一篇文章讲解了通过Spring boot与JdbcTemplate.JPA和MyBatis的集成,实现对数据库的访问.今天主要给大家分享一下如何通过Spring boot向前端返回数据. 在现在的开 ...
- spring boot rest 接口集成 spring security(2) - JWT配置
Spring Boot 集成教程 Spring Boot 介绍 Spring Boot 开发环境搭建(Eclipse) Spring Boot Hello World (restful接口)例子 sp ...
- Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(二)
Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(二) 摘要 上一篇https://javaymw.com/post/59我们已经实现了基本的登录和t ...
- effective OC2.0 52阅读笔记(三 接口与API设计)
第三章:接口与API设计 15 用前缀避免命名空间冲突 总结:避免重名符号错误的唯一办法是变相实现命名空间.为所有符号都加上命名前缀.类和分类都应加三字前缀.注意类实现文件中的纯C函数及全局变量,是算 ...
- 学习设计接口api(转)
介绍 先说说啥是 Api 吧,以下摘自百度百科: API (Application Programming Interface,应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于 ...
随机推荐
- Matrix Power Series(POJ 3233)
原题如下: Matrix Power Series Time Limit: 3000MS Memory Limit: 131072K Total Submissions: 28044 Acce ...
- [SpringBoot项目]问题及解决总结
问题:MySQL 8.0版本连接报错:Could not create connection to database server 原因 MySQL8.0版本需要更换驱动为"com.mysq ...
- [程序员代码面试指南]递归和动态规划-最小编辑代价(DP)
问题描述 输入 原字符串StrOrg,目标字符串StrTarget,插入.删除.替换的编辑代价ic,dc,rc.输出将原字符串编辑成目标字符串的最小代价. 解题思路 状态表示 dp[i][j]表示把s ...
- StringBuilder 比 String 快?空嘴白牙的,证据呢!
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 聊的是八股的文,干的是搬砖的活! 面我的题开发都用不到,你为什么要问?可能这是大部分 ...
- openstack核心组件——nova计算服务(7)
云计算openstack核心组件——nova计算服务(7) 一.nova介绍: Nova 是 OpenStack 最核心的服务,负责维护和管理云环境的计算资源.OpenStack 作为 ...
- MySQL索引的基本操作
常见的索引类型 PROMARY KEY 主键索引,也是唯一索引,不允许重复数据. UNIQUE INDEX唯一索引,不允许有重复数据. INDEX 普通索引允许有重复数据. 组合索引,对多个字段添加索 ...
- hystrix动态修改参数
Hystrix 从入门到深入——运行时修改动态配置 /** * * @author zhangshuo * */ @Component public class DynamicConfigSource ...
- 6.Exchanger-交换机
- 基本的PID算法整理(水缸的例子有问题!!)
一,先谈关于水缸漏水的问题 谈到PID原理入门的时候,大家经常会举的一个例子就是水缸漏水的例子.这里把一个解释水缸漏水的帖子放在这里:https://blog.csdn.net/qq_41736609 ...
- 【FLASK】钩子函数的使用
from flask import Flask from flask import abort app = Flask(__name__) # 在第一次请求之前调用,可以在此方法内部做一些初始化操作 ...