SpringBoot API增加version版本号方式

 更新时间:2021-10-13 16:05:35   作者:佚名   我要评论(0)

目录SpringBoot 增加 API Version一、增加ApiVersion自定义注解二、新增RequestCondition自定义匹配条件三、重写RequestMappingHandlerMappi

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

页面测试效果:

在这里插入图片描述

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

您可能感兴趣的文章:
  • SpringBoot实现API接口多版本支持的示例代码
  • SpringBoot实现API接口的完整代码
  • maven如何动态统一修改版本号的方法步骤

相关文章

  • SpringBoot API增加version版本号方式

    SpringBoot API增加version版本号方式

    目录SpringBoot 增加 API Version一、增加ApiVersion自定义注解二、新增RequestCondition自定义匹配条件三、重写RequestMappingHandlerMappi
    2021-10-13
  • java中的JsonSerializer用法,前后端单位转换必备

    java中的JsonSerializer用法,前后端单位转换必备

    目录java中的JsonSerializer用法背景演示解决方法JackJson中自定义JsonSerializer使用1、自定义类继承JsonSerializer2、在实体类的字段上3、
    2021-10-13
  • 小程序中如何绘制思维导图详解

    小程序中如何绘制思维导图详解

    目录什么是思维导图?F6中如何绘制支付宝中 微信中 总结什么是思维导图? 思维导图(英语:mind map),又称脑图、心智地图、头脑风暴图、
    2021-10-13
  • 一篇文章教你实现VUE多个DIV,button绑定回车事件

    一篇文章教你实现VUE多个DIV,button绑定回车事件

    目前有个需求是这样的,点击确定按钮或键盘回车时执行操作,很多地方都需要用到。 试了几种方法均不行, 首先,我在div(button也一样)上
    2021-10-13
  • MySQL数据库必备之条件查询语句

    MySQL数据库必备之条件查询语句

    目录1、基本语法2、按条件表达式筛选3、按逻辑表达式筛选4、模糊查询5、安全等于1、基本语法 SELECT 查询列表 FROM 表名 WHERE 筛选表达
    2021-10-13
  • 基于@JsonSerialize和@JsonInclude注解使用方法

    基于@JsonSerialize和@JsonInclude注解使用方法

    目录@JsonSerialize和@JsonInclude注解@JsonSerialize使用步骤@JsonIncludeJSON @JsonSerialize 弃用问题解决方案@JsonSerialize和@JsonIncl
    2021-10-13
  • python爬虫实战steam加密逆向RSA登录解析

    python爬虫实战steam加密逆向RSA登录解析

    目录采集目标工具准备项目思路解析采集目标 网址:steam 工具准备 开发工具:pycharm 开发环境:python3.7, Windows10 使用工具包:reques
    2021-10-13
  • 中石化中石油充值API接口

    中石化中石油充值API接口

    话费充值接口文档接口版本:1.0 ―、引言1.1 文档概述本文档提供话费充值接口规范说明,提供一整套的完整的接入示例(http 接口)供商户参 考,可以帮助商户开发人员快速完成
    2021-10-09
  • 中石化中石油充值API接口

    中石化中石油充值API接口

    话费充值接口文档接口版本:1.0 ―、引言1.1 文档概述本文档提供话费充值接口规范说明,提供一整套的完整的接入示例(http 接口)供商户参 考,可以帮助商户开发人员快速完成
    2021-10-09
  • 三网话费API接口,稳定可靠,不卡单

    三网话费API接口,稳定可靠,不卡单

    话费充值接口文档接口版本:1.0 ―、引言1.1 文档概述本文档提供话费充值接口规范说明,提供一整套的完整的接入示例(http 接口)供商户参 考,可以帮助商户开发人员快速完成
    2021-10-09

最新评论