溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

SpringBoot如何統(tǒng)一后端返回格式

發(fā)布時間:2021-07-19 00:44:00 來源:億速云 閱讀:209 作者:chen 欄目:開發(fā)技術(shù)

這篇文章主要講解了“SpringBoot如何統(tǒng)一后端返回格式”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“SpringBoot如何統(tǒng)一后端返回格式”吧!

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

    • 第一種:返回 String

    • 第二種:返回自定義對象

    • 第三種:接口異常

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

    • 高級實現(xiàn)方式

      • 接口異常問題

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

          • 體驗效果

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

              今天我們來聊一聊在基于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

              @GetMapping("/hello")
              public String getStr(){
                return "hello,javadaily";
              }

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

              hello,javadaily

              第二種:返回自定義對象

              @GetMapping("/aniaml")
              public Aniaml getAniaml(){
                Aniaml aniaml = new Aniaml(1,"pig");
                return aniaml;
              }

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

              {
                "id": 1,
                "name": "pig"
              }

              第三種:接口異常

              @GetMapping("/error")
              public int error(){
                  int i = 9/0;
                  return i;
              }

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

              {
                "timestamp": "2021-07-08T08:05:15.423+00:00",
                "status": 500,
                "error": "Internal Server Error",
                "path": "/wrong"
              }

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

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

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

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

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

              • status 狀態(tài)值:由后端統(tǒng)一定義各種返回結(jié)果的狀態(tài)碼

              • message 描述:本次接口調(diào)用的結(jié)果描述

              • data 數(shù)據(jù):本次返回的數(shù)據(jù)

              {
                "status":"100",
                "message":"操作成功",
                "data":"hello,javadaily"
              }

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

              timestamp: 接口調(diào)用時間

              定義返回對象

              @Data
              public class ResultData<t> {
                /** 結(jié)果狀態(tài) ,具體狀態(tài)碼參見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;
                }
              
              }

              定義狀態(tài)碼

              public enum ReturnCode {
                  /**操作成功**/
                  RC100(100,"操作成功"),
                  /**操作失敗**/
                  RC999(999,"操作失敗"),
                  /**服務(wù)限流**/
                  RC200(200,"服務(wù)開啟限流保護,請稍后再試!"),
                  /**服務(wù)降級**/
                  RC201(201,"服務(wù)開啟降級保護,請稍后再試!"),
                  /**熱點參數(shù)限流**/
                  RC202(202,"熱點參數(shù)限流,請稍后再試!"),
                  /**系統(tǒng)規(guī)則不滿足**/
                  RC203(203,"系統(tǒng)規(guī)則不滿足要求,請稍后再試!"),
                  /**授權(quán)規(guī)則不通過**/
                  RC204(204,"授權(quán)規(guī)則不通過,請稍后再試!"),
                  /**access_denied**/
                  RC403(403,"無訪問權(quán)限,請聯(lián)系管理員授予權(quán)限"),
                  /**access_denied**/
                  RC401(401,"匿名用戶訪問無權(quán)限資源時的異常"),
                  /**服務(wù)異常**/
                  RC500(500,"系統(tǒng)異常,請稍后重試"),
              
                  INVALID_TOKEN(2001,"訪問令牌不合法"),
                  ACCESS_DENIED(2003,"沒有權(quán)限訪問該資源"),
                  CLIENT_AUTHENTICATION_FAILED(1001,"客戶端認(rèn)證失敗"),
                  USERNAME_OR_PASSWORD_ERROR(1002,"用戶名或密碼錯誤"),
                  UNSUPPORTED_GRANT_TYPE(1003, "不支持的認(rèn)證模式");
              
              
              
                  /**自定義狀態(tài)碼**/
                  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;
                  }
              }

              統(tǒng)一返回格式

              @GetMapping("/hello")
              public ResultData<string> getStr(){
              	return ResultData.success("hello,javadaily");
              }

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

              {
                "status": 100,
                "message": "hello,javadaily",
                "data": null,
                "timestamp": 1625736481648,
                "httpStatus": 0
              }

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

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

              最大的弊端就是我們后面每寫一個接口都需要調(diào)用ResultData.success()這行代碼對結(jié)果進行包裝,重復(fù)勞動,浪費體力;而且還很容易被其他老鳥給嘲笑。

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

              高級實現(xiàn)方式

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

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

              先來看下ResponseBodyAdvice的源碼:

              public interface ResponseBodyAdvice<t> {
              		/**
              		* 是否支持advice功能
              		* true 支持,false 不支持
              		*/
                  boolean supports(MethodParameter var1, Class<!--? extends HttpMessageConverter<?-->> var2);
              
              	  /**
              		* 對返回的數(shù)據(jù)進行處理
              		*/
                  @Nullable
                  T beforeBodyWrite(@Nullable T var1, MethodParameter var2, MediaType var3, Class<!--? extends HttpMessageConverter<?-->> var4, ServerHttpRequest var5, ServerHttpResponse var6);
              }

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

              /**
               * @author jam
               * @date 2021/7/8 10:10 上午
               */
              @RestControllerAdvice
              public class ResponseAdvice implements ResponseBodyAdvice<object> {
                  @Autowired
                  private ObjectMapper objectMapper;
              
                  @Override
                  public boolean supports(MethodParameter methodParameter, Class<!--? extends HttpMessageConverter<?-->> aClass) {
                      return true;
                  }
              
                  @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));
                      }        
                      return ResultData.success(o);
                  }
              }

              需要注意兩個地方:

              @RestControllerAdvice注解

              @RestControllerAdvice@RestController注解的增強,可以實現(xiàn)三個方面的功能:

              • 全局異常處理

              • 全局?jǐn)?shù)據(jù)綁定全

              • 局?jǐn)?shù)據(jù)預(yù)處理

              String類型判斷

              if(o instanceof String){
                return objectMapper.writeValueAsString(ResultData.success(o));
              }

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

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

              @GetMapping("/hello")
              public String getStr(){
                  return "hello,javadaily";
              }

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

              @GetMapping("/hello")
              public String getStr(){
                return "hello,javadaily";
              }

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

              接口異常問題

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

              @GetMapping("/wrong")
              public int error(){
                  int i = 9/0;
                  return i;
              }

              返回的結(jié)果為:

              SpringBoot如何統(tǒng)一后端返回格式

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

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

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

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

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

              {
                "timestamp": "2021-07-08T08:05:15.423+00:00",
                "status": 500,
                "error": "Internal Server Error",
                "path": "/wrong"
              }

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

              @GetMapping("/wrong")
              public int error(){
                  int i;
                  try{
                      i = 9/0;
                  }catch (Exception e){
                      log.error("error:{}",e);
                      i = 0;
                  }
                  return i;
              }

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

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

              @GetMapping("error1")
              public void empty(){
              	throw  new RuntimeException("自定義異常");
              }

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

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

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

              @Slf4j
              @RestControllerAdvice
              public class RestExceptionHandler {
                  /**
                   * 默認(rèn)全局異常處理。
                   * @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());
                  }
              
              }

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

              • @RestControllerAdvice,RestController的增強類,可用于實現(xiàn)全局異常處理器

              • @ExceptionHandler,統(tǒng)一處理某一類異常,從而減少代碼重復(fù)率和復(fù)雜度,比如要獲取自定義異常可以@ExceptionHandler(BusinessException.class)

              • @ResponseStatus指定客戶端收到的http狀態(tài)碼

              體驗效果

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

              @GetMapping("error1")
              public void empty(){
                  throw  new RuntimeException("自定義異常");
              }

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

              {
                "status": 500,
                "message": "自定義異常",
                "data": null,
                "timestamp": 1625795902556
              }

              基本滿足我們的需求了。

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

              {
                "status": 100,
                "message": "操作成功",
                "data": {
                  "status": 500,
                  "message": "自定義異常",
                  "data": null,
                  "timestamp": 1625796167986
                },
                "timestamp": 1625796168008
              }

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

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

              要讓全局異常接入標(biāo)準(zhǔn)格式很簡單,因為全局異常處理器已經(jīng)幫我們封裝好了標(biāo)準(zhǔn)格式,我們只需要直接返回給客戶端即可。

              @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);
              }

              關(guān)鍵代碼:

              if(o instanceof ResultData){
                return o;
              }

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

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

              {
                "status": 500,
                "message": "自定義異常",
                "data": null,
                "timestamp": 1625796580778
              }

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

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

              感謝各位的閱讀,以上就是“SpringBoot如何統(tǒng)一后端返回格式”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對SpringBoot如何統(tǒng)一后端返回格式這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

              向AI問一下細節(jié)

              免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

              AI