SpringBoot 应用篇 实现后端的接口版本支持

SpringBoot 应用篇 实现后端的接口版本支持

作为一个主职的后端开发者,在平时的工作中,最讨厌的做的事情可以说是参数校验和接口的版本支持了。对于客户端的同学来说,业务的历史包袱会小很多,当出现不兼容的业务变动时,直接开发新的就好;然而后端就没有这么简单了,历史的接口得支持,新的业务也得支持,吭哧吭哧的新加一个服务接口,url 又不能和之前的相同,怎么办?只能在某个地方加一个类似v1, v2...

那么有没有一种不改变 url,通过其他的方式来支持版本管理的方式呢?

本文将介绍一种,利用请求头来传递客户端版本,在相同的 url 中寻找最适合的这个版本请求的接口的实例 case

主要用到的知识点为:

  • RequestCondition
  • RequestMappingHandlerMapping

I. 应用场景

我们希望同一个业务始终用相同的 url,即便不同的版本之间业务完全不兼容,通过请求参数中的版本选择最合适的后端接口来响应这个请求

1. 约定

需要实现上面的 case,首先有两个约定

  • 每个请求中必须携带版本参数
  • 每个接口都定义有一个支持的版本

2. 规则

明确上面两点前提之后,就是基本规则了

版本定义

根据常见的三段式版本设计,版本格式定义如下

x.x.x
  • 其中第一个 x:对应的是大版本,一般来说只有较大的改动升级,才会改变
  • 其中第二个 x:表示正常的业务迭代版本号,每发布一个常规的 app 升级,这个数值+1
  • 最后一个 x:主要针对 bugfix,比如发布了一个 app,结果发生了异常,需要一个紧急修复,需要再发布一个版本,这个时候可以将这个数值+1

接口选择

通常的 web 请求都是通过 url 匹配规则来选择对应响应接口,但是在我们这里,一个 url,可能会有多个不同的接口,该怎么选择呢?

  • 首先从请求中,获取版本参数 version
  • 从所有相同的 url 接口中,根据接口上定义的版本,找到所有小于等于 version 的接口
  • 在上面满足条件的接口中,选择版本最大的接口来响应请求

II. 应用实现

明确上面的应用场景之后,开始设计与实现

1. 接口定义

首先我们需要一个版本定义的注解,用于标记 web 服务接口的版本,默认版本好为 1.0.0

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Api {

    /**
     * 版本
     *
     * @return
     */
    String value() default "1.0.0";
}

其次需要一个版本对应的实体类,注意下面的实现中,默认版本为1.0.0,并实现了Comparable接口,支持版本之间的比较

@Data
public class ApiItem implements Comparable<ApiItem> {

    private int high = 1;

    private int mid = 0;

    private int low = 0;

    public ApiItem() {
    }

    @Override
    public int compareTo(ApiItem right) {
        if (this.getHigh() > right.getHigh()) {
            return 1;
        } else if (this.getHigh() < right.getHigh()) {
            return -1;
        }

        if (this.getMid() > right.getMid()) {
            return 1;
        } else if (this.getMid() < right.getMid()) {
            return -1;
        }

        if (this.getLow() > right.getLow()) {
            return 1;
        } else if (this.getLow() < right.getLow()) {
            return -1;
        }
        return 0;
    }
}

需要一个将 string 格式的版本转换为 ApiItem 的转换类,并且支持了默认版本为1.0.0的设定

public class ApiConverter {
    public static ApiItem convert(String api) {
        ApiItem apiItem = new ApiItem();
        if (StringUtils.isBlank(api)) {
            return apiItem;
        }

        String[] cells = StringUtils.split(api, ".");
        apiItem.setHigh(Integer.parseInt(cells[0]));
        if (cells.length > 1) {
            apiItem.setMid(Integer.parseInt(cells[1]));
        }

        if (cells.length > 2) {
            apiItem.setLow(Integer.parseInt(cells[2]));
        }
        return apiItem;
    }
}

2. HandlerMapping 接口选择

需要一个 url,支持多个请求接口,可以考虑通过RequestCondition来实现,下面是具体的实现类

public class ApiCondition implements RequestCondition<ApiCondition> {

    private ApiItem version;

    public ApiCondition(ApiItem version) {
        this.version = version;
    }

    @Override
    public ApiCondition combine(ApiCondition other) {
        // 选择版本最大的接口
        return version.compareTo(other.version) >= 0 ? new ApiCondition(version) : new ApiCondition(other.version);
    }

    @Override
    public ApiCondition getMatchingCondition(HttpServletRequest request) {
        String version = request.getHeader("x-api");
        ApiItem item = ApiConverter.convert(version);
        // 获取所有小于等于版本的接口
        if (item.compareTo(this.version) >= 0) {
            return this;
        }

        return null;
    }

    @Override
    public int compareTo(ApiCondition other, HttpServletRequest request) {
        // 获取最大版本对应的接口
        return other.version.compareTo(this.version);
    }
}

虽然上面的实现比较简单,但是有必要注意一下两个逻辑

  • getMatchingCondition方法中,控制了只有版本小于等于请求参数中的版本的 ApiCondition 才满足规则
  • compareTo 指定了当有多个ApiCoondition满足这个请求时,选择最大的版本

自定义RequestMappingHandlerMapping实现类ApiHandlerMapping

public class ApiHandlerMapping extends RequestMappingHandlerMapping {
    @Override
    protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
        return buildFrom(AnnotationUtils.findAnnotation(handlerType, Api.class));
    }

    @Override
    protected RequestCondition<?> getCustomMethodCondition(Method method) {
        return buildFrom(AnnotationUtils.findAnnotation(method, Api.class));
    }

    private ApiCondition buildFrom(Api platform) {
        return platform == null ? new ApiCondition(new ApiItem()) :
                new ApiCondition(ApiConverter.convert(platform.value()));
    }
}

注册

@Configuration
public class ApiAutoConfiguration implements WebMvcRegistrations {

    @Override
    public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
        return new ApiHandlerMapping();
    }
}

基于此,一个实现接口版本管理的微框架已经完成;接下来进入测试环节

III. 测试

case1. 方法上添加版本

设计三个接口,一个不加上注解,两外两个添加不同版本的注解

@RestController
@RequestMapping(path = "v1")
public class V1Rest {

    @GetMapping(path = "show")
    public String show1() {
        return "v1/show 1.0.0";
    }

    @Api("1.1.2")
    @GetMapping(path = "show")
    public String show2() {
        return "v1/show 1.1.2";
    }

    @Api("1.1.0")
    @GetMapping(path = "show")
    public String show3() {
        return "v1/show 1.1.0";
    }
}

在发起请求时,分别不带上版本,带指定版本,来测试对应的响应

  • 从上面的截图可以看出,请求头中没有版本时,默认给一个1.0.0的版本
  • 响应的是小于请求版本的接口中,版本最大的哪一个

case2. 类版本+方法版本

每个方法上添加版本有点蛋疼,在上面的注解定义中,就支持了类上注解,从实现上也可以看出,当方法和类上都有注解时,选择最大的版本

@Api("2.0.0")
@RestController
@RequestMapping(path = "v2")
public class V2Rest {

    @Api("1.1.0")
    @GetMapping(path = "show")
    public String show0() {
        return "v2/show0 1.1.0";
    }

    @GetMapping(path = "show")
    public String show1() {
        return "v2/show1 2.0.0";
    }

    @Api("2.1.1")
    @GetMapping(path = "show")
    public String show2() {
        return "v2/show2 2.1.1";
    }

    @Api("2.2.0")
    @GetMapping(path = "show")
    public String show3() {
        return "v2/show3 2.2.0";
    }
}

根据我们的实现规则,show0 和 show1 都会相应 <2.1.1 的版本请求,这个时候会出现冲突;

  • 从上面的截图中,可以看出来版本小于 2.0.0 的请求,报的是 404 错误
  • 请求版本小于 2.1.1 的请求,报的是冲突异常

IV. 其他

0. 项目&相关博文

相关博文

1. 一灰灰 Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

原文地址:https://www.cnblogs.com/yihuihui/p/12117943.html

时间: 2024-10-31 22:22:45

SpringBoot 应用篇 实现后端的接口版本支持的相关文章

(二)SpringBoot基础篇- 静态资源的访问及Thymeleaf模板引擎的使用

一.描述 在应用系统开发的过程中,不可避免的需要使用静态资源(浏览器看的懂,他可以有变量,例:HTML页面,css样式文件,文本,属性文件,图片等): 并且SpringBoot内置了Thymeleaf模板引擎,可以使用模板引擎进行渲染处理,默认版本为2.1,可以重新定义Thymeleaf的版本号,在maven的配置文件中配置如下内容: <properties> <thymeleaf.version>3.0.2.RELEASE</thymeleaf.version> &l

APP接口版本兼容的问题

现在基本每个公司都做APP,所以大家都面临 APP接口版本兼容的问题. iOS和android 要不断开发新版本,很多服务端开发都是在以前接口的逻辑上进行修改.新的APP和接口开发后,接口如何兼容老的APP? 有的公司 每次发布完APP,就强制用户更新到最新版本.不推荐这样,因为用户体验太差. 就算是用 强制更新,在苹果审核期间,新的APP接口和 老的接口 也必须能同时使用. 下面我们说下如何做,我们用的是最后一种方式,大家有不同意见可以 留言讨论. 一.客户端 做兼容,接口不用做兼容 1.AP

Bootstrap开发前端 进阶(优化及与后端结合接口)总结

Bootstrap开发前端 进阶(优化与接口分析)           chang_jw 通过使用bootstrap3.3.7,html5, CSS3进行购票系统网站前端开发. 实现index,film. Cinema, certain,login, success页面并可形成关联性跳转逻辑. 一,使用Google Fonts 插件进行字体优化 文字是网页中很重要的组成部分.为文字选择一个合适的字体,能够更好的展现一个网站的个性,表达所要传递的信息. 对于font理解首先是从CSS中,如: 浏览

面向对象(高级篇之抽象类与接口的应用)

抽象类的实际应用-----模板设计 接口的实际应用--------制定标准 设计模式-------工厂设计 程序在接口和子类之间加入了一个过渡端,通过此过渡端取得接口的实例化对象. 设计模式-------代理设计 所谓的代理设计就是指由一个代理主题来操作真实主题,真实主题执行具体的业务操作,而代理主题负责其他相关业务的处理. 相当于我玩一个游戏需要登录游戏,在这个登录的时候可以设计两个类,一个是登录,另一个是检验你的用户名与密码,而登录是附着在检验类上的. 设计模式-------适配器设计 对于

移动端接口:版本的兼容

来自鼎*的面试问题,简单地说,我搞砸了...我还真的没有考虑过这个问题,稀里糊涂一顿胡说,我都感觉自己丢人. 现在大部分公司都做APP,所以面临一个版本兼容的问题. APP功能的增加导致server接口不停的进行修改,增加.老接口可能在新的版本中不在使用,但并不能保证没有跨度大的历史版本APP用户,又不能直接修改或删除老server接口. 这里,必须保证更新后,历史版本也能同时使用. (转自http://www.elecfans.com/emb/jiekou/20180223638453.htm

SpringBoot应用中使用AOP记录接口访问日志

SpringBoot应用中使用AOP记录接口访问日志 本文主要讲述AOP在mall项目中的应用,通过在controller层建一个切面来实现接口访问的统一日志记录. AOP AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率. AOP的相关术语 通知(Advice) 通知

SpringBoot中如何灵活的实现接口数据的加解密功能?

数据是企业的第四张名片,企业级开发中少不了数据的加密传输,所以本文介绍下SpringBoot中接口数据加密.解密的方式. 本文目录 一.加密方案介绍二.实现原理三.实战四.测试五.踩到的坑 一.加密方案介绍 对接口的加密解密操作主要有下面两种方式: 自定义消息转换器 优势:仅需实现接口,配置简单.劣势:仅能对同一类型的MediaType进行加解密操作,不灵活. 使用spring提供的接口RequestBodyAdvice和ResponseBodyAdvice 优势:可以按照请求的Referrer

SpringCloud分布式微服务云架构 第五篇: 路由网关(zuul)(Finchley版本)

SpringCloud分布式微服务云架构 第五篇: 路由网关(zuul)(Finchley版本)在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现.服务消费.负载均衡.断路器.智能路由.配置管理等,了解springcloud架构可以加求求:三五三六二四七二五九,由这几个基础组件相互协作,共同组建了一个简单的微服务系统.一个简答的微服务系统如下图: 注意:A服务和B服务是可以相互调用的,并且配置服务也是注册到服务注册中心的. 在Spring Cloud微服务系统中,一种常见的负载均衡方

lkmusic项目改进版本之WebAudio版本支持音乐可视化(目前还有问题)后续发布到github

index.html <!DOCTYPE html> <!--对离线存储进行支持--> <html lang="zh-cmn-Hans" <!--manifest="lkmusic.appcache"--> > <head> <meta charset="UTF-8"> <meta name="viewport" content="widt