spring boot: 設計接口站api的版本號,支持次版本號(spring boot 2.3.2)


一,為什么接口站的api要使用版本號?

1,當服務端接口的功能發生改進后,

    客戶端如果不更新版本,

   則服務端返回的功能可能不能使用,
   所以在服務端功能升級后,
    客戶端也要相應的使用新版的服務端接口
 
 
2,注意點:不要頻繁變更服務端接口站的版本
 
不管是新增/修改服務端功能,只要app舊版本可以兼容,
則服務端的版本號無需變動,因為這個版本和git的版本控制不一樣,
它起的作用是比較客戶端的哪些版本和服務端的哪些版本能兼容
只有客戶端不能兼容時,才會新增版本號以便區分

 

3,版本號需要能向下兼容,

   如果訪問一個高的版本不存在時,應該能訪問到比它小的離它最近的版本,

   因為如果每個入口或每個功能在有新版本時都要重新寫一遍,

   則和把代碼復制一份沒有兩樣了,

   這樣才可以方便的管理    

 

4,版本號可以放到請求頭和url中,

     我們采用比較直觀的放到url中的形式演示,

     形如: /v1.2/home/page?

 

5,說明:生產環境中沒發現有必要在次版本號后面再加一級,

           所以有次版本號后已經夠用了,

           大家如果認為有必要增加一級的話,可以修改代碼中VERSION_PREFIX_PATTERN的正則:

 

說明:劉宏締的架構森林是一個專注架構的博客,地址:https://www.cnblogs.com/architectforest

         對應的源碼可以訪問這里獲取: https://github.com/liuhongdi/

說明:作者:劉宏締 郵箱: 371125307@qq.com

 

二,演示項目的相關信息

1,項目地址:

https://github.com/liuhongdi/apiversion

 

2,項目說明:

        我們給接口站的接口增加api的版本號

        支持整數方式(例:v1)和次版本號形式(例:v1.3)

 

3,項目結構:如圖:

 

三,java代碼說明

1,ApiVersion.java

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiVersion {
    //版本號的值,從1開始
    String value() default "1";
}

用來添加版本號的一個注解

 

2,ApiVersionCondition.java

//實現RequestCondition
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
    //api版本號
    private String apiVersion;
    //版本號的格式,如: /v[1-n]/api/test or /v1.5/home/api
    private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("/v((\\d+\\.\\d+)|(\\d+))/");
    public ApiVersionCondition(String apiVersion) {
        this.apiVersion = apiVersion;
    }

    //將不同的篩選條件進行合並
    @Override
    public ApiVersionCondition combine(ApiVersionCondition other) {
        // 采用最后定義優先原則,則方法上的定義覆蓋類上面的定義
        return new ApiVersionCondition(other.getApiVersion());
    }

    //版本比對,用於排序
    @Override
    public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
        //優先匹配最新版本號
        return compareTo(other.getApiVersion(),this.apiVersion)?1:-1;
    }

    //獲得符合匹配條件的ApiVersionCondition
    @Override
    public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
        Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI());
        if (m.find()) {
            String version = m.group(1);
            if (compareTo(version,this.apiVersion)){
                return this;
            }
        }
        return null;
    }
    //compare version
    private boolean compareTo(String version1,String version2){
        if (!version1.contains(".")) {
            version1 += ".0";
        }
        if (!version2.contains(".")) {
            version2 += ".0";
        }
        String[] split1 = version1.split("\\.");
        String[] split2 = version2.split("\\.");
        for (int i = 0; i < split1.length; i++) {
            if (Integer.parseInt(split1[i])<Integer.parseInt(split2[i])){
                return false;
            }
        }
        return true;
    }

    public String getApiVersion() {
        return apiVersion;
    }
}

實現查找和比較版本號的功能

 

3,ApiVersionRequestMappingHandlerMapping.java

//擴展RequestMappingHandlerMapping
public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

    //類上有 @ApiVersion注解時生效
    @Override
    protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
        ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
        return createRequestCondition(apiVersion);
    }

    //方法上有 @ApiVersion注解時生效
    @Override
    protected RequestCondition<?> getCustomMethodCondition(Method method) {
        ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
        return createRequestCondition(apiVersion);
    }
    
    //返回ApiVersionCondition
    private RequestCondition<ApiVersionCondition> createRequestCondition(ApiVersion apiVersion) {
        return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value());
    }
}

繼承RequestMappingHandlerMapping,在類上和方法上有注解時,

使用ApiVersionCondition進行處理

 

4,WebMvcConfig.java

@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
    //在獲取RequestMappingHandlerMapping時
    //返回我們自定義的ApiVersionRequestMappingHandlerMapping
    @Override
    protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
        return new ApiVersionRequestMappingHandlerMapping();
    }
}

把createRequestMappingHandlerMapping方法返回時使用我們自定義的ApiVersionRequestMappingHandlerMapping

 

5,HomeController.java

@RestController
@RequestMapping("/{version}/home")
public class HomeController {

    //匹配版本v1的訪問
    @ApiVersion("1")
    @GetMapping
    @RequestMapping("/home")
    public String home01(@PathVariable String version) {
        return "home v1 : version:" + version;
    }

    //匹配版本v2的訪問
    @ApiVersion("2.0")
    @GetMapping
    @RequestMapping("/home")
    public String home02(@PathVariable String version) {
        return "home v2 version: " + version;
    }

    //匹配版本v1.5-2.0的訪問
    @ApiVersion("1.5")
    @GetMapping
    @RequestMapping("/home")
    public String home15(@PathVariable String version) {
        return "home v1.5 version: " + version;
    }
}

 

6,GoodsV1Controller和GoodsV2Controller

  把版本號加到controller上,

  和加在方法上的使用一樣,

  為節省篇幅,不再貼代碼,大家可以自己去github上訪問

 

四,測試效果

1,訪問

http://127.0.0.1:8080/v1.0/home/home

返回:

home v1 : version:v1.0

訪問

http://127.0.0.1:8080/v2/home/home

返回:

home v2 version: v2

可見版本號帶不帶點,不妨礙它訪問到對應的版本

 

2,訪問:

http://127.0.0.1:8080/v3.0/home/home

返回:

home v2 version: v3.0

訪問:

http://127.0.0.1:8080/v1.14/home/home

返回:

home v1.5 version: v1.14

可見版本號可以自動訪問到距它最近的版本的功能

 

五, 查看spring boot的版本

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.2.RELEASE)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM