SpringBoot 接口多版本
半塘 2024/1/28 SpringBoot 框架
# 1、接口多版本的必要性
# 1.1、多版本接口介绍
开发项目中,随着需求的变更或增加,API接口也会跟着变化,为了不影响原有的API功能,已使用的接口肯定不能直接覆盖更新,需要新增升级版本API与应用版本对应,因此需要多个版本的接口。
# 1.2、接口规范
对于多版本的接口根据不同的需要可以有如下这些规范。
- version区分
xygalaxy/user?version=v1
xygalaxy/user?version=v2
1
2
2
- 路径区分
xygalaxy/v1/user
xygalaxy/v2/user
1
2
2
- 域名区分
v1.xygalaxy.com/user
v2.xygalaxy.com/user
1
2
2
后端处理的话,可以通过判断请求的URL+注解@ApiVersion("1")方式来进行区分。
# 1.3、版本定义
根据常见的三段式版本设计,版本格式定义如下
x.x.x
1
- 其中第一个 x:对应的是大版本,一般来说只有较大的改动升级,才会改变
- 其中第二个 x:表示正常的业务迭代版本号,每发布一个常规的 app 升级,这个数值+1
- 最后一个 x:主要针对 bugfix,比如发布了一个 app,结果发生了异常,需要一个紧急修复,需要再发布一个版本,这个时候可以将这个数值+1
# 2、实现案例
# 2.1、自定义@ApiVersion注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface ApiVersion {
String value();
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 2.2、自定义RequestCondition
版本匹配支持三层版本
v1.1.1 (大版本.小版本.补丁版本) v1.1 (等同于v1.1.0) v1 (等同于v1.0.0)
@Slf4j
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
/**
* 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 List<Pattern> VERSION_LIST = Collections.unmodifiableList(
Arrays.asList(VERSION_PREFIX_PATTERN_1, VERSION_PREFIX_PATTERN_2, VERSION_PREFIX_PATTERN_3)
);
@Getter
private final String apiVersion;
public ApiVersionCondition(String apiVersion) {
this.apiVersion = apiVersion;
}
/**
* method priority is higher then class.
*
* @param other other
* @return ApiVersionCondition
*/
@Override
public ApiVersionCondition combine(ApiVersionCondition other) {
return new ApiVersionCondition(other.apiVersion);
}
@Override
public 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;
}
@Override
public 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;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 2.3、定义HandlerMapping
public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
/**
* add @ApiVersion to controller class.
*
* @param handlerType handlerType
* @return RequestCondition
*/
@Override
protected 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
*/
@Override
protected RequestCondition<?> getCustomMethodCondition(@NonNull Method method) {
ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
return null == apiVersion ? super.getCustomMethodCondition(method) : new ApiVersionCondition(apiVersion.value());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 2.4、配置注册HandlerMapping
@Configuration
public class CustomWebMvcConfiguration extends WebMvcConfigurationSupport {
@Override
public RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
return new ApiVersionRequestMappingHandlerMapping();
}
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 2.5、测试运行结果
@RestController
@RequestMapping("api/{v}/user")
public class UserController {
@RequestMapping("get")
public User getUser() {
return User.builder().age(18).name("xygalaxy, default").build();
}
@ApiVersion("1.0.0")
@RequestMapping("get")
public User getUserV1() {
return User.builder().age(18).name("xygalaxy, v1.0.0").build();
}
@ApiVersion("1.1.0")
@RequestMapping("get")
public User getUserV11() {
return User.builder().age(19).name("xygalaxy, v1.1.0").build();
}
@ApiVersion("1.1.2")
@RequestMapping("get")
public User getUserV112() {
return User.builder().age(19).name("xygalaxy2, v1.1.2").build();
}
}
/** 测试结果访问
http://localhost:8080/api/v1/user/get
// {"name":"xygalaxy, v1.0.0","age":18}
http://localhost:8080/api/v1.1/user/get
// {"name":"xygalaxy, v1.1.0","age":19}
http://localhost:8080/api/v1.1.1/user/get
// {"name":"xygalaxy, v1.1.0","age":19} 匹配比1.1.1小的中最大的一个版本号
http://localhost:8080/api/v1.1.2/user/get
// {"name":"xygalaxy2, v1.1.2","age":19}
http://localhost:8080/api/v1.2/user/get
// {"name":"xygalaxy2, v1.1.2","age":19} 匹配最大的版本号,v1.1.2
**/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45