javascript
SpringBoot 如何统一后端返回格式?老鸟们都是这样玩的!
大家好,我是磊哥。
今天我們來聊一聊在基于SpringBoot前后端分離開發模式下,如何友好的返回統一的標準格式以及如何優雅的處理全局異常。
首先我們來看看為什么要返回統一的標準格式?
為什么要對SpringBoot返回統一的標準格式
在默認情況下,SpringBoot的返回格式常見的有三種:
第一種:返回 String
@GetMapping("/hello") public?String?getStr(){return?"hello,javadaily"; }此時調用接口獲取到的返回值是這樣:
hello,javadaily第二種:返回自定義對象
@GetMapping("/aniaml") public?Aniaml?getAniaml(){Aniaml?aniaml?=?new?Aniaml(1,"pig");return?aniaml; }此時調用接口獲取到的返回值是這樣:
{"id":?1,"name":?"pig" }第三種:接口異常
@GetMapping("/error") public?int?error(){int?i?=?9/0;return?i; }此時調用接口獲取到的返回值是這樣:
{"timestamp":?"2021-07-08T08:05:15.423+00:00","status":?500,"error":?"Internal?Server?Error","path":?"/wrong" }基于以上種種情況,如果你和前端開發人員聯調接口她們就會很懵逼,由于我們沒有給他一個統一的格式,前端人員不知道如何處理返回值。
還有甚者,有的同學比如小張喜歡對結果進行封裝,他使用了Result對象,小王也喜歡對結果進行包裝,但是他卻使用的是Response對象,當出現這種情況時我相信前端人員一定會抓狂的。
所以我們項目中是需要定義一個統一的標準返回格式的。
定義返回標準格式
一個標準的返回格式至少包含3部分:
status 狀態值:由后端統一定義各種返回結果的狀態碼
message 描述:本次接口調用的結果描述
data 數據:本次返回的數據。
當然也可以按需加入其他擴展值,比如我們就在返回對象中添加了接口調用時間
timestamp: 接口調用時間
定義返回對象
@Data public?class?ResultData<T>?{/**?結果狀態?,具體狀態碼參見ResultData.java*/private?int?status;private?String?message;private?T?data;private?long?timestamp?;public?ResultData?(){this.timestamp?=?System.currentTimeMillis();}public?static?<T>?ResultData<T>?success(T?data)?{ResultData<T>?resultData?=?new?ResultData<>();resultData.setStatus(ReturnCode.RC100.getCode());resultData.setMessage(ReturnCode.RC100.getMessage());resultData.setData(data);return?resultData;}public?static?<T>?ResultData<T>?fail(int?code,?String?message)?{ResultData<T>?resultData?=?new?ResultData<>();resultData.setStatus(code);resultData.setMessage(message);return?resultData;}}定義狀態碼
public?enum?ReturnCode?{/**操作成功**/RC100(100,"操作成功"),/**操作失敗**/RC999(999,"操作失敗"),/**服務限流**/RC200(200,"服務開啟限流保護,請稍后再試!"),/**服務降級**/RC201(201,"服務開啟降級保護,請稍后再試!"),/**熱點參數限流**/RC202(202,"熱點參數限流,請稍后再試!"),/**系統規則不滿足**/RC203(203,"系統規則不滿足要求,請稍后再試!"),/**授權規則不通過**/RC204(204,"授權規則不通過,請稍后再試!"),/**access_denied**/RC403(403,"無訪問權限,請聯系管理員授予權限"),/**access_denied**/RC401(401,"匿名用戶訪問無權限資源時的異常"),/**服務異常**/RC500(500,"系統異常,請稍后重試"),INVALID_TOKEN(2001,"訪問令牌不合法"),ACCESS_DENIED(2003,"沒有權限訪問該資源"),CLIENT_AUTHENTICATION_FAILED(1001,"客戶端認證失敗"),USERNAME_OR_PASSWORD_ERROR(1002,"用戶名或密碼錯誤"),UNSUPPORTED_GRANT_TYPE(1003,?"不支持的認證模式");/**自定義狀態碼**/private?final?int?code;/**自定義描述**/private?final?String?message;ReturnCode(int?code,?String?message){this.code?=?code;this.message?=?message;}public?int?getCode()?{return?code;}public?String?getMessage()?{return?message;} }統一返回格式
@GetMapping("/hello") public?ResultData<String>?getStr(){return?ResultData.success("hello,javadaily"); }此時調用接口獲取到的返回值是這樣:
{"status":?100,"message":?"hello,javadaily","data":?null,"timestamp":?1625736481648,"httpStatus":?0 }這樣確實已經實現了我們想要的結果,我在很多項目中看到的都是這種寫法,在Controller層通過ResultData.success()對返回結果進行包裝后返回給前端。
看到這里我們不妨停下來想想,這樣做有什么弊端呢?
最大的弊端就是我們后面每寫一個接口都需要調用ResultData.success()這行代碼對結果進行包裝,重復勞動,浪費體力;
而且還很容易被其他老鳥給嘲笑。
所以呢我們需要對代碼進行優化,目標就是不要每個接口都手工制定ResultData返回值。
高級實現方式
要優化這段代碼很簡單,我們只需要借助SpringBoot提供的ResponseBodyAdvice即可。
“ResponseBodyAdvice的作用:攔截Controller方法的返回值,統一處理返回值/響應體,一般用來統一返回格式,加解密,簽名等等。
”先來看下ResponseBodyAdvice的源碼:
public?interface?ResponseBodyAdvice<T>?{/***?是否支持advice功能*?true?支持,false?不支持*/boolean?supports(MethodParameter?var1,?Class<??extends?HttpMessageConverter<?>>?var2);/***?對返回的數據進行處理*/@NullableT?beforeBodyWrite(@Nullable?T?var1,?MethodParameter?var2,?MediaType?var3,?Class<??extends?HttpMessageConverter<?>>?var4,?ServerHttpRequest?var5,?ServerHttpResponse?var6); }我們只需要編寫一個具體實現類即可
/***?@author?jam*?@date?2021/7/8?10:10?上午*/ @RestControllerAdvice public?class?ResponseAdvice?implements?ResponseBodyAdvice<Object>?{@Autowiredprivate?ObjectMapper?objectMapper;@Overridepublic?boolean?supports(MethodParameter?methodParameter,?Class<??extends?HttpMessageConverter<?>>?aClass)?{return?true;}@SneakyThrows@Overridepublic?Object?beforeBodyWrite(Object?o,?MethodParameter?methodParameter,?MediaType?mediaType,?Class<??extends?HttpMessageConverter<?>>?aClass,?ServerHttpRequest?serverHttpRequest,?ServerHttpResponse?serverHttpResponse)?{if(o?instanceof?String){return?objectMapper.writeValueAsString(ResultData.success(o));}????????return?ResultData.success(o);} }需要注意兩個地方:
@RestControllerAdvice注解
@RestControllerAdvice是@RestController注解的增強,可以實現三個方面的功能:
全局異常處理
全局數據綁定
全局數據預處理
String類型判斷
- if(o?instanceof?String){return?objectMapper.writeValueAsString(ResultData.success(o));
}?
這段代碼一定要加,如果Controller直接返回String的話,SpringBoot是直接返回,故我們需要手動轉換成json。
經過上面的處理我們就再也不需要通過ResultData.success()來進行轉換了,直接返回原始數據格式,SpringBoot自動幫我們實現包裝類的封裝。
@GetMapping("/hello") public?String?getStr(){return?"hello,javadaily"; }此時我們調用接口返回的數據結果為:
@GetMapping("/hello") public?String?getStr(){return?"hello,javadaily"; }是不是感覺很完美,別急,還有個問題在等著你呢。
接口異常問題此時有個問題,由于我們沒對Controller的異常進行處理,當我們調用的方法一旦出現異常,就會出現問題,比如下面這個接口
@GetMapping("/wrong") public?int?error(){int?i?=?9/0;return?i; }返回的結果為:
這顯然不是我們想要的結果,接口都報錯了還返回操作成功的響應碼,前端看了會打人的。別急,接下來我們進入第二個議題,如何優雅的處理全局異常。
SpringBoot為什么需要全局異常處理器
不用手寫try...catch,由全局異常處理器統一捕獲
使用全局異常處理器最大的便利就是程序員在寫代碼時不再需要手寫try...catch了,前面我們講過,默認情況下SpringBoot出現異常時返回的結果是這樣:
{"timestamp":?"2021-07-08T08:05:15.423+00:00","status":?500,"error":?"Internal?Server?Error","path":?"/wrong"
}
對于自定義異常,只能通過全局異常處理器來處理
@GetMapping("error1")
public?void?empty(){throw??new?RuntimeException("自定義異常");
}
當我們引入Validator參數校驗器的時候,參數校驗不通過會拋出異常,此時是無法用try...catch捕獲的,只能使用全局異常處理器。
“SpringBoot集成參數校驗請參考這篇文章SpringBoot開發秘籍 - 集成參數校驗及高階技巧
”@RestControllerAdvice,RestController的增強類,可用于實現全局異常處理器
@ExceptionHandler,統一處理某一類異常,從而減少代碼重復率和復雜度,比如要獲取自定義異常可以@ExceptionHandler(BusinessException.class)
@ResponseStatus指定客戶端收到的http狀態碼
這種數據格式返回給前端,前端是看不懂的,所以這時候我們一般通過try...catch來處理異常
@GetMapping("/wrong") public?int?error(){int?i;try{i?=?9/0;}catch?(Exception?e){log.error("error:{}",e);i?=?0;}return?i; }我們追求的目標肯定是不需要再手動寫try...catch了,而是希望由全局異常處理器處理。
如何實現全局異常處理器
@Slf4j @RestControllerAdvice public?class?RestExceptionHandler?{/***?默認全局異常處理。*?@param?e?the?e*?@return?ResultData*/@ExceptionHandler(Exception.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public?ResultData<String>?exception(Exception?e)?{log.error("全局異常信息?ex={}",?e.getMessage(),?e);return?ResultData.fail(ReturnCode.RC500.getCode(),e.getMessage());}}有三個細節需要說明一下:
體驗效果
這時候我們調用如下接口:
@GetMapping("error1") public?void?empty(){throw??new?RuntimeException("自定義異常"); }返回的結果如下:
{"status":?500,"message":?"自定義異常","data":?null,"timestamp":?1625795902556 }基本滿足我們的需求了。
但是當我們同時啟用統一標準格式封裝功能ResponseAdvice和RestExceptionHandler全局異常處理器時又出現了新的問題:
{"status":?100,"message":?"操作成功","data":?{"status":?500,"message":?"自定義異常","data":?null,"timestamp":?1625796167986},"timestamp":?1625796168008 }此時返回的結果是這樣,統一格式增強功能會給返回的異常結果再次封裝,所以接下來我們需要解決這個問題。
全局異常接入返回的標準格式
要讓全局異常接入標準格式很簡單,因為全局異常處理器已經幫我們封裝好了標準格式,我們只需要直接返回給客戶端即可。
@SneakyThrows @Override public?Object?beforeBodyWrite(Object?o,?MethodParameter?methodParameter,?MediaType?mediaType,?Class<??extends?HttpMessageConverter<?>>?aClass,?ServerHttpRequest?serverHttpRequest,?ServerHttpResponse?serverHttpResponse)?{if(o?instanceof?String){return?objectMapper.writeValueAsString(ResultData.success(o));}if(o?instanceof?ResultData){return?o;}return?ResultData.success(o); }關鍵代碼:
if(o?instanceof?ResultData){return?o; }如果返回的結果是ResultData對象,直接返回即可。
這時候我們再調用上面的錯誤方法,返回的結果就符合我們的要求了。
{"status":?500,"message":?"自定義異常","data":?null,"timestamp":?1625796580778 }好了,今天的文章就到這里了,希望通過這篇文章你能掌握如何在你項目中友好實現統一標準格式到返回并且可以優雅的處理全局異常。
github地址:https://github.com/jianzh5/cloud-blog/
文末福利
最后磊哥為了回饋廣大的讀者朋友,決定送 3 本瘋狂 Java 李剛新作《瘋狂Spring Boot終極講義》橫跨 WebFlux、Redis、ElasticSearch、Kafka、K8s 等 7 大整合方向,并有高并發秒殺系統案例,贈視頻及代碼,免費包郵到家。(日常在看轉發較多的小伙伴獲獎概率更高哦,本周五公布中獎名單~)
當然,不差錢的朋友也可以直接下單購買,此書正在京東、當當限時5折促銷,下單地址:
總結
以上是生活随笔為你收集整理的SpringBoot 如何统一后端返回格式?老鸟们都是这样玩的!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 不错!SpringBoot发布Jar包优
- 下一篇: js 数组添加n次相同元素_数组中两次出