<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何優(yōu)雅的寫 Controller 層代碼?

          共 27969字,需瀏覽 56分鐘

           ·

          2022-07-12 02:48

          來(lái)源:https://bugpool.blog.csdn.net/article/details/105610962

          前言

          本篇主要要介紹的就是controller層的處理,一個(gè)完整的后端請(qǐng)求由4部分組成:1. 接口地址(也就是URL地址)、2. 請(qǐng)求方式(一般就是get、set,當(dāng)然還有put、delete)、3. 請(qǐng)求數(shù)據(jù)(request,有head跟body)、4. 響應(yīng)數(shù)據(jù)(response)

          本篇將解決以下3個(gè)問(wèn)題:

          1. 當(dāng)接收到請(qǐng)求時(shí),如何優(yōu)雅的校驗(yàn)參數(shù)
          2. 返回響應(yīng)數(shù)據(jù)該如何統(tǒng)一的進(jìn)行處理
          3. 接收到請(qǐng)求,處理業(yè)務(wù)邏輯時(shí)拋出了異常又該如何處理

          一、Controller層參數(shù)接收(太基礎(chǔ)了,可以跳過(guò))

          常見(jiàn)的請(qǐng)求就分為getpost2種

          @RestController
          @RequestMapping("/product/product-info")
          public class ProductInfoController {

              @Autowired
              ProductInfoService productInfoService;

              @GetMapping("/findById")
              public ProductInfoQueryVo findById(Integer id) {
               ...
              }

              @PostMapping("/page")
              public IPage findPage(Page page, ProductInfoQueryVo vo) {
               ...
              }
          }
          1. `@RestController`:之前解釋過(guò),`@RestController` = `@Controller` + `ResponseBody`。加上這個(gè)注解,springboot就會(huì)吧這個(gè)類當(dāng)成`controller`進(jìn)行處理,然后把所有返回的參數(shù)放到`ResponseBody`中
          2. `@RequestMapping`:請(qǐng)求的前綴,也就是所有該`Controller`下的請(qǐng)求都需要加上`/product/product-info`的前綴
          3. `@GetMapping("/findById")`:標(biāo)志這是一個(gè)`get`請(qǐng)求,并且需要通過(guò)`/findById`地址才可以訪問(wèn)到
          4. `@PostMapping("/page")`:同理,表示是個(gè)`post`請(qǐng)求
          5. `參數(shù)`:至于參數(shù)部分,只需要寫上`ProductInfoQueryVo`,前端過(guò)來(lái)的`json`請(qǐng)求便會(huì)通過(guò)映射賦值到對(duì)應(yīng)的對(duì)象中,例如請(qǐng)求這么寫,`productId`就會(huì)自動(dòng)被映射到`vo`對(duì)應(yīng)的屬性當(dāng)中
          size : 1
          current : 1

          productId : 1
          productName : 泡腳

          二、統(tǒng)一狀態(tài)碼

          1. 返回格式

          為了跟前端妹妹打好關(guān)系,我們通常需要對(duì)后端返回的數(shù)據(jù)進(jìn)行包裝一下,增加一下狀態(tài)碼狀態(tài)信息,這樣前端妹妹接收到數(shù)據(jù)就可以根據(jù)不同的狀態(tài)碼,判斷響應(yīng)數(shù)據(jù)狀態(tài),是否成功是否異常進(jìn)行不同的顯示。當(dāng)然這讓你擁有了更多跟前端妹妹的交流機(jī)會(huì),假設(shè)我們約定了1000就是成功的意思
          如果你不封裝,那么返回的數(shù)據(jù)是這樣子的

          {
            "productId"1,
            "productName""泡腳",
            "productPrice"100.00,
            "productDescription""中藥泡腳加按摩",
            "productStatus"0,
          }

          經(jīng)過(guò)封裝以后時(shí)這樣子的

          {
            "code"1000,
            "msg""請(qǐng)求成功",
            "data": {
              "productId"1,
              "productName""泡腳",
              "productPrice"100.00,
              "productDescription""中藥泡腳加按摩",
              "productStatus"0,
            }
          }

          2. 封裝ResultVo

          這些狀態(tài)碼肯定都是要預(yù)先編好的,怎么編呢?寫個(gè)常量1000?還是直接寫死1000?要這么寫就真的書(shū)白讀的了,寫狀態(tài)碼當(dāng)然是用枚舉拉

          1. 首先先定義一個(gè)`狀態(tài)碼`的接口,所有`狀態(tài)碼`都需要實(shí)現(xiàn)它,有了標(biāo)準(zhǔn)才好做事
          public interface StatusCode {
              public int getCode();
              public String getMsg();
          }
          1. 然后去找前端妹妹,跟他約定好狀態(tài)碼(這可能是你們唯一的約定了)枚舉類嘛,當(dāng)然不能有`setter`方法了,因此我們不能在用`@Data`注解了,我們要用`@Getter`
          @Getter
          public enum ResultCode implements StatusCode{
              SUCCESS(1000"請(qǐng)求成功"),
              FAILED(1001"請(qǐng)求失敗"),
              VALIDATE_ERROR(1002"參數(shù)校驗(yàn)失敗"),
              RESPONSE_PACK_ERROR(1003"response返回包裝失敗");

              private int code;
              private String msg;

              ResultCode(int code, String msg) {
                  this.code = code;
                  this.msg = msg;
              }
          }
          1. 寫好枚舉類,就開(kāi)始寫`ResultVo`包裝類了,我們預(yù)設(shè)了幾種默認(rèn)的方法,比如成功的話就默認(rèn)傳入`object`就可以了,我們自動(dòng)包裝成`success`
          @Data
          public class ResultVo {
              // 狀態(tài)碼
              private int code;

              // 狀態(tài)信息
              private String msg;

              // 返回對(duì)象
              private Object data;

              // 手動(dòng)設(shè)置返回vo
              public ResultVo(int code, String msg, Object data) {
                  this.code = code;
                  this.msg = msg;
                  this.data = data;
              }

              // 默認(rèn)返回成功狀態(tài)碼,數(shù)據(jù)對(duì)象
              public ResultVo(Object data) {
                  this.code = ResultCode.SUCCESS.getCode();
                  this.msg = ResultCode.SUCCESS.getMsg();
                  this.data = data;
              }

              // 返回指定狀態(tài)碼,數(shù)據(jù)對(duì)象
              public ResultVo(StatusCode statusCode, Object data) {
                  this.code = statusCode.getCode();
                  this.msg = statusCode.getMsg();
                  this.data = data;
              }

              // 只返回狀態(tài)碼
              public ResultVo(StatusCode statusCode) {
                  this.code = statusCode.getCode();
                  this.msg = statusCode.getMsg();
                  this.data = null;
              }
          }
          1. 使用,現(xiàn)在的返回肯定就不是`return data;`這么簡(jiǎn)單了,而是需要`new ResultVo(data);`
              @PostMapping("/findByVo")
              public ResultVo findByVo(@Validated ProductInfoVo vo) {
                  ProductInfo productInfo = new ProductInfo();
                  BeanUtils.copyProperties(vo, productInfo);
                  return new ResultVo(productInfoService.getOne(new QueryWrapper(productInfo)));
              }

          最后返回就會(huì)是上面帶了狀態(tài)碼的數(shù)據(jù)了

          三、統(tǒng)一校驗(yàn)

          1. 原始做法

          假設(shè)有一個(gè)添加ProductInfo的接口,在沒(méi)有統(tǒng)一校驗(yàn)時(shí),我們需要這么做

          @Data
          public class ProductInfoVo {
              // 商品名稱
              private String productName;
              // 商品價(jià)格
              private BigDecimal productPrice;
              // 上架狀態(tài)
              private Integer productStatus;
          }
              @PostMapping("/findByVo")
              public ProductInfo findByVo(ProductInfoVo vo) {
                  if (StringUtils.isNotBlank(vo.getProductName())) {
                      throw new APIException("商品名稱不能為空");
                  }
                  if (null != vo.getProductPrice() && vo.getProductPrice().compareTo(new BigDecimal(0)) < 0) {
                      throw new APIException("商品價(jià)格不能為負(fù)數(shù)");
                  }
                  ...
                  
                  ProductInfo productInfo = new ProductInfo();
                  BeanUtils.copyProperties(vo, productInfo);
                  return new ResultVo(productInfoService.getOne(new QueryWrapper(productInfo)));
              }

          這if寫的人都傻了,能忍嗎?肯定不能忍啊

          2. @Validated參數(shù)校驗(yàn)

          好在有@Validated,又是一個(gè)校驗(yàn)參數(shù)必備良藥了。有了@Validated我們只需要再vo上面加一點(diǎn)小小的注解,便可以完成校驗(yàn)功能

          @Data
          public class ProductInfoVo {
              @NotNull(message = "商品名稱不允許為空")
              private String productName;

              @Min(value = 0, message = "商品價(jià)格不允許為負(fù)數(shù)")
              private BigDecimal productPrice;

              private Integer productStatus;
          }
              @PostMapping("/findByVo")
              public ProductInfo findByVo(@Validated ProductInfoVo vo) {
                  ProductInfo productInfo = new ProductInfo();
                  BeanUtils.copyProperties(vo, productInfo);
                  return new ResultVo(productInfoService.getOne(new QueryWrapper(productInfo)));
              }

          運(yùn)行看看,如果參數(shù)不對(duì)會(huì)發(fā)生什么?
          我們故意傳一個(gè)價(jià)格為-1的參數(shù)過(guò)去

          productName : 泡腳
          productPrice : -1
          productStatus : 1
          {
            "timestamp""2020-04-19T03:06:37.268+0000",
            "status"400,
            "error""Bad Request",
            "errors": [
              {
                "codes": [
                  "Min.productInfoVo.productPrice",
                  "Min.productPrice",
                  "Min.java.math.BigDecimal",
                  "Min"
                ],
                "arguments": [
                  {
                    "codes": [
                      "productInfoVo.productPrice",
                      "productPrice"
                    ],
                    "defaultMessage""productPrice",
                    "code""productPrice"
                  },
                  0
                ],
                "defaultMessage""商品價(jià)格不允許為負(fù)數(shù)",
                "objectName""productInfoVo",
                "field""productPrice",
                "rejectedValue"-1,
                "bindingFailure"false,
                "code""Min"
              }
            ],
            "message""Validation failed for object\u003d\u0027productInfoVo\u0027. Error count: 1",
            "trace""org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors\nField error in object \u0027productInfoVo\u0027 on field \u0027productPrice\u0027: rejected value [-1]; codes [Min.productInfoVo.productPrice,Min.productPrice,Min.java.math.BigDecimal,Min]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [productInfoVo.productPrice,productPrice]; arguments []; default message [productPrice],0]; default message [商品價(jià)格不允許為負(fù)數(shù)]\n\tat org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:164)\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:167)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134)\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:660)\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:741)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:124)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1594)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\tat java.base/java.lang.Thread.run(Thread.java:830)\n",
            "path""/leilema/product/product-info/findByVo"
          }

          大功告成了嗎?雖然成功校驗(yàn)了參數(shù),也返回了異常,并且?guī)?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(150, 84, 181);">"商品價(jià)格不允許為負(fù)數(shù)"的信息。但是你要是這樣返回給前端,前端妹妹就提刀過(guò)來(lái)了,當(dāng)年約定好的狀態(tài)碼,你個(gè)負(fù)心人說(shuō)忘就忘?用戶體驗(yàn)小于等于0啊!所以我們要進(jìn)行優(yōu)化一下,每次出現(xiàn)異常的時(shí)候,自動(dòng)把狀態(tài)碼寫好,不負(fù)妹妹之約!

          3. 優(yōu)化異常處理

          首先我們先看看校驗(yàn)參數(shù)拋出了什么異常

          Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors

          我們看到代碼拋出了org.springframework.validation.BindException的綁定異常,因此我們的思路就是AOP攔截所有controller,然后異常的時(shí)候統(tǒng)一攔截起來(lái),進(jìn)行封裝!完美!

          玩你個(gè)頭啊完美,這么呆瓜的操作springboot不知道嗎?spring mvc當(dāng)然知道拉,所以給我們提供了一個(gè)@RestControllerAdvice來(lái)增強(qiáng)所有@RestController,然后使用@ExceptionHandler注解,就可以攔截到對(duì)應(yīng)的異常。

          這里我們就攔截BindException.class就好了。最后在返回之前,我們對(duì)異常信息進(jìn)行包裝一下,包裝成ResultVo,當(dāng)然要跟上ResultCode.VALIDATE_ERROR的異常狀態(tài)碼。這樣前端妹妹看到VALIDATE_ERROR的狀態(tài)碼,就會(huì)調(diào)用數(shù)據(jù)校驗(yàn)異常的彈窗提示用戶哪里沒(méi)填好

          @RestControllerAdvice
          public class ControllerExceptionAdvice {

              @ExceptionHandler({BindException.class})
              public ResultVo MethodArgumentNotValidExceptionHandler(BindException e
          {
                  // 從異常對(duì)象中拿到ObjectError對(duì)象
                  ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
                  return new ResultVo(ResultCode.VALIDATE_ERROR, objectError.getDefaultMessage());
              }
          }

          來(lái)康康效果,完美。1002與前端妹妹約定好的狀態(tài)碼

          {
            "code"1002,
            "msg""參數(shù)校驗(yàn)失敗",
            "data""商品價(jià)格不允許為負(fù)數(shù)"
          }

          四、統(tǒng)一響應(yīng)

          1. 統(tǒng)一包裝響應(yīng)

          再回頭看一下controller層的返回

          return new ResultVo(productInfoService.getOne(new QueryWrapper(productInfo)));

          開(kāi)發(fā)小哥肯定不樂(lè)意了,誰(shuí)有空天天寫new ResultVo(data)啊,我就想返回一個(gè)實(shí)體!怎么實(shí)現(xiàn)我不管!好把,那就是AOP攔截所有Controller,再@After的時(shí)候統(tǒng)一幫你封裝一下咯

          怕是上一次臉打的不夠疼,springboot能不知道這么個(gè)操作嗎?

          @RestControllerAdvice(basePackages = {"com.bugpool.leilema"})
          public class ControllerResponseAdvice implements ResponseBodyAdvice<Object{
              @Override
              public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
                  // response是ResultVo類型,或者注釋了NotControllerResponseAdvice都不進(jìn)行包裝
                  return !methodParameter.getParameterType().isAssignableFrom(ResultVo.class);
              }

              @Override
              public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest request, ServerHttpResponse response) {
                  // String類型不能直接包裝
                  if (returnType.getGenericParameterType().equals(String.class)) {
                      ObjectMapper objectMapper = new ObjectMapper();
                      try {
                          // 將數(shù)據(jù)包裝在ResultVo里后轉(zhuǎn)換為json串進(jìn)行返回
                          return objectMapper.writeValueAsString(new ResultVo(data));
                      } catch (JsonProcessingException e) {
                          throw new APIException(ResultCode.RESPONSE_PACK_ERROR, e.getMessage());
                      }
                  }
                  // 否則直接包裝成ResultVo返回
                  return new ResultVo(data);
              }
          }
          1. @RestControllerAdvice(basePackages = {"com.bugpool.leilema"})自動(dòng)掃描了所有指定包下的controller,在Response時(shí)進(jìn)行統(tǒng)一處理
          2. 重寫supports方法,也就是說(shuō),當(dāng)返回類型已經(jīng)是ResultVo了,那就不需要封裝了,當(dāng)不等與ResultVo時(shí)才進(jìn)行調(diào)用beforeBodyWrite方法,跟過(guò)濾器的效果是一樣的
          3. 最后重寫我們的封裝方法beforeBodyWrite,注意除了String的返回值有點(diǎn)特殊,無(wú)法直接封裝成json,我們需要進(jìn)行特殊處理,其他的直接new ResultVo(data);就ok了

          打完收工,康康效果

              @PostMapping("/findByVo")
              public ProductInfo findByVo(@Validated ProductInfoVo vo) {
                  ProductInfo productInfo = new ProductInfo();
                  BeanUtils.copyProperties(vo, productInfo);
                  return productInfoService.getOne(new QueryWrapper(productInfo));
              }

          此時(shí)就算我們返回的是po,接收到的返回就是標(biāo)準(zhǔn)格式了,開(kāi)發(fā)小哥露出了欣慰的笑容

          {
            "code"1000,
            "msg""請(qǐng)求成功",
            "data": {
              "productId"1,
              "productName""泡腳",
              "productPrice"100.00,
              "productDescription""中藥泡腳加按摩",
              "productStatus"0,
              ...
            }
          }

          2. NOT統(tǒng)一響應(yīng)

          不開(kāi)啟統(tǒng)一響應(yīng)原因

          開(kāi)發(fā)小哥是開(kāi)心了,可是其他系統(tǒng)就不開(kāi)心了。舉個(gè)例子:我們項(xiàng)目中集成了一個(gè)健康檢測(cè)的功能,也就是這貨

          @RestController
          public class HealthController {
              @GetMapping("/health")
              public String health() {
                  return "success";
              }
          }

          公司部署了一套校驗(yàn)所有系統(tǒng)存活狀態(tài)的工具,這工具就定時(shí)發(fā)送get請(qǐng)求給我們系統(tǒng)

          “兄弟,你死了嗎?”
          “我沒(méi)死,滾”
          “兄弟,你死了嗎?”
          “我沒(méi)死,滾”

          是的,web項(xiàng)目的本質(zhì)就是復(fù)讀機(jī)。一旦發(fā)送的請(qǐng)求沒(méi)響應(yīng),就會(huì)給負(fù)責(zé)人發(fā)信息(企業(yè)微信或者短信之類的),你的系統(tǒng)死啦!趕緊回來(lái)排查bug吧!讓大家感受一下。每次看到我都射射發(fā)抖,早上6點(diǎn)!我tm!!!!!

          好吧,沒(méi)辦法,人家是老大,人家要的返回不是

          {
            "code"1000,
            "msg""請(qǐng)求成功",
            "data""success"
          }

          人家要的返回只要一個(gè)success,人家定的標(biāo)準(zhǔn)不可能因?yàn)槟阋粋€(gè)系統(tǒng)改。俗話說(shuō)的好,如果你改變不了環(huán)境,那你就只能我****

          新增不進(jìn)行封裝注解

          因?yàn)榘俜种?9的請(qǐng)求還是需要包裝的,只有個(gè)別不需要,寫在包裝的過(guò)濾器吧?又不是很好維護(hù),那就加個(gè)注解好了。所有不需要包裝的就加上這個(gè)注解。

          @Target({ElementType.METHOD})
          @Retention(RetentionPolicy.RUNTIME)
          public @interface NotControllerResponseAdvice {
          }

          然后在我們的增強(qiáng)過(guò)濾方法上過(guò)濾包含這個(gè)注解的方法

          @RestControllerAdvice(basePackages = {"com.bugpool.leilema"})
          public class ControllerResponseAdvice implements ResponseBodyAdvice<Object{
              @Override
              public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
                  // response是ResultVo類型,或者注釋了NotControllerResponseAdvice都不進(jìn)行包裝
                  return !(methodParameter.getParameterType().isAssignableFrom(ResultVo.class)
                          || methodParameter.hasMethodAnnotation(NotControllerResponseAdvice.class))
          ;
              }
              ...

          最后就在不需要包裝的方法上加上注解

          @RestController
          public class HealthController {

              @GetMapping("/health")
              @NotControllerResponseAdvice
              public String health() {
                  return "success";
              }
          }

          這時(shí)候就不會(huì)自動(dòng)封裝了,而其他沒(méi)加注解的則依舊自動(dòng)包裝

          五、統(tǒng)一異常

          每個(gè)系統(tǒng)都會(huì)有自己的業(yè)務(wù)異常,比如庫(kù)存不能小于0子類的,這種異常并非程序異常,而是業(yè)務(wù)操作引發(fā)的異常,我們也需要進(jìn)行規(guī)范的編排業(yè)務(wù)異常狀態(tài)碼,并且寫一個(gè)專門處理的異常類,最后通過(guò)剛剛學(xué)習(xí)過(guò)的異常攔截統(tǒng)一進(jìn)行處理,以及打日志

          1. 異常狀態(tài)碼枚舉,既然是狀態(tài)碼,那就肯定要實(shí)現(xiàn)我們的標(biāo)準(zhǔn)接口`StatusCode`
          @Getter
          public enum  AppCode implements StatusCode {

              APP_ERROR(2000"業(yè)務(wù)異常"),
              PRICE_ERROR(2001"價(jià)格異常");

              private int code;
              private String msg;

              AppCode(int code, String msg) {
                  this.code = code;
                  this.msg = msg;
              }
          }
          1. 異常類,這里需要強(qiáng)調(diào)一下,`code`代表`AppCode`的異常狀態(tài)碼,也就是2000;`msg`代表`業(yè)務(wù)異常`,這只是一個(gè)大類,一般前端會(huì)放到彈窗`title`上;最后`super(message);`這才是拋出的詳細(xì)信息,在前端顯示在`彈窗體`中,在`ResultVo`則保存在`data`中
          @Getter
          public class APIException extends RuntimeException {
              private int code;
              private String msg;

              // 手動(dòng)設(shè)置異常
              public APIException(StatusCode statusCode, String message) {
                  // message用于用戶設(shè)置拋出錯(cuò)誤詳情,例如:當(dāng)前價(jià)格-5,小于0
                  super(message);
                  // 狀態(tài)碼
                  this.code = statusCode.getCode();
                  // 狀態(tài)碼配套的msg
                  this.msg = statusCode.getMsg();
              }

              // 默認(rèn)異常使用APP_ERROR狀態(tài)碼
              public APIException(String message) {
                  super(message);
                  this.code = AppCode.APP_ERROR.getCode();
                  this.msg = AppCode.APP_ERROR.getMsg();
              }

          }
          1. 最后進(jìn)行統(tǒng)一異常的攔截,這樣無(wú)論在`service`層還是`controller`層,開(kāi)發(fā)人員只管拋出`API異常`,不需要關(guān)系怎么返回給前端,更不需要關(guān)心`日志`的打印
          @RestControllerAdvice
          public class ControllerExceptionAdvice {

              @ExceptionHandler({BindException.class})
              public ResultVo MethodArgumentNotValidExceptionHandler(BindException e
          {
                  // 從異常對(duì)象中拿到ObjectError對(duì)象
                  ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
                  return new ResultVo(ResultCode.VALIDATE_ERROR, objectError.getDefaultMessage());
              }

              @ExceptionHandler(APIException.class)
              public ResultVo APIExceptionHandler(APIException e
          {
               // log.error(e.getMessage(), e); 由于還沒(méi)集成日志框架,暫且放著,寫上TODO
                  return new ResultVo(e.getCode(), e.getMsg(), e.getMessage());
              }
          }
          1. 最后使用,我們的代碼只需要這么寫
                  if (null == orderMaster) {
                      throw new APIException(AppCode.ORDER_NOT_EXIST, "訂單號(hào)不存在:" + orderId);
                  }
          {
            "code"2003,
            "msg""訂單不存在",
            "data""訂單號(hào)不存在:1998"
          }

          就會(huì)自動(dòng)拋出AppCode.ORDER_NOT_EXIST狀態(tài)碼的響應(yīng),并且?guī)袭惓T敿?xì)信息訂單號(hào)不存在:xxxx。后端小哥開(kāi)發(fā)有效率,前端妹妹獲取到2003狀態(tài)碼,調(diào)用對(duì)應(yīng)警告彈窗,title寫上訂單不存在body詳細(xì)信息記載"訂單號(hào)不存在:1998"。同時(shí)日志還自動(dòng)打上去了!666!老哥們?nèi)B點(diǎn)個(gè)贊!

          程序汪資料鏈接

          程序汪接的7個(gè)私活都在這里,經(jīng)驗(yàn)整理

          Java項(xiàng)目分享  最新整理全集,找項(xiàng)目不累啦 07版

          堪稱神級(jí)的Spring Boot手冊(cè),從基礎(chǔ)入門到實(shí)戰(zhàn)進(jìn)階

          臥槽!字節(jié)跳動(dòng)《算法中文手冊(cè)》火了,完整版 PDF 開(kāi)放下載!

          臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開(kāi)放下載!

          字節(jié)跳動(dòng)總結(jié)的設(shè)計(jì)模式 PDF 火了,完整版開(kāi)放下載!


          歡迎添加程序汪個(gè)人微信 itwang009  進(jìn)粉絲群或圍觀朋友

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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  一本大道AV伊人久久综合蜜芽 | 国产无码一区二区三区视频 | 无码中文字幕在线视频 | 一区二区国产黄片视频在线 | 精品偷拍|