SpringBoot如何实现接口版本控制

网友投稿 415 2022-09-22


SpringBoot如何实现接口版本控制

目录SpringBoot 接口版本控制自定义一个版本号的注解接口ApiVersion.java版本号筛选器ApiVersionCondition版本号匹配拦截器配置WebMvcRegistrationsConfigSpringBoot 2.x 接口多版本1.自定义接口版本注解ApiVersion2.请求映射条件ApiVersionCondition3.创建自定义匹配处理器ApiVersionRequestMappingHandlerMapping4.使用ApiVersionConfig配置来决定是否开启多版本5.配置WebMvcRegistrations,启用自定义路由Mapping

SpringBoot 接口版本控制

一个系统在上线后会不断迭代更新,需求也会不断变化,有可能接口的参数也会发生变化,如果在原有的参数上直接修改,可能会影响到现有项目的正常运行,这时我们就需要设置不同的版本,这样即使参数发生变化,由于老版本没有变化,因此不会影响上线系统的运行。

这里我们选择使用带有一位小数的浮点数作为版本号,在请求地址末尾中带上版本号,大致的地址如:http://api/test/1.0,其中,1.0即代表的是版本号。具体做法请看代码

自定义一个版本号的注解接口ApiVersion.java

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

import java.lang.annotation.*;

/**

* 版本控制

* @author Zac

*/

@Target({ElementType.METHOD, ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Mapping

public @interface ApiVersion {

/**

* 标识版本号

* @return

*/

double value();

}

版本号筛选器ApiVersionCondition

import org.springframework.web.servlet.mvc.condition.RequestCondition;

import javax.servlet.http.HttpServletRequest;

import java.util.regex.Matcher;

import java.util.regex.Pattern;

/**

* 版本号匹配筛选器

* @author Zac

*/

public class ApiVersionCondition implements RequestCondition {

/**

* 路径中版本的正则表达式匹配, 这里用 /1.0的形式

*/

private static final Pattern VERSION_PREFIX_PATTERN = Pattern.compile("^\\S+/([1-9][.][0-9])$");

private double apiVersion;

public ApiVersionCondition(double apiVersion) {

this.apiVersion = apiVersion;

}

@Override

public ApiVersionCondition combine(ApiVersionCondition other) {

// 采用最后定义优先原则,则方法上的定义覆盖类上面的定义

return new ApiVersionCondition(other.getApiVersion());

}

@Override public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {

Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI());

if (m.find()) {

Double version = Double.valueOf(m.group(1));

// 如果请求的版本号大于配置版本号, 则满足

if (version >= this.apiVersion) {

return this;

}

}

return null;

}

@Override

public int compareTo(ApiVersionCondition other, HttpServletRequest request) {

// 优先匹配最新的版本号

return Double.compare(other.getApiVersion(), this.apiVersion);

}

public double getApiVersion() {

return apiVersion;

}

}

版本号匹配拦截器

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;

/**

* 版本号匹配拦截器

* @author Zac

*/

public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

@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);

}

private RequestCondition createCondition(ApiVersion apiVersion) {

return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value());

}

}

配置Webhttp://MvcRegistrationsConfig

import org.springframework.boot.SpringBootConfiguration;

import org.springframework.boot.autoconfigure.web.WebMvcRegistrationsAdapter;

import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

@SpringBootConfiguration

public class WebMvcRegistrationsConfig extends WebMvcRegistrationsAdapter {

@Override

public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {

return newhttp:// CustomRequestMappingHandlerMapping();

}

}

controller层实现

/**

* version

* @return

*/

@GetMapping("/api/test/{version}")

@ApiVersion(2.0)

public String searchTargetImage() {

return "Hello! Welcome to Version2";

}

/**

* 多目标类型搜索,关联图片查询接口

*

* @param attribute

* @return

*/

@GetMapping("/api/test/{version}")

@ApiVersion(1.0)

public AppResult searchTargetImage() {

return "Hello! Welcome to Version1";

}

SpringBoot 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 {

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 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整合还会有问题,继续研究中…


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:链路聚合LACP模式配置(lacp链路聚合作用)
下一篇:配置手工分担模式
相关文章

 发表评论

暂时没有评论,来抢沙发吧~