SpringBoot实现API接口的完整代码

 更新时间:2020年10月25日 21:53  点击:1915

一、简介

产品迭代过程中,同一个接口可能同时存在多个版本,不同版本的接口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<ApiItem> {
  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> {

 /**
 * 方法和类上都存在相同的条件时的处理方法
 */
 T combine(T other);

 /**
 * 判断是否符合当前请求,返回null表示不符合
 */
 @Nullable
 T getMatchingCondition(HttpServletRequest request);

 /**
 *如果存在多个符合条件的接口,则会根据这个来排序,然后用集合的第一个元素来处理
 */
 int compareTo(T other, HttpServletRequest request);

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

ApiCondition

@Slf4j
public class ApiCondition implements RequestCondition<ApiCondition> {

  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;
  }

  /**
   * <pre>
   *   Spring先扫描方法再扫描类,然后调用{@link #combine}
   *   按照方法上的注解优先级大于类上注解的原则处理,但是要注意如果方法上不定义注解的情况。
   *   如果方法或者类上不定义注解,我们会给一个默认的值{@code empty},{@link ApiHandlerMapping}
   * </pre>
   * @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带入,目的是可以在程序中选择性启用或者不启用版本控制。

三、原理解析

四、总结

到此这篇关于SpringBoot实现API接口的文章就介绍到这了,更多相关SpringBoot实现API接口内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

[!--infotagslink--]

相关文章

  • 解决springboot使用logback日志出现LOG_PATH_IS_UNDEFINED文件夹的问题

    这篇文章主要介绍了解决springboot使用logback日志出现LOG_PATH_IS_UNDEFINED文件夹的问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-28
  • c# 三种方法调用WebService接口

    这篇文章主要介绍了c# 三种方法调用WebService接口的相关资料,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下...2020-07-07
  • SpringBoot实现excel文件生成和下载

    这篇文章主要为大家详细介绍了SpringBoot实现excel文件生成和下载,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-02-09
  • 浅谈vue2的$refs在vue3组合式API中的替代方法

    这篇文章主要介绍了浅谈vue2的$refs在vue3组合式API中的替代方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-18
  • vue接口请求加密实例

    这篇文章主要介绍了vue接口请求加密实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-12
  • WebStorm无法正确识别Vue3组合式API的解决方案

    这篇文章主要介绍了WebStorm无法正确识别Vue3组合式API的解决方案,帮助大家更好的理解和学习使用vue框架,感兴趣的朋友可以了解下...2021-02-18
  • SpringBoot集成Redis实现消息队列的方法

    这篇文章主要介绍了SpringBoot集成Redis实现消息队列的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-10
  • 详解springBoot启动时找不到或无法加载主类解决办法

    这篇文章主要介绍了详解springBoot启动时找不到或无法加载主类解决办法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-09-16
  • 解决Springboot get请求是参数过长的情况

    这篇文章主要介绍了解决Springboot get请求是参数过长的情况,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-17
  • Springboot+TCP监听服务器搭建过程图解

    这篇文章主要介绍了Springboot+TCP监听服务器搭建过程,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-10-28
  • Spring Boot项目@RestController使用重定向redirect方式

    这篇文章主要介绍了Spring Boot项目@RestController使用重定向redirect方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-09-02
  • springBoot 项目排除数据库启动方式

    这篇文章主要介绍了springBoot 项目排除数据库启动方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-09-10
  • springboot中使用@Transactional注解事物不生效的坑

    这篇文章主要介绍了springboot中使用@Transactional注解事物不生效的原因,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-01-26
  • 详解SpringBoot之访问静态资源(webapp...)

    这篇文章主要介绍了详解SpringBoot之访问静态资源(webapp...),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-09-14
  • SpringBoot接口接收json参数解析

    这篇文章主要介绍了SpringBoot接口接收json参数解析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-10-19
  • C#简单了解接口(Interface)使用方法

    这篇文章主要介绍了C#简单了解接口(Interface)使用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-12-08
  • 如何使用 JavaScript 操作浏览器历史记录 API

    这篇文章主要介绍了如何使用 JavaScript 操作浏览器历史记录 API,帮助大家更好的理解和使用JavaScript,感兴趣的朋友可以了解下...2020-11-24
  • Feign接口方法返回值设置方式

    这篇文章主要介绍了Feign接口方法返回值设置方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-07-08
  • C# Rx的主要接口深入理解

    这篇文章主要介绍了C# Rx的主要接口深入理解的相关资料,需要的朋友可以参考下...2020-06-25
  • vue设置全局访问接口API地址操作

    这篇文章主要介绍了vue设置全局访问接口API地址操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-14