spring boot: 设计接口站api的版本号,支持次版本号(spring boot 2.3.2)

一,为什么接口站的api要使用版本号?

1,当服务端接口的功能发生改进后,

    客户端如果不更新版本,

   则服务端返回的功能可能不能使用,
   所以在服务端功能升级后,
    客户端也要相应的使用新版的服务端接口
 
 
2,注意点:不要频繁变更服务端接口站的版本
 
不管是新增/修改服务端功能,只要app旧版本可以兼容,
则服务端的版本号无需变动,因为这个版本和git的版本控制不一样,
它起的作用是比较客户端的哪些版本和服务端的哪些版本能兼容
只有客户端不能兼容时,才会新增版本号以便区分

3,版本号需要能向下兼容,

   如果访问一个高的版本不存在时,应该能访问到比它小的离它最近的版本,

   因为如果每个入口或每个功能在有新版本时都要重新写一遍,

   则和把代码复制一份没有两样了,

   这样才可以方便的管理    

4,版本号可以放到请求头和url中,

     我们采用比较直观的放到url中的形式演示,

     形如: /v1.2/home/page?

5,说明:生产环境中没发现有必要在次版本号后面再加一级,

           所以有次版本号后已经够用了,

           大家如果认为有必要增加一级的话,可以修改代码中VERSION_PREFIX_PATTERN的正则:

说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

         对应的源码可以访问这里获取: https://github.com/liuhongdi/

说明:作者:刘宏缔 邮箱: 371125307@qq.com

二,演示项目的相关信息

1,项目地址:

https://github.com/liuhongdi/apiversion

2,项目说明:

        我们给接口站的接口增加api的版本号

        支持整数方式(例:v1)和次版本号形式(例:v1.3)

3,项目结构:如图:

三,java代码说明

1,ApiVersion.java

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiVersion {
    //版本号的值,从1开始
    String value() default "1";
}

用来添加版本号的一个注解

2,ApiVersionCondition.java

//实现RequestCondition
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
    //api版本号
    private String apiVersion;
    //版本号的格式,如: /v[1-n]/api/test or /v1.5/home/api
    private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("/v((\d+\.\d+)|(\d+))/");
    public ApiVersionCondition(String apiVersion) {
        this.apiVersion = apiVersion;
    }

    //将不同的筛选条件进行合并
    @Override
    public ApiVersionCondition combine(ApiVersionCondition other) {
        // 采用最后定义优先原则,则方法上的定义覆盖类上面的定义
        return new ApiVersionCondition(other.getApiVersion());
    }

    //版本比对,用于排序
    @Override
    public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
        //优先匹配最新版本号
        return compareTo(other.getApiVersion(),this.apiVersion)?1:-1;
    }

    //获得符合匹配条件的ApiVersionCondition
    @Override
    public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
        Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI());
        if (m.find()) {
            String version = m.group(1);
            if (compareTo(version,this.apiVersion)){
                return this;
            }
        }
        return null;
    }
    //compare version
    private boolean compareTo(String version1,String version2){
        if (!version1.contains(".")) {
            version1 += ".0";
        }
        if (!version2.contains(".")) {
            version2 += ".0";
        }
        String[] split1 = version1.split("\.");
        String[] split2 = version2.split("\.");
        for (int i = 0; i < split1.length; i++) {
            if (Integer.parseInt(split1[i])<Integer.parseInt(split2[i])){
                return false;
            }
        }
        return true;
    }

    public String getApiVersion() {
        return apiVersion;
    }
}

实现查找和比较版本号的功能

3,ApiVersionRequestMappingHandlerMapping.java

//扩展RequestMappingHandlerMapping
public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

    //类上有 @ApiVersion注解时生效
    @Override
    protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
        ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
        return createRequestCondition(apiVersion);
    }

    //方法上有 @ApiVersion注解时生效
    @Override
    protected RequestCondition<?> getCustomMethodCondition(Method method) {
        ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
        return createRequestCondition(apiVersion);
    }
    
    //返回ApiVersionCondition
    private RequestCondition<ApiVersionCondition> createRequestCondition(ApiVersion apiVersion) {
        return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value());
    }
}

继承RequestMappingHandlerMapping,在类上和方法上有注解时,

使用ApiVersionCondition进行处理

4,WebMvcConfig.java

@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
    //在获取RequestMappingHandlerMapping时
    //返回我们自定义的ApiVersionRequestMappingHandlerMapping
    @Override
    protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
        return new ApiVersionRequestMappingHandlerMapping();
    }
}

把createRequestMappingHandlerMapping方法返回时使用我们自定义的ApiVersionRequestMappingHandlerMapping

5,HomeController.java

@RestController
@RequestMapping("/{version}/home")
public class HomeController {

    //匹配版本v1的访问
    @ApiVersion("1")
    @GetMapping
    @RequestMapping("/home")
    public String home01(@PathVariable String version) {
        return "home v1 : version:" + version;
    }

    //匹配版本v2的访问
    @ApiVersion("2.0")
    @GetMapping
    @RequestMapping("/home")
    public String home02(@PathVariable String version) {
        return "home v2 version: " + version;
    }

    //匹配版本v1.5-2.0的访问
    @ApiVersion("1.5")
    @GetMapping
    @RequestMapping("/home")
    public String home15(@PathVariable String version) {
        return "home v1.5 version: " + version;
    }
}

6,GoodsV1Controller和GoodsV2Controller

  把版本号加到controller上,

  和加在方法上的使用一样,

  为节省篇幅,不再贴代码,大家可以自己去github上访问

四,测试效果

1,访问

http://127.0.0.1:8080/v1.0/home/home

返回:

home v1 : version:v1.0

访问

http://127.0.0.1:8080/v2/home/home

返回:

home v2 version: v2

可见版本号带不带点,不妨碍它访问到对应的版本

2,访问:

http://127.0.0.1:8080/v3.0/home/home

返回:

home v2 version: v3.0

访问:

http://127.0.0.1:8080/v1.14/home/home

返回:

home v1.5 version: v1.14

可见版本号可以自动访问到距它最近的版本的功能

五, 查看spring boot的版本

  .   ____          _            __ _ _
 /\ / ___'_ __ _ _(_)_ __  __ _    
( ( )\___ | '_ | '_| | '_ / _` |    
 \/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.2.RELEASE)
原文地址:https://www.cnblogs.com/architectforest/p/13432869.html