java 单机接口限流处理方案
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
ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
return createCondition(apiVersion);
}
@Override protected RequestCondition
ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
return createCondition(apiVersion);
}
private RequestCondition
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
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小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~