SpringBoot入门到精通(二十一)如何优雅的设计 RESTful API 接口版本号,实现 API 版本控制!

简介: 有些人可能会问,为什么我看到很多公司的api接口文档里面,都有/api/v1/ 这样的地址呢?其实,/api 就是为了和一般的业务地址区分,标明这个地址是api 的接口。v1 则代表版本号。可能很多人又会问了,为什么要版本号呢?那么,接下来就聊一聊Restful 接口为什么要加版本号? 如何优雅的设计 Restful API 接口版本号?

前面介绍了Spring Boot 如何快速实现Restful api 接口,并以人员信息为例,设计了一套操作人员信息的接口。

有些人可能会问,为什么我看到很多公司的api接口文档里面,都有/api/v1/ 这样的地址呢?其实,/api 就是为了和一般的业务地址区分,标明这个地址是api 的接口。v1 则代表版本号。

可能很多人又会问了,为什么要版本号呢?那么,接下来就聊一聊Restful 接口为什么要加版本号? 如何优雅的设计 Restful API 接口版本号?

 

一、为什么加版本号

一般来说,api 接口是提供给其他系统或是其他公司使用,不能随意频繁的变更。然而,需求和业务不断变化,接口和参数也会发生相应的变化。如果直接对原来的接口进行修改,势必会影响线其他系统的正常运行。这就必须对api 接口进行有效的版本控制。

例如,添加用户的接口,由于业务需求变化,接口的字段属性也发生了变化而且可能和之前的功能不兼容。为了保证原有的接口调用方不受影响,只能重新定义一个新的接口。

 

Api 版本控制的方式:

  1、域名区分管理,即不同的版本使用不同的域名,v1.api.test.com,v2.api.test.com

  2、请求url 路径区分,在同一个域名下使用不同的url路径,test.com/api/v1/,test.com/api/v2

  3、请求参数区分,在同一url路径下,增加version=v1或v2 等,然后根据不同的版本,选择执行不同的方法。

实际项目中,一般选择第二种:请求url路径区分。因为第二种既能保证水平扩展,有不影响以前的老版本。

 

二、Spring Boot如何实现

实现方案:

1、首先创建自定义的@APIVersion 注解和自定义URL匹配规则ApiVersionCondition。

2、然后创建自定义的 RequestMappingHandlerMapping 匹配对应的request,选择符合条件的method handler。

 

1、创建自定义注解

首先,在com.weiz.config 包下,创建一个自定义版本号标记注解 @ApiVersion。

package com.weiz.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * API版本控制注解
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiVersion {
    /**
     * @return 版本号
     */
    int value() default 1;
}

说明:

ApiVersion 为自定义的注解,API版本控制,返回对应的版本号。

 

2、自定义url匹配逻辑

创建 ApiVersionCondition 类,并继承RequestCondition 接口,作用是:版本号筛选,将提取请求URL中版本号,与注解上定义的版本号进行比对,以此来判断某个请求应落在哪个controller上。

在com.weiz.config 包下创建ApiVersionCondition 类,重写 RequestCondition,创建自定义的url匹配逻辑。

package com.weiz.config;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import javax.servlet.http.HttpServletRequest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
    private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile(".*v(\\d+).*");
    private int apiVersion;
    ApiVersionCondition(int apiVersion) {
        this.apiVersion = apiVersion;
    }
    private int getApiVersion() {
        return apiVersion;
    }
    @Override
    public ApiVersionCondition combine(ApiVersionCondition apiVersionCondition) {
        return new ApiVersionCondition(apiVersionCondition.getApiVersion());
    }
    @Override
    public ApiVersionCondition getMatchingCondition(HttpServletRequest httpServletRequest) {
        Matcher m = VERSION_PREFIX_PATTERN.matcher(httpServletRequest.getRequestURI());
        if (m.find()) {
            Integer version = Integer.valueOf(m.group(1));
            if (version >= this.apiVersion) {
                return this;
            }
        }
        return null;
    }
    @Override
    public int compareTo(ApiVersionCondition apiVersionCondition, HttpServletRequest httpServletRequest) {
        return apiVersionCondition.getApiVersion() - this.apiVersion;
    }
}

当方法级别和类级别都有ApiVersion注解时,二者将进行合并(ApiVersionRequestCondition.combine)。最终将提取请求URL中版本号,与注解上定义的版本号进行比对,判断url是否符合版本要求。

 

3、自定义匹配的处理器

在com.weiz.config 包下创建 ApiRequestMappingHandlerMapping 类,重写部分 RequestMappingHandlerMapping 的方法。

package com.weiz.config;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.lang.reflect.Method;
public class ApiRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
    private static final String VERSION_FLAG = "{version}";
    private static RequestCondition<ApiVersionCondition> createCondition(Class<?> clazz) {
        RequestMapping classRequestMapping = clazz.getAnnotation(RequestMapping.class);
        if (classRequestMapping == null) {
            return null;
        }
        StringBuilder mappingUrlBuilder = new StringBuilder();
        if (classRequestMapping.value().length > 0) {
            mappingUrlBuilder.append(classRequestMapping.value()[0]);
        }
        String mappingUrl = mappingUrlBuilder.toString();
        if (!mappingUrl.contains(VERSION_FLAG)) {
            return null;
        }
        ApiVersion apiVersion = clazz.getAnnotation(ApiVersion.class);
        return apiVersion == null ? new ApiVersionCondition(1) : new ApiVersionCondition(apiVersion.value());
    }
    @Override
    protected RequestCondition<?> getCustomMethodCondition(Method method) {
        return createCondition(method.getClass());
    }
    @Override
    protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
        return createCondition(handlerType);
    }
}

 

4、配置注册自定义的RequestMappingHandlerMapping

重写请求过处理的方法,将之前创建的 ApiRequestMappingHandlerMapping 注册到系统中。

package com.weiz.config;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
@Configuration
public class WebMvcRegistrationsConfig implements WebMvcRegistrations {
    @Override
    public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
        return new ApiRequestMappingHandlerMapping();
    }
}

上面四步,把api 版本控制配置完了。代码看着复杂,其实都是重写spring boot 内部的处理流程。

 

三、测试

配置完成之后,接下来编写测试的控制器进行测试。

1、在Controller/api 目录下,分别创建UserV1Controller 和 UserV2Controller

@RequestMapping("api/{version}/user")
@RestController
public class UserV1Controller {
    @GetMapping("/test")
    public String test() {
        return "version1";
    }
    @GetMapping("/extend")
    public String extendTest() {
        return "user v1 extend";
    }
}
UserV2Controller
@RequestMapping("api/{version}/user")
@RestController
@ApiVersion(2)
public class UserV2Controller {
    @GetMapping("/test")
    public String test() {
        return "user v2 test";
    }
}

 

2、启动项目后,输入相关地址,查看版本控制是否生效

测试结果:

v1正常的接口地址:/api/v1/user/test

image.png


v2正常的接口地址:/api/v2/user/test

image.png

v2继承的接口地址:/api/v2/user/extend

image.png

说明:

  上图的前两个截图说明,请求正确的版本地址,会自动匹配版本的对应接口。当请求的版本大于当前版本时,默认匹配当前版本。

  第三个截图说明,当请求对应的版本不存在接口时,会匹配之前版本的接口,即请求/v2/user/extend 接口时,由于v2 控制器未实现该接口,所以自动匹配v1 版本中的接口。这就是所谓的版本继承。


最后

以上,就把Spring Boot 如何优雅的设计 Restful API 接口版本号,实现 API 版本控制介绍完了。版本控制和权限验证是rest api 的基础,虽然看着比较复杂,但是理解了,要实现还是比较简单的。

这个系列课程的完整源码,也会提供给大家。大家关注我的微信公众号(架构师精进),回复:springboot源码。获取这个系列课程的完整源码。




推荐阅读:

SpringBoot从入门到精通(二十)快速构建RESTful Web API 服务

SpringBoot从入门到精通(十九)使用注解实现动态Sql、参数传递

SpringBoot从入门到精通(十八)Mybatis系列之——使用注解的方式实现后台管理功能

SpringBoot从入门到精通(十七)MyBatis系列之——创建自定义mapper 实现多表关联查询!

SpringBoot从小白到精通(十六)使用pagehelper实现分页查询功能

SpringBoot从小白到精通(十五)实现开发环境热部署

SpringBoot从小白到精通(十四)使用JdbcTemplate操作数据库,配置多数据源!

SpringBoot从小白到精通(十三)如何实现事务保存

SpringBoot从小白到精通(十二)logback日志配置

SpringBoot从小白到精通(十一)统一异常处理

SpringBoot从小白到精通(十)使用Interceptor拦截器,一学就会!

SpringBoot从小白到精通(九)使用@Async实现异步执行任务

SpringBoot从小白到精通(八)熟悉@EnableScheduling,一秒搞定定时任务

SpringBoot从小白到精通(七)使用Redis实现高速缓存架构

SpringBoot从小白到精通(六)使用Mybatis实现增删改查【附详细步骤】

SpringBoot从小白到精通(五)Thymeleaf的语法及常用标签

SpringBoot从小白到精通(四)Thymeleaf页面模板引擎

SpringBoot从小白到精通(三)系统配置及自定义配置

SpringBoot从小白到精通(二)如何返回统一的数据格式

SpringBoot从小白到精通(一)如何快速创建SpringBoot项目

 

相关文章
|
1天前
|
JSON 前端开发 搜索推荐
关于商品详情 API 接口 JSON 格式返回数据解析的示例
本文介绍商品详情API接口返回的JSON数据解析。最外层为`product`对象,包含商品基本信息(如id、name、price)、分类信息(category)、图片(images)、属性(attributes)、用户评价(reviews)、库存(stock)和卖家信息(seller)。每个字段详细描述了商品的不同方面,帮助开发者准确提取和展示数据。具体结构和字段含义需结合实际业务需求和API文档理解。
|
4天前
|
监控 数据挖掘 API
京东商品历史价格 API 接口系列(京东 API)
本文介绍了如何使用京东开放平台API获取商品价格信息。首先,需注册账号并创建应用以获取App Key和App Secret,进而获取Access Token。准备好开发工具后,通过调用`jd.item_search`和`jd.item_get`接口,可以分别按关键字搜索商品和获取指定商品的详细信息及价格。示例代码展示了如何使用Python的requests库进行API请求。应用场景包括价格监控、商家定价策略、电商平台数据分析及商业智能决策支持。
42 10
|
6天前
|
缓存 API 开发者
京东按图搜索商品(拍立淘)API接口系列(京东API)
京东按图搜索商品(拍立淘)API 接口(.jd.item_search_img)通过上传图片搜索京东平台上的相似商品,基于图像识别技术提供便捷的商品搜索方式。适用于电商平台展示、比价等场景。响应参数包括公共参数、商品信息及搜索结果相关参数,方便分页展示和了解整体搜索规模。Python 请求示例展示了如何使用该接口进行图片搜索。
46 15
|
7天前
|
JSON API 数据格式
京东商品SKU价格接口(Jd.item_get)丨京东API接口指南
京东商品SKU价格接口(Jd.item_get)是京东开放平台提供的API,用于获取商品详细信息及价格。开发者需先注册账号、申请权限并获取密钥,随后通过HTTP请求调用API,传入商品ID等参数,返回JSON格式的商品信息,包括价格、原价等。接口支持GET/POST方式,适用于Python等语言的开发环境。
50 11
|
6天前
|
JSON 监控 API
京东商品列表 API 接口系列(京东 API)
京东商品列表API接口为开发者提供获取店铺内商品详细信息的功能,包括名称、价格、库存、图片、ID、销量等。通过HTTP GET请求并包含必要参数(如店铺ID、API密钥),可获取JSON格式的商品列表数据,适用于展示、库存管理、价格监控等场景。示例代码展示了使用Python调用该接口的方法,返回的数据包含状态码、商品总数、分页信息及具体商品详情。
|
4天前
|
JSON 数据挖掘 API
京东店铺所有商品 API 接口系列(京东 API)
京东店铺所有商品API接口用于获取指定店铺的全面商品信息,包括基本属性、价格、库存、销售数据等。前期需仔细研读接口文档,掌握请求地址、参数格式及频率限制。接口支持分页和筛选参数,返回JSON格式数据。Python示例中使用`requests`库发送HTTP请求并处理返回数据。该API适用于竞品分析、商品管理工具开发、市场调研及价格监测等场景,助力电商从业者优化运营策略。
|
1月前
|
人工智能 自然语言处理 API
Multimodal Live API:谷歌推出新的 AI 接口,支持多模态交互和低延迟实时互动
谷歌推出的Multimodal Live API是一个支持多模态交互、低延迟实时互动的AI接口,能够处理文本、音频和视频输入,提供自然流畅的对话体验,适用于多种应用场景。
80 3
Multimodal Live API:谷歌推出新的 AI 接口,支持多模态交互和低延迟实时互动
|
18天前
|
JSON 安全 API
淘宝商品详情API接口(item get pro接口概述)
淘宝商品详情API接口旨在帮助开发者获取淘宝商品的详细信息,包括商品标题、描述、价格、库存、销量、评价等。这些信息对于电商企业而言具有极高的价值,可用于商品信息展示、市场分析、价格比较等多种应用场景。
|
26天前
|
前端开发 API 数据库
Next 编写接口api
Next 编写接口api
|
1月前
|
XML JSON 缓存
阿里巴巴商品详情数据接口(alibaba.item_get) 丨阿里巴巴 API 实时接口指南
阿里巴巴商品详情数据接口(alibaba.item_get)允许商家通过API获取商品的详细信息,包括标题、描述、价格、销量、评价等。主要参数为商品ID(num_iid),支持多种返回数据格式,如json、xml等,便于开发者根据需求选择。使用前需注册并获得App Key与App Secret,注意遵守使用规范。