前言
因为现在开发都是前后端分离。前端如果是小程序或者App开发的时候,由于前端有版本控制而后端没有导致的问题,此时如果接口要修改之前的,那么后端没办法判断当前客户用的小程序版本只能让前端多传递一个参数了,这样的判断是不是不够优雅呢?是的没错,优雅的方式来啦~
实现逻辑
实现逻辑主要运用到了两个类
org.springframework.web.servlet.mvc.condition.RequestCondition
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
RequestCondition了解
在 spring mvc 中,通过DispatchServlet接收客户端发起的一个请求之后,会通过 HanderMapping 来获取对应的请求处理器;而 HanderMapping 如何找到可以处理这个请求的处理器呢,这就需要 RequestCondition 来决定了
RequestCondition主要有三个方法。
combine方法理解
当一个接口上有多个条件规则时,用于合并
比如类上指定了@RequestMapping的 url 为 root - 而方法上指定的@RequestMapping的 url 为 method - 那么在获取这个接口的 url 匹配规则时,类上扫描一次,方法上扫描一次,这个时候就需要把这两个合并成一个,表示这个接口匹配root/method
getMatchingCondition方法理解
这个是重点,用于判断当前匹配条件和请求是否匹配;如果不匹配返回null,如果匹配,生成一个新的请求匹配条件,该新的请求匹配条件是当前请求匹配条件针对指定请求request的剪裁,举个例子来讲,如果当前请求匹配条件是一个路径匹配条件,包含多个路径匹配模板,并且其中有些模板和指定请求request匹配,那么返回的新建的请求匹配条件将仅仅,包含和指定请求request匹配的那些路径模板。
compareTo方法理解
针对指定的请求对象request发现有多个满足条件的,用来排序指定优先级,使用最优的进行响应
比如说V有1.0,1.1,1.2版本这个时候前端传入1.1那么就需要通过该方法来判断走哪个接口了。
在 Spring MVC 中,默认提供了下面几种
类 | 说明 |
---|---|
PatternsRequestCondition | 路径匹配,即 url |
RequestMethodsRequestCondition | 请求方法,注意是指 http 请求方法 |
ParamsRequestCondition | 请求参数条件匹配 |
HeadersRequestCondition | 请求头匹配 |
ConsumesRequestCondition | 可消费 MIME 匹配条件 |
ProducesRequestCondition | 可生成 MIME 匹配条件 |
RequestMappingHandlerMapping了解
戳我查看
这个作者写的还挺详细的,借用下😛
实现方式
好啦好啦,说了这么多,赶紧来看看实现方式吧。
首先肯定要先定义下注解啦
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiVersion {
/**
* 版本号
* 可与前端协商对应版本
* 目前我使用的版本格式如下
* x.x.x
* @return 版本号
*/
@AliasFor("version")
String value() default "1.0.0";
@AliasFor("value")
String version() default "1.0.0";
}
有注解了那我们怎么判断版本呢?定义一个判断用的Util吧,默认版本1.0.0
@Data
public class ApiVersionComparable implements Comparable<ApiVersionComparable> {
private int high = 1;
private int mid = 0;
private int low = 0;
@Override
public int compareTo(ApiVersionComparable 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;
}
}
前端传递过来的肯定是一个String那在让我们定义一个转换类吧
@UtilityClass
public class ApiVersionConverter {
/**
* 将x.x.x转换为对应参数
* @param version x.x.x
* @return ApiVersionComparable
*/
public ApiVersionComparable convert(String version) {
ApiVersionComparable apiItem = new ApiVersionComparable();
if (StringUtils.isBlank(version)) {
return apiItem;
}
String[] cells = StringUtils.split(version, ".");
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;
}
}
重头戏来啦,最核心的两个类
主要用来做判断,是使用哪个版本的接口
import cn.ideamake.common.util.api.version.ApiVersionComparable;
import cn.ideamake.common.util.api.version.ApiVersionConverter;
import lombok.AllArgsConstructor;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import javax.servlet.http.HttpServletRequest;
@AllArgsConstructor
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
private ApiVersionComparable version;
@Override
public ApiVersionCondition combine(ApiVersionCondition other) {
// 选择版本最大的接口
return version.compareTo(other.version) >= 0
? new ApiVersionCondition(version)
: new ApiVersionCondition(other.version);
}
@Override
public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
//获取前端传递的版本号
String version = request.getHeader("interface-version");
ApiVersionComparable item = ApiVersionConverter.convert(version);
// 获取所有小于等于版本的接口
if (item.compareTo(this.version) >= 0) {
return this;
}
return null;
}
@Override
public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
// 获取最大版本对应的接口
return other.version.compareTo(this.version);
}
}
用于注入spring用来管理。
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.lang.reflect.Method;
public class ApiVersionHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
return buildFrom(AnnotationUtils.findAnnotation(handlerType, ApiVersion.class));
}
@Override
protected RequestCondition<?> getCustomMethodCondition(Method method) {
return buildFrom(AnnotationUtils.findAnnotation(method, ApiVersion.class));
}
private ApiVersionCondition buildFrom(ApiVersion apiVersion) {
return apiVersion == null
? new ApiVersionCondition(new ApiVersionComparable())
: new ApiVersionCondition(ApiVersionConverter.convert(apiVersion.value()));
}
}
此时万事俱备只欠东风了。
东风跑不掉肯定是让我们的spring来接管了。写一个配置类。
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
@Configuration
public class ApiVersionAutoConfiguration implements WebMvcRegistrations {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new ApiVersionHandlerMapping();
}
}
搞定。
让我们来看看成果吧。
如果接口不声明@ApiVersion那么默认版本是1.0.0
切忌,版本号不能相同!!!!!!!!!!
接口调用时和前端约定版本号,让前端同学在请求头中塞入"interface-version"即可。
示例:
只要是没有更高版本这个时候请求头输入无论多高的版本,都会走我们最新版本接口。
对于版本范围而言,如上图,如果有2.0.0和2.2.0(2.2)版本,此时如果输入2.1则会找到2.0.0因为2.1还没有达到2.2.0的要求,如果输入2.2则会找到(2.2)版本因为已经达到需求啦~。
借鉴:https://segmentfault.com/a/1190000021437444
切忌版本号不能为空噢。
本文到此结束,如果对你有帮助请给博主一点鼓励,点个赞吧gie gie.