尋找寫程式碼感覺(十六)之 整合Validation做引數校驗

久曲健發表於2022-02-05

一、寫在前面

今天是大年初五了......

不知不覺,又要上班了,美好的假期只剩一天了,有點不捨呢!

也不知道為什麼,總感覺像沒睡醒一樣,也不是因為眼睛小,更多應該是自尋煩惱,想得多罷了。

二、引數校驗任務拆解

  • 對儲存介面和查詢介面增加引數校驗
  • 校驗不通過時,前端彈出錯誤提示

三、整合Validation做引數校驗實現

1、引入依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2、對查詢介面增加引數校驗

2.1、給對應實體欄位新增校驗,示例程式碼如下:


package com.rongrong.wiki.req;

import lombok.Data;

import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;

@Data
public class PageReq {
    private int page;
    @NotNull(message = "【每頁條數】不能為空")
    @Max(value = 100, message = "【每頁條數】不能超過100")
    private int size;
}

2.2、再給對應介面新增校驗,示例程式碼如下:

   /**
     * 查詢功能,支援按名字模糊查詢
     *
     * @param eBookReq
     * @return
     */
    @GetMapping("/list")
    public CommonResp list(@Valid EBookQueryReq eBookReq) {
        CommonResp<PageResp<EBookResp>> resp = new CommonResp<>();
        PageResp<EBookResp> list = eBookService.list(eBookReq);
        resp.setMessage("執行查詢成功!");
        resp.setContent(list);
        return resp;
    } 

2.3、測試介面是否有校驗了

GET http://localhost:8888/ebook/list?page=1&size=1000

HTTP/1.1 400 
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sat, 05 Feb 2022 01:26:51 GMT
Connection: close

{
  "timestamp": "2022-02-05T01:26:51.739+00:00",
  "status": 400,
  "error": "Bad Request",
  "trace": "org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors\nField error in object 'EBookQueryReq' on field 'size': rejected value [1000]; codes [Max.EBookQueryReq.size,Max.size,Max.int,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [EBookQueryReq.size,size]; arguments []; default message [size],100]; default message [【每頁條數】不能超過100]\r\n\tat org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:170)\r\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:170)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:893)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:807)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1061)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:961)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:626)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:733)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)\r\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.lang.Thread.run(Thread.java:748)\r\n",
  "message": "Validation failed for object='EBookQueryReq'. Error count: 1",
  "errors": [
    {
      "codes": [
        "Max.EBookQueryReq.size",
        "Max.size",
        "Max.int",
        "Max"
      ],
      "arguments": [
        {
          "codes": [
            "EBookQueryReq.size",
            "size"
          ],
          "arguments": null,
          "defaultMessage": "size",
          "code": "size"
        },
        100
      ],
      "defaultMessage": "【每頁條數】不能超過100",
      "objectName": "EBookQueryReq",
      "field": "size",
      "rejectedValue": 1000,
      "bindingFailure": false,
      "code": "Max"
    }
  ],
  "path": "/ebook/list"
}

Response code: 400; Time: 105ms; Content length: 5763 bytes

由上可知,已經觸發了校驗,但這樣的提示,並不是我們想要的,需要對校驗做進一步處理。

2.4、統一異常處理

加入後,將會對controller中的異常進行處理,示例程式碼如下:

package com.rongrong.wiki.controller;

import com.rongrong.wiki.exception.BusinessException;
import com.rongrong.wiki.resp.CommonResp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 統一異常處理、資料預處理等
 */
@ControllerAdvice
public class ControllerExceptionHandler {

    private static final Logger LOG = LoggerFactory.getLogger(ControllerExceptionHandler.class);

    /**
     * 校驗異常統一處理
     * @param e
     * @return
     */
    @ExceptionHandler(value = BindException.class)
    @ResponseBody
    public CommonResp validExceptionHandler(BindException e) {
        CommonResp commonResp = new CommonResp();
        LOG.warn("引數校驗失敗:{}", e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
        commonResp.setSuccess(false);
        commonResp.setMessage(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
        return commonResp;
    }

    /**
     * 校驗異常統一處理
     * @param e
     * @return
     */
    @ExceptionHandler(value = BusinessException.class)
    @ResponseBody
    public CommonResp validExceptionHandler(BusinessException e) {
        CommonResp commonResp = new CommonResp();
        LOG.warn("業務異常:{}", e.getCode().getDesc());
        commonResp.setSuccess(false);
        commonResp.setMessage(e.getCode().getDesc());
        return commonResp;
    }

    /**
     * 校驗異常統一處理
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public CommonResp validExceptionHandler(Exception e) {
        CommonResp commonResp = new CommonResp();
        LOG.error("系統異常:", e);
        commonResp.setSuccess(false);
        commonResp.setMessage("系統出現異常,請聯絡管理員");
        return commonResp;
    }
}

再次呼叫查詢介面,測試結果如下:

3、對儲存介面增加引數校驗

和上面的查詢介面一樣,也是需要將需要校驗欄位加上註解校驗,再對介面加上對應註解,即完成校驗操作,示例程式碼如下:


    @NotNull(message = "【名稱】不能為空")
    private String name;

    /**
     * 新增或編輯功能
     *
     * @param eBookSaveReq
     * @return
     */
    @PostMapping("/save")
    public CommonResp save(@Valid @RequestBody EBookSaveReq eBookSaveReq) {
        CommonResp resp = new CommonResp<>();
        eBookService.save(eBookSaveReq);
        return resp;
    }


4、校驗不通過時,前端彈出錯誤提示

在前段頁面新增彈窗提示元件,示例程式碼如下:

import {message} from 'ant-design-vue';

      if (data.success) {
          modalVisible.value = false;
          modalLoading.value = false;
          //重新載入列表
          handleQuery({
            page: 1,
            size: pagination.value.pageSize,
          });
        }else {
          message.error(data.message);
        }
      })

四、編譯執行,測試結果

1、查詢介面前端驗證結果

2、儲存介面前端驗證結果

五、寫在最後

就一個功能來說的話,肯定是要有對應的校驗規則,才是嚴謹的,從某種角度來說,也體現了程式的健壯性。

當然,也可能有的同學會說,寫校驗規則多麻煩,基本功能能用不就行了嗎。

其實不然,因為使用者的行為,是最不可控的,不能保證每個使用者(使用者),都能按照開發者預設的想法去使用軟體。

如果那樣話,那所有程式,都會沒有bug了,你說是不是?

相關文章