Spring Boot 2.x 接口多版本

准备将现有的接口加上版本管理,兼容以前的版本。网上一调研,发现有很多示例,但是还是存在以下两个问题。
1.大部分使用Integer作为版本号,但是通常的版本号形式为v1.0.0,
2.版本号携带在header中,对接调用不清晰。
针对以上两个问题,做如下改造。

1.自定义接口版本注解ApiVersion

后面条件映射使用equals匹配,此处是否将String变为String[]应对多个版本使用同一代码的问题。

package com.yugioh.api.common.core.version;

import org.springframework.web.bind.annotation.Mapping;

import java.lang.annotation.*;

/**
 * 接口版本
 *
 * @author lieber
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface ApiVersion {
  
    String value() default "1.0.0";

}

2.请求映射条件ApiVersionCondition

package com.yugioh.api.common.core.version;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.mvc.condition.RequestCondition;

import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 版本控制
 *
 * @author lieber
 */
@AllArgsConstructor
@Getter
@Slf4j
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {

    private String version;

    private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile(".*v(\\d+(.\\d+){0,2}).*");

    public final static String API_VERSION_CONDITION_NULL_KEY = "API_VERSION_CONDITION_NULL_KEY";

    @Override
    public ApiVersionCondition combine(ApiVersionCondition other) {
        // 方法上的注解优于类上的注解
        return new ApiVersionCondition(other.getVersion());
    }

    @Override
    public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
        Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI());
        if (m.find()) {
            String version = m.group(1);
            if (this.compareTo(version)) {
                return this;
            }
        }
        // 将错误放在request中,可以在错误页面明确提示,此处可重构为抛出运行时异常
        request.setAttribute(API_VERSION_CONDITION_NULL_KEY, true);
        return null;
    }

    @Override
    public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
        return this.compareTo(other.getVersion()) ? 1 : -1;
    }

    private boolean compareTo(String version) {
        return Objects.equals(version, this.version);
    }
}

3.创建自定义匹配处理器ApiVersionRequestMappingHandlerMapping

网上大部分只重写了getCustomTypeCondition和getCustomMethodCondition方法。这里为了解决路由映射问题,重写getMappingForMethod方法,在路由中加入前缀{version},加入后路由变为/api/{version}/xxx

package com.yugioh.api.common.core.version;

import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Method;

/**
 * 自定义匹配处理器
 *
 * @author lieber
 */
public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

    private final static String VERSION_PREFIX = "{version}";

    @Override
    protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
        ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
        return createCondition(apiVersion);
    }

    @Override
    protected RequestCondition<?> getCustomMethodCondition(Method method) {
        ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
        return createCondition(apiVersion);
    }

    @Override
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        RequestMappingInfo requestMappingInfo = super.getMappingForMethod(method, handlerType);
        if (requestMappingInfo == null) {
            return null;
        }
        return createCustomRequestMappingInfo(method, handlerType, requestMappingInfo);
    }

    private RequestMappingInfo createCustomRequestMappingInfo(Method method, Class<?> handlerType, RequestMappingInfo requestMappingInfo) {
        ApiVersion methodApi = AnnotatedElementUtils.findMergedAnnotation(method, ApiVersion.class);
        ApiVersion handlerApi = AnnotatedElementUtils.findMergedAnnotation(handlerType, ApiVersion.class);
        if (methodApi != null || handlerApi != null) {
            return RequestMappingInfo.paths(VERSION_PREFIX).options(this.config).build().combine(requestMappingInfo);
        }
        return requestMappingInfo;
    }


    private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();


    private RequestCondition<ApiVersionCondition> createCondition(ApiVersion apiVersion) {
        return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value());
    }

}

4.使用ApiVersionConfig配置来决定是否开启多版本

package com.yugioh.api.common.core.version;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * 版本管理器配置
 *
 * @author lieber
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "api.config.version", ignoreInvalidFields = true)
public class ApiVersionConfig {

    /**
     * 是否开启
     */
    private boolean enable;

}

5.配置WebMvcRegistrations,启用自定义路由Mapping

package com.yugioh.api.common.core.version;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

/**
 * @author lieber
 */
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ApiWebMvcRegistrations implements WebMvcRegistrations {

    private final ApiVersionConfig apiVersionConfig;

    @Autowired
    public ApiWebMvcRegistrations(ApiVersionConfig apiVersionConfig) {
        this.apiVersionConfig = apiVersionConfig;
    }

    @Override
    public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
        if (apiVersionConfig.isEnable()) {
            return new ApiVersionRequestMappingHandlerMapping();
        }
        return null;
    }

}

至此我们就能在项目中愉快的使用@APIVersion来指定版本了,但是现在这个和swagger整合还会有问题,继续研究中…

参考资料:
1.https://www.jianshu.com/p/1389fdf20ee2
2.https://blog.csdn.net/gongjian1990/article/details/105533574

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值