自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

SpringBoot 如何統(tǒng)一后端返回格式?老鳥們都是這樣玩的!

開發(fā) 架構(gòu)
今天我們來聊一聊在基于SpringBoot前后端分離開發(fā)模式下,如何友好的返回統(tǒng)一的標(biāo)準(zhǔn)格式以及如何優(yōu)雅的處理全局異常。

[[411637]]

大家好,我是飄渺。

今天我們來聊一聊在基于SpringBoot前后端分離開發(fā)模式下,如何友好的返回統(tǒng)一的標(biāo)準(zhǔn)格式以及如何優(yōu)雅的處理全局異常。

首先我們來看看為什么要返回統(tǒng)一的標(biāo)準(zhǔn)格式?

為什么要對SpringBoot返回統(tǒng)一的標(biāo)準(zhǔn)格式

在默認(rèn)情況下,SpringBoot的返回格式常見的有三種:

第一種:返回 String

  1. @GetMapping("/hello"
  2. public String getStr(){ 
  3.   return "hello,javadaily"

此時(shí)調(diào)用接口獲取到的返回值是這樣:

  1. hello,javadaily 

第二種:返回自定義對象

  1. @GetMapping("/aniaml"
  2. public Aniaml getAniaml(){ 
  3.   Aniaml aniaml = new Aniaml(1,"pig"); 
  4.   return aniaml; 

此時(shí)調(diào)用接口獲取到的返回值是這樣:

  1.   "id": 1, 
  2.   "name""pig" 

第三種:接口異常

  1. @GetMapping("/error"
  2. public int error(){ 
  3.     int i = 9/0; 
  4.     return i; 

此時(shí)調(diào)用接口獲取到的返回值是這樣:

  1.   "timestamp""2021-07-08T08:05:15.423+00:00"
  2.   "status": 500, 
  3.   "error""Internal Server Error"
  4.   "path""/wrong" 

基于以上種種情況,如果你和前端開發(fā)人員聯(lián)調(diào)接口她們就會(huì)很懵逼,由于我們沒有給他一個(gè)統(tǒng)一的格式,前端人員不知道如何處理返回值。

還有甚者,有的同學(xué)比如小張喜歡對結(jié)果進(jìn)行封裝,他使用了Result對象,小王也喜歡對結(jié)果進(jìn)行包裝,但是他卻使用的是Response對象,當(dāng)出現(xiàn)這種情況時(shí)我相信前端人員一定會(huì)抓狂的。

所以我們項(xiàng)目中是需要定義一個(gè)統(tǒng)一的標(biāo)準(zhǔn)返回格式的。

定義返回標(biāo)準(zhǔn)格式

一個(gè)標(biāo)準(zhǔn)的返回格式至少包含3部分:

  1. status 狀態(tài)值:由后端統(tǒng)一定義各種返回結(jié)果的狀態(tài)碼
  2. message 描述:本次接口調(diào)用的結(jié)果描述
  3. data 數(shù)據(jù):本次返回的數(shù)據(jù)。
  1.   "status":"100"
  2.   "message":"操作成功"
  3.   "data":"hello,javadaily" 

當(dāng)然也可以按需加入其他擴(kuò)展值,比如我們就在返回對象中添加了接口調(diào)用時(shí)間

4.timestamp: 接口調(diào)用時(shí)間

定義返回對象

  1. @Data 
  2. public class ResultData<T> { 
  3.   /** 結(jié)果狀態(tài) ,具體狀態(tài)碼參見ResultData.java*/ 
  4.   private int status; 
  5.   private String message; 
  6.   private T data; 
  7.   private long timestamp ; 
  8.  
  9.  
  10.   public ResultData (){ 
  11.     this.timestamp = System.currentTimeMillis(); 
  12.   } 
  13.  
  14.  
  15.   public static <T> ResultData<T> success(T data) { 
  16.     ResultData<T> resultData = new ResultData<>(); 
  17.     resultData.setStatus(ReturnCode.RC100.getCode()); 
  18.     resultData.setMessage(ReturnCode.RC100.getMessage()); 
  19.     resultData.setData(data); 
  20.     return resultData; 
  21.   } 
  22.  
  23.   public static <T> ResultData<T> fail(int code, String message) { 
  24.     ResultData<T> resultData = new ResultData<>(); 
  25.     resultData.setStatus(code); 
  26.     resultData.setMessage(message); 
  27.     return resultData; 
  28.   } 
  29.  

定義狀態(tài)碼

  1. public enum ReturnCode { 
  2.     /**操作成功**/ 
  3.     RC100(100,"操作成功"), 
  4.     /**操作失敗**/ 
  5.     RC999(999,"操作失敗"), 
  6.     /**服務(wù)限流**/ 
  7.     RC200(200,"服務(wù)開啟限流保護(hù),請稍后再試!"), 
  8.     /**服務(wù)降級**/ 
  9.     RC201(201,"服務(wù)開啟降級保護(hù),請稍后再試!"), 
  10.     /**熱點(diǎn)參數(shù)限流**/ 
  11.     RC202(202,"熱點(diǎn)參數(shù)限流,請稍后再試!"), 
  12.     /**系統(tǒng)規(guī)則不滿足**/ 
  13.     RC203(203,"系統(tǒng)規(guī)則不滿足要求,請稍后再試!"), 
  14.     /**授權(quán)規(guī)則不通過**/ 
  15.     RC204(204,"授權(quán)規(guī)則不通過,請稍后再試!"), 
  16.     /**access_denied**/ 
  17.     RC403(403,"無訪問權(quán)限,請聯(lián)系管理員授予權(quán)限"), 
  18.     /**access_denied**/ 
  19.     RC401(401,"匿名用戶訪問無權(quán)限資源時(shí)的異常"), 
  20.     /**服務(wù)異常**/ 
  21.     RC500(500,"系統(tǒng)異常,請稍后重試"), 
  22.  
  23.     INVALID_TOKEN(2001,"訪問令牌不合法"), 
  24.     ACCESS_DENIED(2003,"沒有權(quán)限訪問該資源"), 
  25.     CLIENT_AUTHENTICATION_FAILED(1001,"客戶端認(rèn)證失敗"), 
  26.     USERNAME_OR_PASSWORD_ERROR(1002,"用戶名或密碼錯(cuò)誤"), 
  27.     UNSUPPORTED_GRANT_TYPE(1003, "不支持的認(rèn)證模式"); 
  28.  
  29.  
  30.  
  31.     /**自定義狀態(tài)碼**/ 
  32.     private final int code; 
  33.     /**自定義描述**/ 
  34.     private final String message; 
  35.  
  36.     ReturnCode(int code, String message){ 
  37.         this.code = code; 
  38.         this.message = message; 
  39.     } 
  40.  
  41.  
  42.     public int getCode() { 
  43.         return code; 
  44.     } 
  45.  
  46.     public String getMessage() { 
  47.         return message; 
  48.     } 

統(tǒng)一返回格式

  1. @GetMapping("/hello"
  2. public ResultData<String> getStr(){ 
  3.  return ResultData.success("hello,javadaily"); 

此時(shí)調(diào)用接口獲取到的返回值是這樣:

  1.   "status": 100, 
  2.   "message""hello,javadaily"
  3.   "data"null
  4.   "timestamp": 1625736481648, 
  5.   "httpStatus": 0 

這樣確實(shí)已經(jīng)實(shí)現(xiàn)了我們想要的結(jié)果,我在很多項(xiàng)目中看到的都是這種寫法,在Controller層通過ResultData.success()對返回結(jié)果進(jìn)行包裝后返回給前端。

看到這里我們不妨停下來想想,這樣做有什么弊端呢?

最大的弊端就是我們后面每寫一個(gè)接口都需要調(diào)用ResultData.success()這行代碼對結(jié)果進(jìn)行包裝,重復(fù)勞動(dòng),浪費(fèi)體力;

而且還很容易被其他老鳥給嘲笑。

所以呢我們需要對代碼進(jìn)行優(yōu)化,目標(biāo)就是不要每個(gè)接口都手工制定ResultData返回值。

高級實(shí)現(xiàn)方式

要優(yōu)化這段代碼很簡單,我們只需要借助SpringBoot提供的ResponseBodyAdvice即可。

ResponseBodyAdvice的作用:攔截Controller方法的返回值,統(tǒng)一處理返回值/響應(yīng)體,一般用來統(tǒng)一返回格式,加解密,簽名等等。

先來看下ResponseBodyAdvice的源碼:

  1. public interface ResponseBodyAdvice<T> { 
  2.   /** 
  3.   * 是否支持advice功能 
  4.   * true 支持,false 不支持 
  5.   */ 
  6.     boolean supports(MethodParameter var1, Class<? extends HttpMessageConverter<?>> var2); 
  7.  
  8.    /** 
  9.   * 對返回的數(shù)據(jù)進(jìn)行處理 
  10.   */ 
  11.     @Nullable 
  12.     T beforeBodyWrite(@Nullable T var1, MethodParameter var2, MediaType var3, Class<? extends HttpMessageConverter<?>> var4, ServerHttpRequest var5, ServerHttpResponse var6); 

我們只需要編寫一個(gè)具體實(shí)現(xiàn)類即可

  1. /** 
  2.  * @author jam 
  3.  * @date 2021/7/8 10:10 上午 
  4.  */ 
  5. @RestControllerAdvice 
  6. public class ResponseAdvice implements ResponseBodyAdvice<Object> { 
  7.     @Autowired 
  8.     private ObjectMapper objectMapper; 
  9.  
  10.     @Override 
  11.     public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { 
  12.         return true
  13.     } 
  14.  
  15.     @SneakyThrows 
  16.     @Override 
  17.     public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { 
  18.         if(o instanceof String){ 
  19.             return objectMapper.writeValueAsString(ResultData.success(o)); 
  20.         }         
  21.         return ResultData.success(o); 
  22.     } 

需要注意兩個(gè)地方:

  • @RestControllerAdvice注解

@RestControllerAdvice是@RestController注解的增強(qiáng),可以實(shí)現(xiàn)三個(gè)方面的功能:

  1. 全局異常處理
  2. 全局?jǐn)?shù)據(jù)綁定
  3. 全局?jǐn)?shù)據(jù)預(yù)處理
  • String類型判斷
  1. if(o instanceof String){ 
  2.   return objectMapper.writeValueAsString(ResultData.success(o)); 
  3. }  

這段代碼一定要加,如果Controller直接返回String的話,SpringBoot是直接返回,故我們需要手動(dòng)轉(zhuǎn)換成json。

經(jīng)過上面的處理我們就再也不需要通過ResultData.success()來進(jìn)行轉(zhuǎn)換了,直接返回原始數(shù)據(jù)格式,SpringBoot自動(dòng)幫我們實(shí)現(xiàn)包裝類的封裝。

  1. @GetMapping("/hello"
  2. public String getStr(){ 
  3.     return "hello,javadaily"

此時(shí)我們調(diào)用接口返回的數(shù)據(jù)結(jié)果為:

  1. @GetMapping("/hello"
  2. public String getStr(){ 
  3.   return "hello,javadaily"

是不是感覺很完美,別急,還有個(gè)問題在等著你呢。

接口異常問題

此時(shí)有個(gè)問題,由于我們沒對Controller的異常進(jìn)行處理,當(dāng)我們調(diào)用的方法一旦出現(xiàn)異常,就會(huì)出現(xiàn)問題,比如下面這個(gè)接口

  1. @GetMapping("/wrong"
  2. public int error(){ 
  3.     int i = 9/0; 
  4.     return i; 

返回的結(jié)果為:

這顯然不是我們想要的結(jié)果,接口都報(bào)錯(cuò)了還返回操作成功的響應(yīng)碼,前端看了會(huì)打人的。

別急,接下來我們進(jìn)入第二個(gè)議題,如何優(yōu)雅的處理全局異常。

SpringBoot為什么需要全局異常處理器

不用手寫try...catch,由全局異常處理器統(tǒng)一捕獲

使用全局異常處理器最大的便利就是程序員在寫代碼時(shí)不再需要手寫try...catch了,前面我們講過,默認(rèn)情況下SpringBoot出現(xiàn)異常時(shí)返回的結(jié)果是這樣:

  1.   "timestamp""2021-07-08T08:05:15.423+00:00"
  2.   "status": 500, 
  3.   "error""Internal Server Error"
  4.   "path""/wrong" 

這種數(shù)據(jù)格式返回給前端,前端是看不懂的,所以這時(shí)候我們一般通過try...catch來處理異常

  1. @GetMapping("/wrong"
  2. public int error(){ 
  3.     int i; 
  4.     try{ 
  5.         i = 9/0; 
  6.     }catch (Exception e){ 
  7.         log.error("error:{}",e); 
  8.         i = 0; 
  9.     } 
  10.     return i; 

我們追求的目標(biāo)肯定是不需要再手動(dòng)寫try...catch了,而是希望由全局異常處理器處理。

對于自定義異常,只能通過全局異常處理器來處理

  1. @GetMapping("error1"
  2. public void empty(){ 
  3.  throw  new RuntimeException("自定義異常"); 

當(dāng)我們引入Validator參數(shù)校驗(yàn)器的時(shí)候,參數(shù)校驗(yàn)不通過會(huì)拋出異常,此時(shí)是無法用try...catch捕獲的,只能使用全局異常處理器。

SpringBoot集成參數(shù)校驗(yàn)請參考這篇文章SpringBoot開發(fā)秘籍 - 集成參數(shù)校驗(yàn)及高階技巧

如何實(shí)現(xiàn)全局異常處理器

  1. @Slf4j 
  2. @RestControllerAdvice 
  3. public class RestExceptionHandler { 
  4.     /** 
  5.      * 默認(rèn)全局異常處理。 
  6.      * @param e the e 
  7.      * @return ResultData 
  8.      */ 
  9.     @ExceptionHandler(Exception.class) 
  10.     @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 
  11.     public ResultData<String> exception(Exception e) { 
  12.         log.error("全局異常信息 ex={}", e.getMessage(), e); 
  13.         return ResultData.fail(ReturnCode.RC500.getCode(),e.getMessage()); 
  14.     } 
  15.  

有三個(gè)細(xì)節(jié)需要說明一下:

  1. @RestControllerAdvice,RestController的增強(qiáng)類,可用于實(shí)現(xiàn)全局異常處理器
  2. @ExceptionHandler,統(tǒng)一處理某一類異常,從而減少代碼重復(fù)率和復(fù)雜度,比如要獲取自定義異??梢訞ExceptionHandler(BusinessException.class)
  3. @ResponseStatus指定客戶端收到的http狀態(tài)碼

體驗(yàn)效果

這時(shí)候我們調(diào)用如下接口:

  1. @GetMapping("error1"
  2. public void empty(){ 
  3.     throw  new RuntimeException("自定義異常"); 

返回的結(jié)果如下:

  1.   "status": 500, 
  2.   "message""自定義異常"
  3.   "data"null
  4.   "timestamp": 1625795902556 

基本滿足我們的需求了。

但是當(dāng)我們同時(shí)啟用統(tǒng)一標(biāo)準(zhǔn)格式封裝功能ResponseAdvice和RestExceptionHandler全局異常處理器時(shí)又出現(xiàn)了新的問題:

  1.   "status": 100, 
  2.   "message""操作成功"
  3.   "data": { 
  4.     "status": 500, 
  5.     "message""自定義異常"
  6.     "data"null
  7.     "timestamp": 1625796167986 
  8.   }, 
  9.   "timestamp": 1625796168008 

此時(shí)返回的結(jié)果是這樣,統(tǒng)一格式增強(qiáng)功能會(huì)給返回的異常結(jié)果再次封裝,所以接下來我們需要解決這個(gè)問題。

全局異常接入返回的標(biāo)準(zhǔn)格式

要讓全局異常接入標(biāo)準(zhǔn)格式很簡單,因?yàn)槿之惓L幚砥饕呀?jīng)幫我們封裝好了標(biāo)準(zhǔn)格式,我們只需要直接返回給客戶端即可。

  1. @SneakyThrows 
  2. @Override 
  3. public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { 
  4.   if(o instanceof String){ 
  5.     return objectMapper.writeValueAsString(ResultData.success(o)); 
  6.   } 
  7.   if(o instanceof ResultData){ 
  8.     return o; 
  9.   } 
  10.   return ResultData.success(o); 

關(guān)鍵代碼:

  1. if(o instanceof ResultData){ 
  2.   return o; 

如果返回的結(jié)果是ResultData對象,直接返回即可。

這時(shí)候我們再調(diào)用上面的錯(cuò)誤方法,返回的結(jié)果就符合我們的要求了。

  1.   "status": 500, 
  2.   "message""自定義異常"
  3.   "data"null
  4.   "timestamp": 1625796580778 

好了,今天的文章就到這里了,希望通過這篇文章你能掌握如何在你項(xiàng)目中友好實(shí)現(xiàn)統(tǒng)一標(biāo)準(zhǔn)格式到返回并且可以優(yōu)雅的處理全局異常。

 

 

 

github地址:https://github.com/jianzh5/cloud-blog/

 

責(zé)任編輯:武曉燕 來源: JAVA日知錄
相關(guān)推薦

2023-11-23 08:25:31

String性能

2023-04-12 08:56:37

RocketMQSpring核心業(yè)務(wù)

2023-02-04 10:08:40

2015-07-15 13:47:13

上網(wǎng)高手

2022-08-31 08:19:04

接口returnCode代碼

2019-09-29 10:23:09

APIJava編程語言

2024-06-13 08:19:08

Controller接口參數(shù)

2022-05-30 08:03:06

后端參數(shù)校驗(yàn)異常處理

2020-11-16 13:38:31

PostMessage

2021-10-11 19:34:03

全局格式項(xiàng)目

2024-08-06 09:51:21

SpringHTTPJSON

2021-07-28 06:10:47

拖拽設(shè)計(jì)器 transmat

2021-09-05 07:55:37

前端Emoji 表情

2015-04-16 09:48:12

APP測試

2017-11-13 12:02:33

程序猿華為軟件

2023-11-28 14:32:04

2024-08-02 08:38:20

Controller接口地址

2022-12-30 08:49:41

SpringBoot@Validated

2019-05-30 14:58:56

Pythonxml文件

2018-08-27 15:53:43

編程語言javaPython
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)