1. SpringBoot:如何優(yōu)雅地進(jìn)行響應(yīng)數(shù)據(jù)封裝、異常處理?

        共 9354字,需瀏覽 19分鐘

         ·

        2022-08-04 14:10

        背景

        越來(lái)越多的項(xiàng)目開(kāi)始基于前后端分離的模式進(jìn)行開(kāi)發(fā),這對(duì)后端接口的報(bào)文格式便有了一定的要求。通常,我們會(huì)采用JSON格式作為前后端交換數(shù)據(jù)格式,從而減少溝通成本等。

        這篇文章,就帶大家了解一下基于SpringBoot框架來(lái)封裝返回報(bào)文以及統(tǒng)一異常處理。

        報(bào)文基本格式

        一般報(bào)文格式通常會(huì)包含狀態(tài)碼、狀態(tài)描述(或錯(cuò)誤提示信息)、業(yè)務(wù)數(shù)據(jù)等信息。在此基礎(chǔ)上,不同的架構(gòu)師、項(xiàng)目搭建者可能會(huì)有所調(diào)整。但從整體上來(lái)說(shuō),基本上都是大同小異。

        在SpringBoot項(xiàng)目中,通常接口返回的報(bào)文中至少包含三個(gè)屬性:

        • code:請(qǐng)求接口的返回碼,成功或者異常等返回編碼,例如定義請(qǐng)求成功。
        • message:請(qǐng)求接口的描述,也就是對(duì)返回編碼的描述。
        • data:請(qǐng)求接口成功,返回的業(yè)務(wù)數(shù)據(jù)。

        示例報(bào)文如下:

        {
          "code":200,
          "message":"SUCCESS",
          "data":{
            "info":"測(cè)試成功"
          }
        }

        在上述報(bào)文格式中,不同的設(shè)計(jì)者是會(huì)有一些分歧的,特別是code值的定義。如果完全基于RESTful API設(shè)計(jì)的話(huà),code字段可能就不需要存在了,而是通過(guò)HTTP協(xié)議中提供的GET、POST、PUT、DELETE操作等來(lái)完成資源的訪(fǎng)問(wèn)。

        但在實(shí)踐中,不論是出于目前國(guó)內(nèi)大多數(shù)程序員的習(xí)慣,還是受限于HTTP協(xié)議提供的操作方法的局限性,很少完全遵照RESTful API方式進(jìn)行設(shè)計(jì)。通常都是通過(guò)自定義Code值的形式來(lái)賦予它業(yè)務(wù)意義或業(yè)務(wù)錯(cuò)誤編碼。

        雖然可以不用完全遵守RESTful API風(fēng)格來(lái)定義Code,在Code值的自定義中,也存在兩種形式:遵循HTTP狀態(tài)碼和自主定義。

        像上面的示例,用200表示返回成功,這就是遵循HTTP響應(yīng)狀態(tài)碼的形式來(lái)返回,比如還有其他的400、401、404、500等。當(dāng)然,還有完全自主定義的,比如用0表示成功,1表示失敗,然后再跟進(jìn)通用編碼、業(yè)務(wù)分類(lèi)編碼等進(jìn)行定義。

        在此,筆者暫不評(píng)論每種形式的好壞,只列舉了常規(guī)的幾種形式,大家了解對(duì)應(yīng)的情況,做到心中有數(shù),有所選擇即可。

        響應(yīng)參數(shù)封裝實(shí)踐

        創(chuàng)建一個(gè)SpringBoot項(xiàng)目,并引入Lombok依賴(lài)(精簡(jiǎn)代碼),對(duì)應(yīng)的核心依賴(lài)如下:

            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </dependency>
                <dependency>
                    <groupId>org.projectlombok</groupId>
                    <artifactId>lombok</artifactId>
                </dependency>
            </dependencies>

        創(chuàng)建枚舉類(lèi),用于定義返回的錯(cuò)誤碼:

        @Getter
        @AllArgsConstructor
        public enum ResponseCodeEnums {

         SUCCESS(200, "success"),
         FAIL(500, "failed"),

         HTTP_STATUS_200(200, "ok"),
         HTTP_STATUS_400(400, "request error"),
         HTTP_STATUS_401(401, "no authentication"),
         HTTP_STATUS_403(403, "no authorities"),
         HTTP_STATUS_500(500, "server error");

         private final int code;

         private final String message;
        }

        這里只定義了一些通用的、基于的HTTP響應(yīng)狀態(tài)碼,業(yè)務(wù)相關(guān)的編碼可根據(jù)業(yè)務(wù)需求進(jìn)行定義。

        定義統(tǒng)一返回結(jié)果實(shí)體類(lèi):

        @Data
        public class ResponseInfo<T> {

         /**
          * 狀態(tài)碼
          */
         protected int code;

         /**
          * 響應(yīng)信息
          */
         protected String message;

         /**
          * 返回?cái)?shù)據(jù)
          */
         private T data;

         public static <T> ResponseInfo<T> success() {
          return new ResponseInfo<>();
         }

         public static <T> ResponseInfo<T> success(T data) {
          return new ResponseInfo<>(data);
         }

         public static <T> ResponseInfo<T> fail(String message) {
          return new ResponseInfo<>(ResponseCodeEnums.FAIL.getCode(), message);
         }

         public ResponseInfo() {
          this.code = ResponseCodeEnums.SUCCESS.getCode();
          this.message = ResponseCodeEnums.SUCCESS.getMessage();
         }

         public ResponseInfo(ResponseCodeEnums statusEnums) {
          this.code = statusEnums.getCode();
          this.message = statusEnums.getMessage();
         }

         /**
          * 若沒(méi)有數(shù)據(jù)返回,可以人為指定狀態(tài)碼和提示信息
          */
         public ResponseInfo(int code, String msg) {
          this.code = code;
          this.message = msg;
         }

         /**
          * 有數(shù)據(jù)返回時(shí),狀態(tài)碼為200,默認(rèn)提示信息為“操作成功!”
          */
         public ResponseInfo(T data) {
          this.data = data;
          this.code = ResponseCodeEnums.SUCCESS.getCode();
          this.message = ResponseCodeEnums.SUCCESS.getMessage();
         }

         /**
          * 有數(shù)據(jù)返回,狀態(tài)碼為 200,人為指定提示信息
          */
         public ResponseInfo(T data, String msg) {
          this.data = data;
          this.code = ResponseCodeEnums.SUCCESS.getCode();
          this.message = msg;
         }
        }

        在ResponseInfo中運(yùn)用了泛型和公共方法、構(gòu)造方法的封裝,方便在業(yè)務(wù)中使用。示例中只提供了部分方法的封裝,根據(jù)自身業(yè)務(wù)場(chǎng)景和需要可進(jìn)一步封裝。

        統(tǒng)一報(bào)文封裝在接口中的使用:

        @Slf4j
        @RestController
        public class TestController {

         @RequestMapping("/calc")
         public ResponseInfo<String> calc(Integer id) {
          try {
           // 模擬異常業(yè)務(wù)代碼
           int num = 1 / id;
           log.info("計(jì)算結(jié)果num={}", num);
           return ResponseInfo.success();
          } catch (Exception e) {
           return ResponseInfo.fail("系統(tǒng)異常,請(qǐng)聯(lián)系管理員!");
          }
         }
        }

        在瀏覽器中訪(fǎng)問(wèn):http://localhost:8080/calc,返回結(jié)果如下:

        {
            "code": 500,
            "message""系統(tǒng)異常,請(qǐng)聯(lián)系管理員!",
            "data": null
        }

        這是因?yàn)闆](méi)傳遞id參數(shù),導(dǎo)致業(yè)務(wù)拋異常,走異常報(bào)文返回。

        在瀏覽器中訪(fǎng)問(wèn):http://localhost:8080/calc?id=1,返回結(jié)果如下:

        {
            "code": 200,
            "message""success",
            "data": null
        }

        正常返回結(jié)果。

        統(tǒng)一異常處理

        在上述實(shí)例中,我們通過(guò)try...catch的形式捕獲異常,并進(jìn)行處理。在SpringBoot中,我們可以通過(guò)RestControllerAdvice注解來(lái)定義全局異常處理,這樣就無(wú)需每處都try...catch了。

        @Slf4j
        @RestControllerAdvice
        public class ExceptionHandlerAdvice {

         /**
          * 參數(shù)格式異常處理
          */
         @ExceptionHandler({IllegalArgumentException.class})
         @ResponseStatus(HttpStatus.BAD_REQUEST)
         public ResponseInfo<String> badRequestException(IllegalArgumentException ex) {
          log.error("參數(shù)格式不合法:{}", ex.getMessage());
          return new ResponseInfo<>(HttpStatus.BAD_REQUEST.value() + """參數(shù)格式不符!");
         }

         /**
          * 權(quán)限不足異常處理
          */
         @ExceptionHandler({AccessDeniedException.class})
         @ResponseStatus(HttpStatus.FORBIDDEN)
         public ResponseInfo<String> badRequestException(AccessDeniedException ex) {
          return new ResponseInfo<>(HttpStatus.FORBIDDEN.value() + "", ex.getMessage());
         }

         /**
          * 參數(shù)缺失異常處理
          */
         @ExceptionHandler({MissingServletRequestParameterException.class})
         @ResponseStatus(HttpStatus.BAD_REQUEST)
         public ResponseInfo<String> badRequestException(Exception ex) {
          return new ResponseInfo<>(HttpStatus.BAD_REQUEST.value() + """缺少必填參數(shù)!");
         }

         /**
          * 空指針異常
          */
         @ExceptionHandler(NullPointerException.class)
         @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
         public ResponseInfo<String> handleTypeMismatchException(NullPointerException ex) {
          log.error("空指針異常,{}", ex.getMessage());
          return ResponseInfo.fail("空指針異常");
         }

         @ExceptionHandler(Exception.class)
         @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
         public ResponseInfo<String> handleUnexpectedServer(Exception ex) {
          log.error("系統(tǒng)異常:", ex);
          return ResponseInfo.fail("系統(tǒng)發(fā)生異常,請(qǐng)聯(lián)系管理員");
         }

         /**
          * 系統(tǒng)異常處理
          */
         @ExceptionHandler(Throwable.class)
         @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
         public ResponseInfo<String> exception(Throwable throwable) {
          log.error("系統(tǒng)異常", throwable);
          return new ResponseInfo<>(HttpStatus.INTERNAL_SERVER_ERROR.value() + "系統(tǒng)異常,請(qǐng)聯(lián)系管理員!");
         }
        }

        在上述方法中,對(duì)一些常見(jiàn)的異常進(jìn)行了統(tǒng)一處理。通常情況下,根據(jù)業(yè)務(wù)需要還會(huì)定義業(yè)務(wù)異常,并對(duì)業(yè)務(wù)異常進(jìn)行處理,大家可以根據(jù)自己項(xiàng)目中異常的使用情況進(jìn)行拓展。

        關(guān)于@RestControllerAdvice的幾點(diǎn)說(shuō)明:

        • @RestControllerAdvice注解包含了@Component注解,會(huì)把被注解的類(lèi)作為組件交給Spring來(lái)管理。
        • @RestControllerAdvice注解包含了@ResponseBody注解,異常處理完之后給調(diào)用方輸出一個(gè)JSON格式的封裝數(shù)據(jù)。
        • @RestControllerAdvice注解有一個(gè)basePackages屬性,該屬性用來(lái)攔截哪個(gè)包中的異常信息,一般不指定,攔截項(xiàng)目工程中的所有異常。
        • 在方法上通過(guò)@ExceptionHandler注解來(lái)指定具體的異常,在方法中處理該異常信息,最后將結(jié)果通過(guò)統(tǒng)一的JSON結(jié)構(gòu)體返回給調(diào)用者。

        重新定義一個(gè)接口:

         @RequestMapping("/calc1")
         public ResponseInfo<String> calc1(Integer id) {
          // 模擬異常業(yè)務(wù)代碼
          int num = 1 / id;
          log.info("計(jì)算結(jié)果num={}", num);
          return ResponseInfo.success();
         }

        在請(qǐng)求的時(shí)候,不傳遞id值,即在瀏覽器中訪(fǎng)問(wèn):

        {
            "code": 500,
            "message""空指針異常",
            "data": null
        }

        可以看到統(tǒng)一異常處理對(duì)空指針異常進(jìn)行了攔截處理,并返回了ExceptionHandlerAdvice中定義的統(tǒng)一報(bào)文格式。

        小結(jié)

        在使用SpringBoot或其他項(xiàng)目中,統(tǒng)一的報(bào)文格式和統(tǒng)一的異常處理都是必須的。本篇文章介紹了基于SpringBoot的實(shí)現(xiàn),如果你的項(xiàng)目中采用了其他的技術(shù)棧,則可考慮對(duì)應(yīng)的處理方式。同時(shí),日常中很多類(lèi)似的功能都可以統(tǒng)一進(jìn)行處理,避免大量無(wú)效的硬編碼。

        isEmpty 和 isBlank 的用法區(qū)別,至少一半的人答不上來(lái)...

        2022-08-02

        Spring Boot + EasyExcel導(dǎo)入導(dǎo)出,簡(jiǎn)直太好用了!

        2022-08-01

        代碼真的越改越爛。。

        2022-07-31

        Redis中的布隆過(guò)濾器與布谷鳥(niǎo)過(guò)濾器,你了解多少?

        2022-07-30

        為了實(shí)現(xiàn)CI/CD,先來(lái)定制一個(gè)Docker鏡像【實(shí)戰(zhàn)精華篇】

        2022-07-29

        Linux安裝Docker完整教程

        2022-07-28




        如果你覺(jué)得這篇文章不錯(cuò),那么,下篇通常會(huì)更好。備注“公眾號(hào)”添加微信好友(微信號(hào):zhuan2quan)

        ▲ 長(zhǎng)按關(guān)注”程序新視界“,洞察技術(shù)內(nèi)幕


        瀏覽 40
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 蜜桃AV秘 无码一区二区三区 | 一道本一区二区三区久久久久久久 | 巨骚综合少妇 | 亚洲精品乱码久久久久久久久久久久 | 亚洲高清免费视频 |