• Spring MVC 实现REST风格API版本控制


    Spring MVC 实现REST风格API版本控制

    项目的开发会是一个迭代一直更新的过程,从软件工程的角度说,除非项目进入废弃,否则项目从开始到维护升级都是一个不断更新的项目。项目的版本更新和升级这个概念很好理解,但是实施起来切实是一个痛点。现在大部分的系统后端架构通常都是基于一个网关对外暴露API接口,这些API包括网页端API接口,APP端接口和小程序端接口等。

    后端项目的升级可以通过我们自己来升级,但是对于用户而言,类似APP或者PC端程序用户而言,更新程序与后端接口达成一致性是很困难的,特别是你的项目群体特别大的时候,没有出现特别大的后端版本更新的时候,通常都只是提示用户更新而已。所以会造成部分用户更新至最新的版本,而部分用户还停留在旧版本中。所以一个接口操作会存在不同版本客户端请求。相比PC端应用程序和移动端应用程序,C/S架构的应用程序,可以做到及时更新,但是也会存在一个问题,

    • 对于当前的接口,Web端API更新到最新了,但是移动端或者PC端用户还没有更新上来。

    所以系统的更新同样需要考虑接口的版本问题。本文章综合网络上的各种解决方案,写一个基于Spring MVC的REST风格的API版本方法。

    上代码:

    定义一个Api版本的注解

    
    /**
     * @author shaoyayu
     * @date 2021/12/10
     * @apiNote api接口的注解
     */
    @Target({ElementType.METHOD,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Mapping
    public @interface ApiVersion {
        int value();
    }
    
    

    定义一个类实现RequestCondition

    
    /**
     * @author shaoyayu
     * @date 2021/12/10
     * @apiNote
     */
    @Getter
    @Setter
    @Slf4j
    public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
    
        private static final 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 request) {
            try {
                Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI());
                if (m.find()){
                    int version = Integer.parseInt(m.group(1));
                    if (version>=this.apiVersion){
                        return this;
                    }
                }
                return null;
            }catch (Exception e){
                log.info("api 版本转换异常:"+request.getRequestURI());
            }
            return null;
        }
    
        @Override
        public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
            return other.getApiVersion() - this.apiVersion;
        }
    }
    
    

    到这里已经完成一半了,但是需要一个继承RequestMappingHandlerMapping的实现类

    
    /**
     * @author shaoyayu
     * @date 2021/12/10
     * @apiNote
     */
    public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
        @Override
        protected RequestCondition<ApiVersionCondition> getCustomTypeCondition(Class<?> handlerType) {
            ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
            return createCondition(apiVersion);
        }
        @Override
        protected RequestCondition<ApiVersionCondition> 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());
        }
    }
    
    
    

    剩下的就是把这个实现RequestMappingHandlerMapping的CustomRequestMappingHandlerMapping注入到容器中。

    有两种方法实现注册。一通过配置类里面,注入

    
    /**
     * @author shaoyayu
     * @date 2021/12/10
     * @apiNote
     */
    @Configuration
    public class WebConfiguration extends WebMvcAutoConfiguration {
    
        @Bean
        public RequestMappingHandlerMapping requestMappingHandlerMapping(){
            RequestMappingHandlerMapping handlerMapping = new CustomRequestMappingHandlerMapping();
            handlerMapping.setOrder(0);
            return handlerMapping;
        }
    
    }
    
    

    这种方式注入Spring Boot版本越高也是不推荐这种方法。

    第二种方法,通过继承WebMvcConfigurationSupport,在createRequestMappingHandlerMapping方法中返回

    /**
     * @author shaoyayu
     * @date 2021/12/10
     * @apiNote
     */
    @Configuration
    public class CustomWebMvcConfigurationSupport extends WebMvcConfigurationSupport {
        @Override
        protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
            RequestMappingHandlerMapping  handlerMapping = new CustomRequestMappingHandlerMapping();
            handlerMapping.setOrder(0);
            return handlerMapping;
        } 
    }
    
    
    

    个人推荐第二种方式。

    测试

    定义一个UserController

    
    /**
     * @author shaoyayu
     * @date 2021/12/10
     * @apiNote SonarLint: Remove this empty class, write its code or make it an "interface".
     */
    @Slf4j
    @RestController
    @RequestMapping("{version}/web/user")
    public class UserController {
    
        @GetMapping("/hello")
        public Map<String,Object> hello(){
            Map<String, Object> map = new HashMap<>();
            map.put("code",200);
            map.put("ok",true);
            map.put("msg","v api interface");
            map.put("data",null);
            return map;
        }
    
        @GetMapping("/hello")
        @ApiVersion(1)
        public Map<String,Object> hello1(){
            Map<String, Object> map = new HashMap<>();
            map.put("code",200);
            map.put("ok",true);
            map.put("msg","v1 api interface");
            map.put("data",null);
            return map;
        }
    
        @GetMapping("/hello")
        @ApiVersion(2)
        public Map<String,Object> hello2(){
            Map<String, Object> map = new HashMap<>();
            map.put("code",200);
            map.put("ok",true);
            map.put("msg","v2 api interface");
            map.put("data",null);
            return map;
        }
    
    }
    
    

    测试的结果

    • 如访问/web/user/hello不带版本号,出现404的结果

    • 访问/v/web/user/hello会调用第一个方法

    • 访问/v1/web/user/helo会调用第二个方法。

    • 访问/v2/web/user/helo会调用第三个方法。

    • 访问/v3/web/user/hello会调用第三个方法。

    综合的测试结果,

    1. 不带v的版本控制会出现404

    2. 版本号会向下访问比自己更低一级的版本

    记得加油学习哦^_^
  • 相关阅读:
    Linkerd 2.10(Step by Step)—将 GitOps 与 Linkerd 和 Argo CD 结合使用
    Linkerd 2.10(Step by Step)—多集群通信
    Linkerd 2.10(Step by Step)—使用 Kustomize 自定义 Linkerd 的配置
    Linkerd 2.10(Step by Step)—控制平面调试端点
    Linkerd 2.10(Step by Step)—配置超时
    Linkerd 2.10(Step by Step)—配置重试
    Linkerd 2.10(Step by Step)—配置代理并发
    本地正常运行,线上环境诡异异常原因集合
    Need to invoke method 'xxx' declared on target class 'yyy', but not found in any interface(s) of the exposed proxy type
    alpine 安装常用命令
  • 原文地址:https://www.cnblogs.com/shaoyayu/p/15669965.html
Copyright © 2020-2023  润新知