ApiVersion配置相关类
WebConfig
在WebConfig(implements WebMvcRegistrations)里:
@Override
@NonNull
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new ApiVersionRequestMappingHandlerMapping(ApiVersionKey.of(apiProperties.getVersion()));
}
一、使用方式
- 在Controller上注解:
@Api(value="Redis", tags="[Car]")
@ApiVersion("v12")
@RestController
@RequestMapping("{version}/redis")
public class RedisController {}
这里的{version}仅作占位使用,使之能识别一个参数,实际该参数在工程中并未被使用,以下是如何使用该参数:
@RequestMapping("/{version}/redis")
public String getData(@PathVariable String version){}
- 在方法上注解
@ApiVersion("v15")
@ApiOperation("publish message")
@ApiOperationSupport(ignoreParameters = {"id"})
@GetMapping("/pub")
public String publish(String channel, String body) {
redisTemplateString.convertAndSend(channel, body);
return "ok";
}
这样/v15/redis/pub将可以被spring识别。
二、无法匹配大于个位数的版本号
现有正则串只支持匹配一位版本号,即只能匹配/v1.3.4/,原pattern:
Pattern VERSION_PREFIX_PATTERN_1 = Pattern.compile("/v\\d\\.\\d\\.\\d/");
Pattern VERSION_PREFIX_PATTERN_2 = Pattern.compile("/v\\d\\.\\d/");
Pattern VERSION_PREFIX_PATTERN_3 = Pattern.compile("/v\\d/");
修改为:
private static final Pattern VERSION_PREFIX_PATTERN_1 = Pattern.compile("/v\\d+\\.\\d+\\.\\d+/");
/**
* version format, 2 levels, v1.1 e.g.
*/
private static final Pattern VERSION_PREFIX_PATTERN_2 = Pattern.compile("/v\\d\\+.\\d+/");
/**
* version format, 3 levels, v1.1.1 e.g.
*/
private static final Pattern VERSION_PREFIX_PATTERN_3 = Pattern.compile("/v\\d+/");
使之可以匹配多个数字,如匹配/v103.413.4342/。
三、当两个方法有相同路由,且同时被ApiVersion注解时
@ApiVersion("v16")
@GetMapping("/pub")
public String publish1(String channel, String body) {
redisTemplateString.convertAndSend(channel, body);
return "ok";
}
@ApiVersion("v15")
@ApiOperation("publish message")
@ApiOperationSupport(ignoreParameters = {"id"})
@GetMapping("/pub")
public String publish2(String channel, String body) {
redisTemplateString.convertAndSend(channel, body);
return "ok";
}
这样注册时/{version}/pub的version被设置为v16,latestVersion为yaml配置中config.api.version的值,version为实际请求的值。
经测试,将两个方法定义的顺序交换,该api版本变为v15,也就是原api管理的代码没有实现识别最新版本接口的功能(应完善ApiVersionRequestMappingHandlerMapping类)。
三、多版本选择逻辑
目前版本选择涉及到三个版本定义的比较:
-
yaml文件中配置的config.api.version,在compareVersion中该变量名为latestVersion
-
由@ApiVersion定义的接口version,在compareVersion中该变量名为apiVersion
-
请求url中带的version,在conpareVersion中该变量名为version
请求version需满足比apiVersion大,且比latestVersion小,不然会返回404,比如以下是有效请求: -
config.api.version=v20.0.0
-
接口@ApiVersion(“v15”)
-
请求url:/v18/pub
以下是无效请求: -
请求url version小于ApiVersion
- config.api.version=v20.0.0
- 接口@ApiVersion(“v15.0”)
- 请求url:/v12/pub
-
ApiVersion大于config.api.version
- config.api.version=v2.0
- 接口@ApiVersion(“v15”)
- 请求url:/v12/pub
版本号是以点分割,以v开头以点连接的1~3个数字,按位比较,逐次从高到低位比较,可以兼容三种pattern。
四、多模块版本管理
创建了三个模块,其中有公共配置模块shared-lib和依赖shared-lib的app1和app2,具体依赖到shared-lib中以下五个文件:
- ApiVersion.java
- ApiVersionCondition.java
- ApiVersionKey.java
- ApiVersionRequestMappingHandlerMapping.java
- ApiProperties.java
- WebConfig.java
其中ApiProperties为获取propertise的Data,各模块的properties文件仅该模块可见,且后端服务端口不同,api version可共用代码,但各管各模块的。
各模块配置和试验结果如下
- shared-lib:
- config.api.version=v10.0.0
- app1:8080端口
- config.api.version=v9.0.0
- @ApiVersion(“v2”)
- 结果:v1:404,v2:ok,v8:ok,v9:ok,v10:404
- app2:8081端口
- config.api.version=v12.0.0
- @ApiVersion(“v3”)
- 结果:v2:404,v3:ok,v10:ok,v12:ok,v13:404
结论:shard-lib配置的properties没有起作用,各模块执行代码使用的各自的上下文和method实体。
如何配置共用config.api.version
方法一:(失败)
子模块会覆盖父模块的properties,所以新增一条子模块配置项,改下相应的代码就可以了,重点是要明确api版本比较的逻辑,因为增加了一项需要比较的。
- 在子模块中新增配置项:config.api.childVersion
- 在ApiProperties.java增加一项:
private String childVersion;
- 修改ApiVersionCondition.java,暂定如果子模块version大于父模块定义的全局version,则在该子模块中覆盖,否则取全局version作为latestVersion
//新增
@Getter
private final ApiVersionKey childVersion;
//修改参数
@Override
@NonNull
public ApiVersionCondition combine(@NonNull ApiVersionCondition other) {
return new ApiVersionCondition(other.apiVersion, latestVersion, childVersion);
}
//修改比较逻辑
@Override
public ApiVersionCondition getMatchingCondition(@NonNull HttpServletRequest request) {
for (Pattern pattern : VERSION_LIST) {
Matcher m = pattern.matcher(request.getRequestURI());
if (m.find()) {
ApiVersionKey version = ApiVersionKey.of(m.group(0).replace("/", StringConstants.EMPTY));
ApiVersionKey compVersion = compareVersion(this.latestVersion, this.childVersion) > 0? this.latestVersion: this.childVersion;
if (compareVersion(version, this.apiVersion) >= 0 && compareVersion(version, compVersion) <= 0) {
return this;
}
}
}
return null;
}
- 同步修改WebConfig.java
@Override
@NonNull
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new ApiVersionRequestMappingHandlerMapping(ApiVersionKey.of(apiProperties.getVersion()), ApiVersionKey.of(apiProperties.getChildVersion()));
}
- 同步修改ApiVersionRequestMappingHandlerMapping.java
@Getter
private final ApiVersionKey childVersion;
public ApiVersionRequestMappingHandlerMapping(final ApiVersionKey maxVersion, final ApiVersionKey childVersion) {
this.latestVersion = maxVersion;
this.childVersion = childVersion;
}
- 需要使子模块启动时,ApiVersion的bean能同时加载到父模块和子模块的properties,网上说mvn install父模块即可,但是这样尝试之后仍获取不到父模块version,如果需要采用这个方法的话还需探明启动子模块时ApiVersion bean初始化方法。
方法二:(成功)
直接在ApiVersionKey.java中配置final项指定default_version,以后修改全局项改该成员变量:
public static final ApiVersionKey DEFAULT_VERSION = new ApiVersionKey(10, 0, 0);
- 测试结果成功
- shared-lib:
- DEFAULT_VERSION=v10.0.0
- app1:8080端口
- config.api.childVersion=v9.0.0
- @ApiVersion(“v2”)
- 结果:v1:404,v2:ok,v8:ok,v9:ok,v10:ok
- app2:8081端口
- config.api.childVersion=v12.0.0
- @ApiVersion(“v3”)
- 结果:v2:404,v3:ok,v10:ok,v12:ok,v13:404