SpringBoot API增加version版本号方式

目录
  • SpringBoot 增加 API Version
    • 一、增加ApiVersion自定义注解
    • 二、新增RequestCondition自定义匹配条件
    • 三、重写RequestMappingHandlerMapping处理
    • 四、Controller接口增加@ApiVersion注解
    • 五、测试调用
    • 六、总结
  • SpringBoot的项目API版本控制
    • 一、自定义版本号标记注解
    • 二、重写RequestCondition,自定义url匹配逻辑
    • 三、重写RequestMappingHandlerMapping,自定义匹配的处理器
    • 四、配置注册自定义WebMvcRegistrations
    • 五、编写测试接口

SpringBoot 增加 API Version

基于restful风格上,增加version版本号

例如: get /api/v1/users/

一、增加ApiVersion自定义注解

作用于Controller上,指定API版本号

这里版本号使用了double ,考虑到小版本的情况,例如1.1

import java.lang.annotation.*;
/**
 * API Version type
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiVersion {
    /**
     * api version begin 1
     */
    double version() default 1;
}

二、新增RequestCondition自定义匹配条件

Spring提供RequestCondition接口,用于定义API匹配条件

这里通过自定义匹配条件,识别ApiVersion,进行版本匹配

getMatchingCondition 用于检查URL中,是否符合/v{版本号},用于过滤无版本号接口;

compareTo 用于决定多个相同API时,使用哪个接口进行处理;

import org.springframework.web.servlet.mvc.condition.RequestCondition;
import javax.servlet.http.HttpServletRequest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * API version condition
 * @author w
 * @date 2020-11-16
 */
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
    /**
     * 接口路径中的版本号前缀,如: api/v[1-n]/test
     */
    private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("/v([0-9]+\\.{0,1}[0-9]{0,2})/");
    /** API VERSION interface **/
    private ApiVersion apiVersion;
    ApiVersionCondition(ApiVersion apiVersion){
        this.apiVersion = apiVersion;
    }
    /**
     * [当class 和 method 请求url相同时,触发此方法用于合并url]
     * 官方解释:
     * - 某个接口有多个规则时,进行合并
     * - 比如类上指定了@RequestMapping的 url 为 root
     * - 而方法上指定的@RequestMapping的 url 为 method
     * - 那么在获取这个接口的 url 匹配规则时,类上扫描一次,方法上扫描一次,这个时候就需要把这两个合并成一个,表示这个接口匹配root/method
     * @param other 相同api version condition
     * @return ApiVersionCondition
     */
    @Override
    public ApiVersionCondition combine(ApiVersionCondition other) {
        // 此处按优先级,method大于class
        return new ApiVersionCondition(other.getApiVersion());
    }
    /**
     * 判断是否成功,失败返回 null;否则,则返回匹配成功的条件
     * @param httpServletRequest http request
     * @return 匹配成功条件
     */
    @Override
    public ApiVersionCondition getMatchingCondition(HttpServletRequest httpServletRequest) {
        // 通过uri匹配版本号
        System.out.println(httpServletRequest.getRequestURI());
        Matcher m = VERSION_PREFIX_PATTERN.matcher(httpServletRequest.getRequestURI());
        if (m.find()) {
            // 获得符合匹配条件的ApiVersionCondition
            System.out.println("groupCount:"+m.groupCount());
            double version = Double.valueOf(m.group(1));
            if (version >= getApiVersion().version()) {
                return this;
            }
        }
        return null;
    }
    /**
     * 多个都满足条件时,用来指定具体选择哪一个
     * @param other 多个时
     * @param httpServletRequest http request
     * @return 取版本号最大的
     */
    @Override
    public int compareTo(ApiVersionCondition other, HttpServletRequest httpServletRequest) {
        // 当出现多个符合匹配条件的ApiVersionCondition,优先匹配版本号较大的
        return other.getApiVersion().version() >= getApiVersion().version() ? 1 : -1;
    }
    public ApiVersion getApiVersion() {
        return apiVersion;
    }
}

三、重写RequestMappingHandlerMapping处理

通过重写 RequestMappingHandlerMapping 类,对RequestMappering进行识别@ApiVersion注解,针对性处理;

这里考虑到有些接口不存在版本号,则使用Spring原来的ApiVersionRequestMappingHandlerMapping继续处理;

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;
/**
 * API version setting
 * @author w
 * @date 2020-11-15
 */
public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
    /**
     * class condition
     * - 在class上加@ApiVersion注解&url加{version}
     * @param handlerType class type
     * @return ApiVersionCondition
     */
    @Override
    protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
        ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType,ApiVersion.class);
        return null == apiVersion ? super.getCustomTypeCondition(handlerType) : new ApiVersionCondition(apiVersion);
    }
    /**
     * method condition
     * - 在方法上加@ApiVersion注解&url加{version}
     * @param method method object
     * @return ApiVersionCondition
     */
    @Override
    protected RequestCondition<?> getCustomMethodCondition(Method method) {
        ApiVersion apiVersion = AnnotationUtils.findAnnotation(method,ApiVersion.class);
        return null == apiVersion ? super.getCustomMethodCondition(method) : new ApiVersionCondition(apiVersion);
    }
}

四、Controller接口增加@ApiVersion注解

通过@ApiVersion注解指定该接口版本号

import com.panda.common.web.controller.BasicController;
import com.panda.common.web.version.ApiVersion;
import com.panda.core.umc.service.UserInfoService;
import com.panda.core.umc.vo.QueryUsersConditionVo;
import com.panda.face.umc.dto.user.QueryUsersReq;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
 * 用户信息服务
 * @author w
 * @date 2020-11-06
 */
@RequestMapping("/api")
@RestController
public class UserInfoController extends BasicController{
    @Autowired
    private UserInfoService userInfoService;
    /**
     * 查询所有用户信息
     * @param req 查询条件信息
     */
    @ApiVersion
    @RequestMapping(value = "{version}/users", method = RequestMethod.GET)
    @ResponseBody
    public ResponseEntity getUsers(@PathVariable("version") String version, QueryUsersReq req){
        QueryUsersConditionVo condition = new QueryUsersConditionVo();
        BeanUtils.copyProperties(req,condition);
        condition.setOrderBy("CREATE_TIME");
        condition.setSort("DESC");
        return assemble("1111");
    }
    /**
     * 查询所有用户信息
     * @param req 查询条件信息
     */
    @ApiVersion(version = 1.1)
    @RequestMapping(value = "{version}/users", method = RequestMethod.GET)
    @ResponseBody
    public ResponseEntity getUsersV2(@PathVariable("version") String version, QueryUsersReq req){
        QueryUsersConditionVo condition = new QueryUsersConditionVo();
        BeanUtils.copyProperties(req,condition);
        condition.setOrderBy("CREATE_TIME");
        condition.setSort("DESC");
        return assemble("222");
    }
    /**
     * 根据用户ID获取用户信息
     * @param userId 用户ID
     */
    @RequestMapping(value = "/users/uid/{userId}", method = RequestMethod.GET)
    @ResponseBody
    public ResponseEntity getUserInfo(@PathVariable("userId") String userId){
        return assemble(userInfoService.selectByUserId(userId));
    }
}

五、测试调用

通过访问以下URL,测试返回结果

GET http://127.0.0.1/api/v1/users

GET http://127.0.0.1/api/v1.1/users

GET http://127.0.0.1/api/v1.2/users

GET http://127.0.0.1/api/users/uid/U0001

六、总结

1.通过@ApiVersion注解方式,可以灵活指定接口版本;

2.缺点很明显,需要在URL上加入{version},才能进行匹配成功,这种PathVariable识别过于模糊,后期排查问题增加困难;

3.建议通过包名增加v1/v2明显区分版本,且在controller的URL上直接写死v1版本号,这种更直观;

SpringBoot的项目API版本控制

一、自定义版本号标记注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiVersion {
    /**
     * 标识版本号,从1开始
     */
    int value() default 1;
}

二、重写RequestCondition,自定义url匹配逻辑

@Data
@Slf4j
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
    /**
     * 接口路径中的版本号前缀,如: api/v[1-n]/fun
     */
    private final static Pattern VERSION_PREFIX = Pattern.compile("/v(\\d+)/");
    private int apiVersion;
    ApiVersionCondition(int apiVersion) {
        this.apiVersion = apiVersion;
    }
    /**
     * 最近优先原则,方法定义的 @ApiVersion > 类定义的 @ApiVersion
     */
    @Override
    public ApiVersionCondition combine(ApiVersionCondition other) {
        return new ApiVersionCondition(other.getApiVersion());
    }
    /**
     * 获得符合匹配条件的ApiVersionCondition
     */
    @Override
    public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
        Matcher m = VERSION_PREFIX.matcher(request.getRequestURI());
        if (m.find()) {
            int version = Integer.valueOf(m.group(1));
            if (version >= getApiVersion()) {
                return this;
            }
        }
        return null;
    }
    /**
     * 当出现多个符合匹配条件的ApiVersionCondition,优先匹配版本号较大的
     */
    @Override
    public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
        return other.getApiVersion() - getApiVersion();
    }
}

说明:

getMatchingCondition方法中,控制了只有版本小于等于请求参数中的版本的 ApiCondition 才满足规则

compareTo 指定了当有多个ApiCoondition满足这个请求时,选择最大的版本

三、重写RequestMappingHandlerMapping,自定义匹配的处理器

public class ApiRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
    @Override
    protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
        // 扫描类上的 @ApiVersion
        ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
        return createRequestCondition(apiVersion);
    }
    @Override
    protected RequestCondition<?> getCustomMethodCondition(Method method) {
        // 扫描方法上的 @ApiVersion
        ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
        return createRequestCondition(apiVersion);
    }
    private RequestCondition<ApiVersionCondition> createRequestCondition(ApiVersion apiVersion) {
        if (Objects.isNull(apiVersion)) {
            return null;
        }
        int value = apiVersion.value();
        Assert.isTrue(value >= 1, "Api Version Must be greater than or equal to 1");
        return new ApiVersionCondition(value);
    }
}

四、配置注册自定义WebMvcRegistrations

@Configuration
public class WebMvcRegistrationsConfig implements WebMvcRegistrations {
    @Override
    public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
        return new ApiRequestMappingHandlerMapping();
    }
}

五、编写测试接口

@RestController
@RequestMapping("/api/{version}")
public class ApiControler {
    @GetMapping("/fun")
    public String fun1() {
        return "fun 1";
    }
    @ApiVersion(5)
    @GetMapping("/fun")
    public String fun2() {
        return "fun 2";
    }
    @ApiVersion(9)
    @GetMapping("/fun")
    public String fun3() {
        return "fun 5";
    }
}

页面测试效果:

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

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

    一.简介 产品迭代过程中,同一个接口可能同时存在多个版本,不同版本的接口URL.参数相同,可能就是内部逻辑不同.尤其是在同一接口需要同时支持旧版本和新版本的情况下,比如APP发布新版本了,有的用户可能不选择升级,这是后接口的版本管理就十分必要了,根据APP的版本就可以提供不同版本的接口. 二.代码实现 本文的代码实现基于SpringBoot 2.3.4-release 1.定义注解 ApiVersion @Target({ElementType.TYPE, ElementType.METHOD}

  • maven如何动态统一修改版本号的方法步骤

    前言 最近业务开发部门因为开发环境和测试环境共用一个maven私仓,导致他们开发环境的API包和测试环境的API包发生了覆盖现象.于是他们向我们部门提出一个需求,希望我们能帮他们实现或者提供这么一个方案,就是项目自动化构建时,项目的版本号能跟着环境变更.比如是开发环境,则项目的API包版本就形如1.0-dev,如果是测试环境,则项目的API版本就形如1.0-test 示例演示 项目层级如下 方案一:mvn -Denv.project.version=1.0-env 注:env.project.v

  • SpringBoot实现API接口的完整代码

    一.简介 产品迭代过程中,同一个接口可能同时存在多个版本,不同版本的接口URL.参数相同,可能就是内部逻辑不同.尤其是在同一接口需要同时支持旧版本和新版本的情况下,比如APP发布新版本了,有的用户可能不选择升级,这是后接口的版本管理就十分必要了,根据APP的版本就可以提供不同版本的接口. 二.代码实现 本文的代码实现基于SpringBoot 2.3.4-release 1.定义注解 ApiVersion @Target({ElementType.TYPE, ElementType.METHOD}

  • SpringBoot API增加version版本号方式

    目录 SpringBoot 增加 API Version 一.增加ApiVersion自定义注解 二.新增RequestCondition自定义匹配条件 三.重写RequestMappingHandlerMapping处理 四.Controller接口增加@ApiVersion注解 五.测试调用 六.总结 SpringBoot的项目API版本控制 一.自定义版本号标记注解 二.重写RequestCondition,自定义url匹配逻辑 三.重写RequestMappingHandlerMappi

  • 关于Springboot日期时间格式化处理方式总结

    项目中使用LocalDateTime系列作为DTO中时间的数据类型,但是SpringMVC收到参数后总报错,为了配置全局时间类型转换,尝试了如下处理方式. 注:本文基于Springboot2.x测试,如果无法生效可能是spring版本较低导致的.PS:如果你的Controller中的LocalDate类型的参数啥注解(RequestParam.PathVariable等)都没加,也是会出错的,因为默认情况下,解析这种参数是使用ModelAttributeMethodProcessor进行处理,而

  • SpringBoot配置 Druid 三种方式(包括纯配置文件配置)

    记录一下在项目中用纯 YML(application.yml 或者 application.properties)文件.Java 代码配置 Bean 和注解三种方式配置 Alibaba Druid 用于监控或者查看 SQL 状况: 1. 纯配置文件 .yml 或者 .properties (1)pom.xml 添加相关依赖 <!-- SPRINGBOOT WEB --> <dependency> <groupId>org.springframework.boot<

  • springboot整合多数据源配置方式

    目录 简介 一.表结构 二.多数据源整合 1. springboot+mybatis使用分包方式整合 1.1 主要依赖包 1.2 application.yml 配置文件 1.3 建立连接数据源的配置文件 1.4 具体实现 2. springboot+druid+mybatisplus使用注解整合 2.1 主要依赖包 2.2 application.yml 配置文件 2.3 给使用非默认数据源添加注解@DS 简介 主要介绍两种整合方式,分别是 springboot+mybatis 使用分包方式整

  • springboot的四种启动方式

    目录 环境准备 第一种:直接main方法启动TxDemo2Application 第二种:通过maven插件来启动 第三种打jar包来访问 第四种通过docker容器虚拟化运行 环境准备 创建工程 pom.xml内容 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="h

  • springboot配置文件属性变量引用方式${}和@@用法及区别说明

    目录 配置文件属性变量引用${}和@@用法 ${}常用于pom.xml @@方式常用于引用springboot非默认配置文件 配置文件中的“@”问题 springboot配置文件中的${…}和@…@ 起因 原因 解决 配置文件属性变量引用${}和@@用法 ${}和@@都是springboot引用属性变量的方式 具体区别与用法: ${}常用于pom.xml 和 src/main/resources/application.properties等默认配置文件的属性变量引用. 语法为:field_na

  • Java与SpringBoot对redis的使用方式

    目录 1.Java连接redis 1.1 使用Jedis 1.2 使用连接池连接redis 1.3 java连接redis集群模式 2.SpringBoot整合redis 2.1 StringRedisTemplate 2.2 RedisTemplate 1.Java连接redis redis支持哪些语言可以操作 (去redis官网查询) 1.1 使用Jedis  (1)添加jedis依赖 <dependency> <groupId>junit</groupId> &l

  • SpringBoot统一功能处理的方式详解

    目录 SpringMVC统一处理的三种方式 1.基于SpringMVC的配置类扩展 2.统一的响应数据格式封装 3.统一异常处理 基于SpringAOP已经实现统一功能增强,但如果希望对Controller增强,就无法获取其中的http请求数据.因此,实现以下这些统一增强的业务,就不能使用SpringAOP: 响应数据统一封装 统一异常处理(返回错误信息的http响应) http请求日志记录 SpringMVC统一处理的三种方式 SpringMVC在SpringBoot项目中,是默认进行了配置,

  • MYSQL 增加从库方式介绍

    目录 一.MySQL主从复制 实现细节 二.增加一个slave 一.MySQL主从复制 常见的主从架构: 一主一从:一个 Master,一个 Slave 一主多从:一个 Master,多个 Slave 具体,参考下图: 实现细节 MySQL 在主从同步时,其底层实现细节又是什么?为此后分析主从延迟原因以及优化方案,做好理论准备. 总结来说,MySQL 的主从复制:异步单线程. Master上 1 个IO线程,负责向Slave传输 binary log(binlog) Slave上 2 个线程:I

  • JS中封装axios来管控api的2种方式

    前言:我们在开发项目的时候,往往要处理大量的接口.并且在测试环境 开发环境 生产环境使用的接口baseurl都不一样 这时候如果在开发环境完成之后切换每一个接口的baseurl会变的非常的麻烦,(要去每一个发出请求的页面都要去修改地址) 所以为了更好的管控这些api,我们需要自己封装一个axios定义统一的接口baseurl 这样在环境的切换的时候更好的管控和修改.话不多说上代码!!! 自己创建一个api文件夹 即可 import axios from 'axios' 为了处理java字符串问题

随机推荐