SpringBoot實(shí)現(xiàn)API接口的完整代碼
一、簡介
產(chǎn)品迭代過程中,同一個(gè)接口可能同時(shí)存在多個(gè)版本,不同版本的接口URL、參數(shù)相同,可能就是內(nèi)部邏輯不同。尤其是在同一接口需要同時(shí)支持舊版本和新版本的情況下,比如APP發(fā)布新版本了,有的用戶可能不選擇升級(jí),這是后接口的版本管理就十分必要了,根據(jù)APP的版本就可以提供不同版本的接口。
二、代碼實(shí)現(xiàn)
本文的代碼實(shí)現(xiàn)基于SpringBoot 2.3.4-release
1.定義注解
ApiVersion
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ApiVersion { /** * 版本。x.y.z格式 * * @return */ String value() default "1.0.0"; }
value值默認(rèn)為1.0.0
EnableApiVersion
/** * 是否開啟API版本控制 */ @Target(ElementType.TYPE) @Documented @Retention(RetentionPolicy.RUNTIME) @Import(ApiAutoConfiguration.class) public @interface EnableApiVersion { }
在啟動(dòng)類上添加這個(gè)注解后就可以開啟接口的多版本支持。使用Import引入配置ApiAutoConfiguration。
2.將版本號(hào)抽象為ApiItem類
ApiItem
@Data public class ApiItem implements Comparable<ApiItem> { private int high = 1; private int mid = 0; private int low = 0; public static final ApiItem API_ITEM_DEFAULT = ApiConverter.convert(ApiVersionConstant.DEFAULT_VERSION); 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; } }
為了比較版本號(hào)的大小,實(shí)現(xiàn)Comparable接口并重寫compareTo(),從高位到低位依次比較。
ApiConverter
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; } }
ApiConverter提供靜態(tài)方法將字符創(chuàng)轉(zhuǎn)為ApiItem。
常量類,定義請(qǐng)求頭及默認(rèn)版本號(hào)
public class ApiVersionConstant { /** * header 指定版本號(hào)請(qǐng)求頭 */ public static final String API_VERSION = "x-api-version"; /** * 默認(rèn)版本號(hào) */ public static final String DEFAULT_VERSION = "1.0.0"; }
3.核心ApiCondition
新建ApiCondition類,實(shí)現(xiàn)RequestCondition,重寫combine、getMatchingCondition、compareTo方法。
RequestCondition
public interface RequestCondition<T> { /** * 方法和類上都存在相同的條件時(shí)的處理方法 */ T combine(T other); /** * 判斷是否符合當(dāng)前請(qǐng)求,返回null表示不符合 */ @Nullable T getMatchingCondition(HttpServletRequest request); /** *如果存在多個(gè)符合條件的接口,則會(huì)根據(jù)這個(gè)來排序,然后用集合的第一個(gè)元素來處理 */ int compareTo(T other, HttpServletRequest request);
以上對(duì)RequestCondition簡要說明,后續(xù)詳細(xì)源碼分析各個(gè)方法的作用。
ApiCondition
@Slf4j public class ApiCondition implements RequestCondition<ApiCondition> { public static ApiCondition empty = new ApiCondition(ApiConverter.convert(ApiVersionConstant.DEFAULT_VERSION)); private ApiItem version; private boolean NULL; public ApiCondition(ApiItem item) { this.version = item; } public ApiCondition(ApiItem item, boolean NULL) { this.version = item; this.NULL = NULL; } /** * <pre> * Spring先掃描方法再掃描類,然后調(diào)用{@link #combine} * 按照方法上的注解優(yōu)先級(jí)大于類上注解的原則處理,但是要注意如果方法上不定義注解的情況。 * 如果方法或者類上不定義注解,我們會(huì)給一個(gè)默認(rèn)的值{@code empty},{@link ApiHandlerMapping} * </pre> * @param other 方法掃描封裝結(jié)果 * @return */ @Override public ApiCondition combine(ApiCondition other) { // 選擇版本最大的接口 if (other.NULL) { return this; } return other; } @Override public ApiCondition getMatchingCondition(HttpServletRequest request) { if (CorsUtils.isPreFlightRequest(request)) { return empty; } String version = request.getHeader(ApiVersionConstant.API_VERSION); // 獲取所有小于等于版本的接口;如果前端不指定版本號(hào),則默認(rèn)請(qǐng)求1.0.0版本的接口 if (StringUtils.isBlank(version)) { log.warn("未指定版本,使用默認(rèn)1.0.0版本。"); version = ApiVersionConstant.DEFAULT_VERSION; } ApiItem item = ApiConverter.convert(version); if (item.compareTo(ApiItem.API_ITEM_DEFAULT) < 0) { throw new IllegalArgumentException(String.format("API版本[%s]錯(cuò)誤,最低版本[%s]", version, ApiVersionConstant.DEFAULT_VERSION)); } if (item.compareTo(this.version) >= 0) { return this; } return null; } @Override public int compareTo(ApiCondition other, HttpServletRequest request) { // 獲取到多個(gè)符合條件的接口后,會(huì)按照這個(gè)排序,然后get(0)獲取最大版本對(duì)應(yīng)的接口.自定義條件會(huì)最后比較 int compare = other.version.compareTo(this.version); if (compare == 0) { log.warn("RequestMappingInfo相同,請(qǐng)檢查!version:{}", other.version); } return compare; } }
3.配置類注入容器
ApiHandlerMapping
public class ApiHandlerMapping extends RequestMappingHandlerMapping { @Override protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) { return buildFrom(AnnotationUtils.findAnnotation(handlerType, ApiVersion.class)); } @Override protected RequestCondition<?> getCustomMethodCondition(Method method) { return buildFrom(AnnotationUtils.findAnnotation(method, ApiVersion.class)); } private ApiCondition buildFrom(ApiVersion platform) { return platform == null ? getDefaultCondition() : new ApiCondition(ApiConverter.convert(platform.value())); } private ApiCondition getDefaultCondition(){ return new ApiCondition(ApiConverter.convert(ApiVersionConstant.DEFAULT_VERSION),true); } }
ApiAutoConfiguration
public class ApiAutoConfiguration implements WebMvcRegistrations { @Override public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { return new ApiHandlerMapping(); } }
ApiAutoConfiguration沒有使用Configuration自動(dòng)注入,而是使用Import帶入,目的是可以在程序中選擇性啟用或者不啟用版本控制。
三、原理解析
四、總結(jié)
到此這篇關(guān)于SpringBoot實(shí)現(xiàn)API接口的文章就介紹到這了,更多相關(guān)SpringBoot實(shí)現(xiàn)API接口內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot3整合SpringDoc OpenAPI生成接口文檔的詳細(xì)過程
- 關(guān)于springboot忽略接口,參數(shù)注解的使用ApiIgnore
- Springboot+Redis實(shí)現(xiàn)API接口防刷限流的項(xiàng)目實(shí)踐
- SpringBoot?快速實(shí)現(xiàn)?api?接口加解密功能
- 詳解Springboot快速搭建跨域API接口的步驟(idea社區(qū)版2023.1.4+apache-maven-3.9.3-bin)
- SpringBoot整合Sa-Token實(shí)現(xiàn)?API?接口簽名安全校驗(yàn)功能
- SpringBoot如何根據(jù)目錄結(jié)構(gòu)生成API接口前綴
- SpringBoot可視化接口開發(fā)工具magic-api的簡單使用教程
- springboot接入方式對(duì)接股票數(shù)據(jù)源API接口的操作方法
相關(guān)文章
基于@JsonSerialize和@JsonInclude注解使用方法
這篇文章主要介紹了@JsonSerialize和@JsonInclude注解使用方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10探索Java中private方法添加@Transactional事務(wù)未生效原因
你又遇到過明明給private方法添加了@Transactional但是事務(wù)依然沒有生效的情況嗎,具體原因本篇文章將詳細(xì)告訴你,有需要的朋友跟著小編往下看吧2021-11-11在IDEA中安裝scala、maven、hadoop遇到的問題小結(jié)
這篇文章主要介紹了在IDEA中安裝scala、maven、hadoop遇到的問題小結(jié),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10Java web Hibernate如何與數(shù)據(jù)庫鏈接
這篇文章主要介紹了Java web Hibernate如何與數(shù)據(jù)庫鏈接,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06jdbc實(shí)現(xiàn)寵物商店管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了jdbc實(shí)現(xiàn)寵物商店管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10