#RESTful API版本控制实施方案 ## 1、为什么要做API版本控制 - 随业务发展,需求的更变,原有API逻辑处理,返回数据等的处理已不再适应于当前需求,改动在所难免。为避免API接口变化给使用者带来的影响,我们有需要区分不同版本的API接口处理 ## 2、怎么做? 以下是主流的方式: - #### url路径携带版本号 例如: https://1hjz.3ncto.com.cn/v2/users 好处:能直观展示当前版本号。使用者能方便的调用不同版本API,直接看到效果 坏处:不优雅,违背RESTful架构原则,添加版本号会混淆版本和资源的概念,使得架构会比较混乱。增加日后维护成本 - #### 请求参数传递版本号 例如: https://1hjz.3ncto.com.cn/v2/users?version=v2.3.0 ​ - #### 自定义请求头添加版本号(推荐) 在HTTP请求的header中使用自定义header标识版本。 例如: ````http Request Header Accept: application/json, text/javascript, */*; q=0.01 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Connection: keep-alive Host: disqus.com Content-Type: application/json version: 2.3.0 ```` 好处: 遵循RESTful API设计风格。请求url不发生改变。 坏处:版本不直观,需要能设置header的client才能调用查看该API的效果。 ## 3、实施方案 ###3.1 原理 扩展SpringBoot配置。通过我们自定义请求映射处理器, 匹配比较执行相应方法上。 实现效果:当请求接口带有版本号时,通过版本号的比较,执行相应版本。假若当前版本有v2.1.0,v2.3.0。 - 不带版本号,执行默认版本。 - 请求version:1.1.0,找不到标识版本,将执行默认版本。 - 请求version:2.1.0,执行的是2.1.0,执行相应版本。 - 请求version:2.2.0,执行的是2.1.0,没有当前对应版本,将使用当前版本号下的最新版本。 - 请求version:3.2.0,执行的是2.3.0,没有当前对应版本,将使用当前版本号下的最新版本。 ##3.2 java实现方案 项目目录结构,以1hjz为例 ```text src.main.java |-com.midou.tech |- common |- apiVersion API版本配置 |- modules |- admin |- controller |- web |- AdminBookController 原API接口文件 |- version |- v2_3_0 |- AdminBookControllerV230 v2.3.0版本API接口文件 |- service |- version |- v230 |- impl |- BookServiceImplV230 v2.3.0版本业务逻辑处理 |- BookServiceV230 |- impl |- AdminBookServiceImpl |- AdminBookService ``` #### 3.2.1 实现自定义请求映射处理器 ```java @Configuration public class MyWebMvcRegistrations implements WebMvcRegistrations { @Override public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { return new ApiVersionRequestMappingHandlerMapping(); } } ``` ApiVersionRequestMappingHandlerMapping ```java public class ApiVersionRequestMappingHandlerMapping 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()); } } ``` #### 3.2.2 实现自定义匹配比较器 ```java /** * 自定义匹配比较器 */ public class ApiVersionCondition implements RequestCondition { /** * 将不同筛选条件合并 * @param other * @return */ @Override public ApiVersionCondition combine(ApiVersionCondition other) { ... } /** * 根据request查找匹配到的筛选条件 * @param request * @return */ @Override public ApiVersionCondition getMatchingCondition(HttpServletRequest request) { ... } /** * 不同筛选条件比较,用于比较 * @param other * @param request * @return */ @Override public int compareTo(ApiVersionCondition other, HttpServletRequest request) { ... } } ``` #### 3.1.3 实现自定义注解@apiVersion 使用@apiVersion标识方法版本号。通过匹配规则的获取@apiVersion标识的版本号,比较筛选符合的方法 ```java @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Mapping public @interface ApiVersion { String value() default ""; } ``` ##4、使用 #### controller层 原API接口 ```java @Api(description = "运营后台预约相关接口") @RequestMapping(value = "/admin/web/book") @RestController public class AdminBookController extends AdminWebBaseController { @GetMapping(value = "/card/list") public JSONObject getServiceCardList() { ... } } ``` 版本控制API命名 原controller名称+版本号 示例:AdminBookControllerV2_3_0 ```java @Api(description = "运营后台预约相关接口") @RequestMapping(value = "/admin/web/book") @RestController public class AdminBookControllerV2_3_0 extends AdminWebBaseController { @ApiVersion("2.3.0") @GetMapping(value = "/card/list") public JSONObject getServiceCardList() { ... } } ``` #### service层 继承原接口层 命名规则: 原service名称+版本号 例如:当前bookService,2.3.0版本改变 ```java public interface AdminBookServiceV2_3_0 extends AdminBookService { ... } ``` 实现类 ```java @Primary @Service public class AdminBookServiceImplV2_3_0 extends AdminBookServiceImpl implements AdminBookServiceV2_3_0 { ... } ``` ## 5、前端接口调用 ```javascript "ajax": { "url": url, "type": 'GET', "xhrFields": {// 允许跨域 withCredentials: true }, "headers":{// API版本 "version": "2.3.0" }, "crossDomain": true, "data": function (d) { ... } } ``` 问题: controller继承,方法重写请求内容