SpringBoot 实现后端的接口版本支持
引言
作为一个主职的后端开发者,在平时的工作中,最讨厌的做的事情可以说是参数校验和接口的版本支持了。对于客户端的同学来说,业务的历史包袱会小很多,当出现不兼容的业务变动时,直接开发新的就好;然而后端就没有这么简单了,历史的接口得支持,新的业务也得支持,吭哧吭哧的新加一个服务接口,url 又不能和之前的相同,怎么办?只能在某个地方加一个类似v1, v2...
那么有没有一种不改变 url,通过其他的方式来支持版本管理的方式呢?
RequestCondition
RequestMappingHandlerMapping
每个请求中必须携带版本参数
每个接口都定义有一个支持的版本
其中第一个 x:对应的是大版本,一般来说只有较大的改动升级,才会改变
其中第二个 x:表示正常的业务迭代版本号,每发布一个常规的 app 升级,这个数值+1
最后一个 x:主要针对 bugfix,比如发布了一个 app,结果发生了异常,需要一个紧急修复,需要再发布一个版本,这个时候可以将这个数值+1
首先从请求中,获取版本参数 version
从所有相同的 url 接口中,根据接口上定义的版本,找到所有小于等于 version 的接口
在上面满足条件的接口中,选择版本最大的接口来响应请求
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Api {
/**
* 版本
*
* @return
*/
String value() default "1.0.0";
}@Data
public class ApiItem implements Comparable<ApiItem> {
private int high = 1;
private int mid = 0;
private int low = 0;
public ApiItem() {
}
@Override
public int compareTo(ApiItem right) {
if (this.getHigh() > right.getHigh()) {
return 1;
} else if (this.getHigh() < right.getHigh()) {
return -1;
}
if (this.getMid() > right.getMid()) {
return 1;
} else if (this.getMid() < right.getMid()) {
return -1;
}
if (this.getLow() > right.getLow()) {
return 1;
} else if (this.getLow() < right.getLow()) {
return -1;
}
return 0;
}
}public class ApiConverter {
public static ApiItem convert(String api) {
ApiItem apiItem = new ApiItem();
if (StringUtils.isBlank(api)) {
return apiItem;
}
String[] cells = StringUtils.split(api, ".");
apiItem.setHigh(Integer.parseInt(cells[0]));
if (cells.length > 1) {
apiItem.setMid(Integer.parseInt(cells[1]));
}
if (cells.length > 2) {
apiItem.setLow(Integer.parseInt(cells[2]));
}
return apiItem;
}
}public class ApiCondition implements RequestCondition<ApiCondition> {
private ApiItem version;
public ApiCondition(ApiItem version) {
this.version = version;
}
@Override
public ApiCondition combine(ApiCondition other) {
// 选择版本最大的接口
return version.compareTo(other.version) >= 0 ? new ApiCondition(version) : new ApiCondition(other.version);
}
@Override
public ApiCondition getMatchingCondition(HttpServletRequest request) {
String version = request.getHeader("x-api");
ApiItem item = ApiConverter.convert(version);
// 获取所有小于等于版本的接口
if (item.compareTo(this.version) >= 0) {
return this;
}
return null;
}
@Override
public int compareTo(ApiCondition other, HttpServletRequest request) {
// 获取最大版本对应的接口
return other.version.compareTo(this.version);
}
}getMatchingCondition方法中,控制了只有版本小于等于请求参数中的版本的 ApiCondition 才满足规则
compareTo 指定了当有多个ApiCoondition满足这个请求时,选择最大的版本
public class ApiHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected RequestCondition getCustomTypeCondition(Class handlerType) {
return buildFrom(AnnotationUtils.findAnnotation(handlerType, Api.class));
}
@Override
protected RequestCondition getCustomMethodCondition(Method method) {
return buildFrom(AnnotationUtils.findAnnotation(method, Api.class));
}
private ApiCondition buildFrom(Api platform) {
return platform == null ? new ApiCondition(new ApiItem()) :
new ApiCondition(ApiConverter.convert(platform.value()));
}
}@Configuration
public class ApiAutoConfiguration implements WebMvcRegistrations {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new ApiHandlerMapping();
}
}@RestController
@RequestMapping(path = "v1")
public class V1Rest {
@GetMapping(path = "show")
public String show1() {
return "v1/show 1.0.0";
}
@Api("1.1.2")
@GetMapping(path = "show")
public String show2() {
return "v1/show 1.1.2";
}
@Api("1.1.0")
@GetMapping(path = "show")
public String show3() {
return "v1/show 1.1.0";
}
}
从上面的截图可以看出,请求头中没有版本时,默认给一个1.0.0的版本
响应的是小于请求版本的接口中,版本最大的哪一个
@Api("2.0.0")
@RestController
@RequestMapping(path = "v2")
public class V2Rest {
@Api("1.1.0")
@GetMapping(path = "show")
public String show0() {
return "v2/show0 1.1.0";
}
@GetMapping(path = "show")
public String show1() {
return "v2/show1 2.0.0";
}
@Api("2.1.1")
@GetMapping(path = "show")
public String show2() {
return "v2/show2 2.1.1";
}
@Api("2.2.0")
@GetMapping(path = "show")
public String show3() {
return "v2/show3 2.2.0";
}
}
从上面的截图中,可以看出来版本小于 2.0.0 的请求,报的是 404 错误
请求版本小于 2.1.1 的请求,报的是冲突异常

