SpringBoot实现API接口多版本支持的示例代码

网友投稿 372 2022-11-16


SpringBoot实现API接口多版本支持的示例代码

一、简介

产品迭代过程中,同一个接口可能同时存在多个版本,不同版本的接口URL、参数相同,可能就是内部逻辑不同。尤其是在同一接口需要同时支持旧版本和新版本的情况下,比如APP发布新版本了,有的用户可能不选择升级,这是后接口的版本管理就十分必要了,根据APP的版本就可以提供不同版本的接口。

二、代码实现

本文的代码实现基于SpringBoot 2.3.4-release

1.定义注解

ApiVersion

@Target({ElementType.TYPE, ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface ApiVersion {

/**

* 版本。x.y.z格式

*

* @return

*/

String value() default "1.0.0";

}

value值默认为1.0.0

EnableApiVersion

/**

* 是否开启API版本控制

*/

@Target(ElementType.TYPE)

@Documented

@Retention(RetentionPolicy.RUNTIME)

@Import(ApiAutoConfiguration.class)

public @interface EnableApiVersion {

}

在启动类上添加这个注解后就可以开启接口的多版本支持。使用Import引入配置ApiAutoConfiguration。

2.将版本号抽象为ApiItem类

ApiItem

@Data

public class ApiItem implements Comparable {

private int high = 1;

private int mid = 0;

private int low = 0;

public static final ApiItem API_ITEM_DEFAULT = ApiConverter.convert(ApiVersionConstant.DEFAULT_VERSION);

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;

}

}

为了比较版本号的大小,实现Comparable接口并重写compareTo(),从高位到低位依次比较。

ApiConverter

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;

}

}

ApiConverter提供静态方法将字符创转为ApiItem。

常量类,定义请求头及默认版本号

public class ApiVersionConstant {

/**

* header 指定版本号请求头

*/

public static final String API_VERSION = "x-api-version";

/**

* 默认版本号

*/

public static final String DEFAULT_VERSION = "1.0.0";

}

3.核心ApiCondition 新建ApiCondition类,实现RequestCondition,重写combine、getMatchingCondition、compareTo方法。

RequestCondition

public interface RequestCondition {

/**

* 方法和类上都存在相同的条件时的处理方法

*/

T combine(T other);

/**

* 判断是否符合当前请求,返回null表示不符合

*/

@Nullable

T getMatchingCondition(HttpServletRequest request);

/**

*如果存在多个符合条件的接口,则会根据这个来排序,然后用集合的第一个元素来处理

*/

int compareTo(T other, HttpServletRequest request);

以上对RequestCondition简要说明,后续详细源码分析各个方法的作用。

ApiCondition

@Slf4j

public class ApiCondition implements RequestCondition {

public static ApiCondition empty = new ApiCondition(ApiConverter.convert(ApiVersionConstant.DEFAULT_VERSION));

private ApiItem version;

private boolean NULL;

public ApiCondition(ApiItem item) {

this.version = item;

}

public ApiCondition(ApiItem item, boolean NULL) {

this.version = item;

this.NULL = NULL;

}

/**

*

* Spring先扫描方法再扫描类,然后调用{@link #combine}

* 按照方法上的注解优先级大于类上注解的原则处理,但是要注意如果方法上不定义注解的情况。

* 如果方法或者类上不定义注解,我们会给一个默认的值{@code empty},{@link ApiHandlerMapping}

*

* @param other 方法扫描封装结果

* @return

*/

@Override

public ApiCondition combine(ApiCondition other) {

// 选择版本最大的接口

if (other.NULL) {

return this;

}

return other;

}

@Override

public ApiCondition getMatchingCondition(HttpServletRequest request) {

if (CorsUtils.isPreFlightRequest(request)) {

return empty;

}

String version = request.getHeader(ApiVersionConstant.API_VERSION);

// 获取所有小于等于版本的接口;如果前端不指定版本号,则默认请求1.0.0版本的接口

if (StringUtils.isBlank(version)) {

log.warn("未指定版本,使用默认1.0.0版本。");

version = ApiVersionConstant.DEFAULT_VERSION;

}

ApiItem item = ApiConverter.convert(version);

if (item.compareTo(ApiItem.API_ITEM_DEFAULT) < 0) {

throw new IllegalArgumentException(String.format("API版本[%s]错误,最低版本[%s]", version, ApiVersionConstant.DEFAULT_VERSION));

}

if (item.compareTo(this.version) >= 0) {

return this;

}

return null;

}

@Override

public int compareTo(ApiCondition other, HttpServletRequest request) {

// 获取到多个符合条件的接口后,会按照这个排序,然后get(0)获取最大版本对应的接口.自定义条件会最后比较

int compare = other.version.compareTo(this.version);

if (compare == 0) {

log.warn("RequestMappingInfo相同,请检查!version:{}", other.version);

}

return compare;

}

}

3.配置类注入容器

ApiHandlerMapping

public class ApiHandlerMapping 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 ApiCondition buildFrom(ApiVersion platform) {

return platform == null ? getDefaultCondition() :

new ApiCondition(ApiConverter.convert(platform.value()));

}

private ApiCondition getDefaultCondition(){

return new ApiCondition(ApiConverter.convert(ApiVersionConstant.DEFAULT_VERSION),true);

}

}

ApiAutoConfiguration

public class ApiAutoConfiguration implements WebMvcRegistrations {

@Override

public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {

return new ApiHandlerMapping();

}

}

ApiAutoConfiguration没有使用Configuration自动注入,而是使用Import带入,目的是可以在程序中选择性启用或者不启用版本控制。

总结


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Mybatis一对一延迟加载实现过程解析
下一篇:BMIDE环境导入项目报编码错误解决方案
相关文章

 发表评论

暂时没有评论,来抢沙发吧~