SpringBoot API增加version版本号

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版本号,这种更直观;

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
好的,以下是一个简单的 SpringBoot 实现 API 接口多版本支持的示例代码: 首先,在 pom.xml 文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.6</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.11.2</version> </dependency> ``` 然后,创建一个 Controller 类,示例代码如下: ```java @RestController @RequestMapping("/api/v{version}") public class ApiController { @GetMapping("/hello") public String hello(@PathVariable String version) { return "Hello from API version " + version; } } ``` 在上述示例代码中,我们使用了 `@RequestMapping` 注解来指定 API 接口的版本,即 `/api/v{version}`,其中 `{version}` 是一个占位符,表示版本号,例如 `/api/v1`。 接下来,我们需要创建一个拦截器来判断访问的 API 接口版本号,示例代码如下: ```java @Component public class ApiVersionInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String version = request.getHeader("version"); if (StringUtils.isEmpty(version)) { version = "1"; } request.setAttribute("version", version); return true; } } ``` 在上述示例代码中,我们通过 `HttpServletRequest` 获取请求头中的 `version` 参数,并将其存储在请求属性中,以便后续的 Controller 中可以获取到。 最后,我们需要在 SpringBoot 的配置文件中配置拦截器,示例代码如下: ```java @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Autowired private ApiVersionInterceptor apiVersionInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(apiVersionInterceptor).addPathPatterns("/api/**"); } } ``` 在上述示例代码中,我们通过 `addInterceptor` 方法将拦截器添加到拦截器链中,并指定需要拦截的 API 接口路径为 `/api/**`。 现在,我们就可以在 Controller 中通过 `@PathVariable` 注解获取请求路径中的版本号,示例代码如下: ```java @RestController @RequestMapping("/api") public class ApiController { @GetMapping("/hello") public String hello(@PathVariable String version) { return "Hello from API version " + version; } } ``` 至此,我们就完成了 SpringBoot 实现 API 接口多版本支持的示例代码。当客户端访问 `/api/v1/hello` 时,将返回 `Hello from API version 1`,当访问 `/api/v2/hello` 时,将返回 `Hello from API version 2`。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值