1. 说明 随着业务不断的升级优化,会导致新老版本的api入参和出参不一致。在实际场景中又不能每次修改接口都要求客户端强制升级,这样会使用户体验变得很差。而多版本接口的设计就是为了兼容新老版本的差异,从而提高用户体验。
2. 多版本策略
管理策略
示例
域名区分
v1.api.com v2.api.com
请求url path
/v1/test/index /v2/test/index
请求参数区分
/test/index?v=v1 /test/index?v=v2
3. 请求(url path)实现
基于SpringBoot(2.3.5.RELEASE)
实现;
3.1 自定义版本注解 新建文件: src/main/java/com/hui/apiversion/annotion/ApiVersion.java
package com.hui.apiversion.annotion;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface ApiVersion { int value () default 1 ; }
3.2 自定义匹配URL 新建文件: src/main/java/com/hui/apiversion/config/ApiVersionCondition.java
package com.hui.apiversion.config;import lombok.Data;import org.springframework.web.servlet.mvc.condition.RequestCondition;import javax.servlet.http.HttpServletRequest;import java.util.regex.Matcher;import java.util.regex.Pattern;@Data public class ApiVersionCondition implements RequestCondition <ApiVersionCondition> { private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("v(\\d+)" ); private int apiVersion; public ApiVersionCondition (int apiVersion) { this .apiVersion = apiVersion; } @Override public ApiVersionCondition combine (ApiVersionCondition apiVersionCondition) { return new ApiVersionCondition (apiVersionCondition.getApiVersion()); } @Override public ApiVersionCondition getMatchingCondition (HttpServletRequest httpServletRequest) { Matcher matcher = VERSION_PREFIX_PATTERN.matcher(httpServletRequest.getRequestURI()); if (matcher.find()) { int version = Integer.parseInt(matcher.group(1 )); if (version >= this .apiVersion) { return this ; } } return null ; } @Override public int compareTo (ApiVersionCondition apiVersionCondition, HttpServletRequest httpServletRequest) { return apiVersionCondition.getApiVersion() - this .apiVersion; } }
3.3 自定义匹配处理器 新建文件: src/main/java/com/hui/apiversion/config/ApiVersionConfig.java
package com.hui.apiversion.config;import com.hui.apiversion.annotion.ApiVersion;import org.springframework.context.annotation.Configuration;import org.springframework.core.annotation.AnnotationUtils;import org.springframework.format.support.FormattingConversionService;import org.springframework.web.accept.ContentNegotiationManager;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;import org.springframework.web.servlet.mvc.condition.RequestCondition;import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;import org.springframework.web.servlet.resource.ResourceUrlProvider;import java.lang.reflect.Method;@Configuration public class ApiVersionConfig extends WebMvcConfigurationSupport { @Override public RequestMappingHandlerMapping requestMappingHandlerMapping (ContentNegotiationManager contentNegotiationManager, FormattingConversionService conversionService, ResourceUrlProvider resourceUrlProvider) { CustomRequestMappingHandlerMapping customRequestMappingHandlerMapping = new CustomRequestMappingHandlerMapping (); customRequestMappingHandlerMapping.setOrder(1 ); return customRequestMappingHandlerMapping; } private static 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<ApiVersionCondition> createCondition (ApiVersion apiVersion) { return apiVersion == null ? null : new ApiVersionCondition (apiVersion.value()); } } @Override protected void addResourceHandlers (ResourceHandlerRegistry registry) { super .addResourceHandlers(registry); if (!registry.hasMappingForPattern("/**" )) { registry.addResourceHandler("/**" ) .addResourceLocations( "classpath:/META-INF/resources/" , "classpath:/resources/" , "classpath:/static/" , "classpath:/public/" ); } } }
3.4 创建controller 新建(v1)
版本控制器: src/main/java/com/hui/apiversion/controller/v1/TestController.java
package com.hui.apiversion.controller.v1;import com.hui.apiversion.annotion.ApiVersion;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController("TestControllerV1") @ApiVersion @RequestMapping("{version}/test") public class TestController { @RequestMapping("/index") public String index () { return "v1 - index -> " + System.currentTimeMillis(); } @RequestMapping("/extend") public String extend () { return "v1 - extend -> " + System.currentTimeMillis(); } }
新建(v2)
版本控制器: src/main/java/com/hui/apiversion/controller/v2/TestController.java
package com.hui.apiversion.controller.v2;import com.hui.apiversion.annotion.ApiVersion;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController("TestControllerV2") @ApiVersion(2) @RequestMapping("{version}/test") public class TestController { @RequestMapping("index") public String index () { return "v2 - index -> " + System.currentTimeMillis(); } }
当控制器名字一样时,需要通过@RestController("?")
来设置唯一值,否则报错: ...Annotation-specified bean name 'testController' for bean class .. conflicts with existing, non-compatible bean definition of same name and class ...
3.5 请求测试
4.查看源码 上述代码地址(https://github.com/java-item/api-version)