首頁>技術>

springboot天生支援使用hibernate validation對引數的優雅校驗,如果不使用它,只能對引數挨個進行如下方式的手工校驗,不僅難看,使用起來還很不方便:

if(StringUtils.isEmpty(userName)){	throw new RuntimeException("使用者名稱不能為空");}

下面將介紹hibernate validation的基本使用方法。

一、引入依賴

這裡在springboot 2.4.1中進行實驗,引入以下依賴:

在Controller上加上 @Validated 註解在需要校驗的欄位前面加上 @NotNull(message = "使用者id不能為空") 註解定義全域性異常處理類,定製化返回結果
@RestControllerAdvice   @Slf4j   public class ValidationAdvice {              @ExceptionHandler(Exception.class)       @ResponseBody       public WrapperResult handler(Exception e) {           //獲取異常資訊,獲取異常堆疊的完整異常資訊           StringWriter sw = new StringWriter();           PrintWriter pw = new PrintWriter(sw);           e.printStackTrace(pw);           //日誌輸出異常詳情           log.error(sw.toString());           return WrapperResult.faild("服務異常,請稍後再試");       }          @ExceptionHandler(ConstraintViolationException.class)       @ResponseBody       public WrapperResult handler(ConstraintViolationException e) {           StringBuffer errorMsg = new StringBuffer();           Set<ConstraintViolation<?>> violations = e.getConstraintViolations();           violations.forEach(x -> errorMsg.append(x.getMessage()).append(";"));           return WrapperResult.faild(errorMsg.toString());       }   }

Controller層程式碼如下所示:

@RestController   @Slf4j   @RequestMapping("/user")   @Validated   public class UserController {          /**        * 根據id查詢使用者資訊        *        * @param id        * @return        */       @GetMapping       public WrapperResult<UserModel> findUser(@NotNull(message = "使用者id不能為空")                                                @RequestParam(value = "id")                                                String id) {           return WrapperResult.success(new UserModel());       }   }

如果發起請求 127.0.0.1:8080/user?id= 則會返回結果

{       "status": 1,       "data": "使用者id不能為空;",       "msg": "FAIL",       "success": false   }
三、物件內參數校驗

上面是GET請求,下面介紹POST請求,請求物件內的引數校驗。

1.Controller類上加上@Validated註解
@RestController@Slf4j@RequestMapping("/user")**@Validated**public class UserController {}
2.在POST請求方法引數前面加上 @Validated 註解
@PostMapping("/mobile-regist")    public WrapperResult<Boolean> mobileRegit(@Validated @RequestBody UserModel userModel) {        return WrapperResult.success(true);    }
3.在上面介紹的 ValidationAdvice 類中加上物件引數校驗異常捕獲
//處理校驗異常,對於物件型別的資料的校驗異常    @ExceptionHandler(MethodArgumentNotValidException.class)    @ResponseBody    public WrapperResult handler(MethodArgumentNotValidException e) {        StringBuffer sb = new StringBuffer();        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();        allErrors.forEach(msg -> sb.append(msg.getDefaultMessage()).append(";"));        return WrapperResult.faild(sb.toString());    }

UserModel類的定義如下:

@Data@Builder@NoArgsConstructor@AllArgsConstructor@Accessors(chain = true)public class UserModel {    @NotEmpty(message = "姓名不能為空")    private String name;    @NotEmpty(message = "手機號不能為空")//    @Mobile(message = "手機號格式不正確")    private String mobile;    @NotEmpty(message = "電子郵箱不能為空")	@Email(message = "電子郵箱格式不正確")    private String email;    private String password;    private String address;    @NotNull(message = "年齡不能為空")    @Min(value = 12, message = "允許註冊年齡最小為12歲")    @Max(value = 24, message = "允許年齡最大為24歲")    private Integer age;    @NotEmpty(message = "聯絡人不允許為空")    @Size(min = 1, max = 3, message = "聯絡人長度只允許1到3之間")    private List<String> contacts;}

如果POST請求如下所示

{    "name":"",    "mobile":"12666666666",    "email":"",    "password":"",    "address":"",    "age": null,    "contacts":[    ]}

則會返回如下定製化返回結果:

{    "status": 1,    "data": "電子郵箱不能為空;聯絡人長度只允許1到3之間;年齡不能為空;聯絡人不允許為空;姓名不能為空;手機號格式不正確;",    "msg": "FAIL",    "success": false}
四、自定義校驗器

像是@NotNull、@Email等註解都是hibernate validation 內建的註解,我們想開發像是@Email註解一樣功能的註解,如何做呢,比如@Mobile,它的使用方法將和@Email一模一樣。

首先,先定義一個工具類存放 ValidationUtil 兩個常量值

public class ValidationUtil {    //手機號校驗正則    public static final String MOBILE_REGX = "^[1][3-9][0-9]{9}$";    public static final String MOBILE_MSG = "手機號格式錯誤";}
1.定義註解 Mobile

具體程式碼可以參考@Email的實現,直接將Email名字改成Mobile即可,如下所示:

@Documented@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})@Retention(RUNTIME)public @interface Mobile {    String message() default ValidationUtil.MOBILE_MSG;    Class<?>[] groups() default {};    Class<? extends Payload>[] payload() default {};    String regexp() default ValidationUtil.MOBILE_REGX;    Pattern.Flag[] flags() default {};    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})    @Retention(RUNTIME)    @Documented    public @interface List {        Mobile[] value();    }}
2.定義 MobileValidator 實現對引數的校驗邏輯
public class MobileValidator implements ConstraintValidator<Mobile, String> {    private String regexp;    @Override    public void initialize(Mobile constraintAnnotation) {        //獲取校驗的手機號的格式        this.regexp = constraintAnnotation.regexp();    }    @Override    public boolean isValid(String value, ConstraintValidatorContext context) {        if (!StringUtils.hasText(value)) {            return true;        }        return value.matches(regexp);    }}
3.使用方法和 @Email 一模一樣

不贅述

五、分組校驗

假設一個使用者註冊的場景,使用者註冊有三種方式

使用者名稱+圖形驗證碼註冊郵箱+郵箱驗證碼註冊手機號+簡訊驗證碼註冊

使用者註冊的時候除了方式不一樣,其他使用者資訊基本相同,後端開了三個介面對應著著三種註冊方式,請求體中我們使用一個Model封裝了以上所有資訊,包含著使用者名稱、郵箱、手機號等資訊,這時候不同的介面被呼叫,model中需要校驗的引數就不一樣了:

使用者名稱註冊的時候郵箱地址和手機號可以為空,但是使用者名稱不能為空;透過郵箱註冊的時候,郵箱地址不能為空,但是使用者名稱和手機號可以為空;......

分組校驗專門應對這種情況。

1.首先定義三個介面,表示三種組類別
public interface ValidEmail {}public interface ValidMobile {}public interface ValidUserName {}
2.在UserModel實體類上指名組類別
@Data@Builder@NoArgsConstructor@AllArgsConstructor@Accessors(chain = true)public class UserModel {    @NotEmpty(message = "姓名不能為空", groups = {ValidUserName.class})    @UserName(groups = {ValidUserName.class})    private String name;    @NotEmpty(message = "手機號不能為空", groups = {ValidMobile.class})    @Mobile(groups = {ValidMobile.class})    private String mobile;    @NotEmpty(message = "電子郵箱不能為空", groups = {ValidEmail.class})    @Email(message = "電子郵箱格式不正確", groups = {ValidEmail.class})    private String email;    private String password;    private String address;    @NotNull(message = "年齡不能為空")    @Min(value = 12, message = "允許註冊年齡最小為12歲", groups = {ValidEmail.class,ValidMobile.class,ValidUserName.class})    @Max(value = 24, message = "允許年齡最大為24歲",groups = {ValidEmail.class,ValidMobile.class,ValidUserName.class})    private Integer age;    @NotEmpty(message = "聯絡人不允許為空",groups = {ValidEmail.class,ValidMobile.class,ValidUserName.class})    @Size(min = 1, max = 3, message = "聯絡人長度只允許1到3之間",groups = {ValidEmail.class,ValidMobile.class,ValidUserName.class})    private List<String> contacts;}
3.Controller方法上指名驗證組別
/**     * 手機號註冊     *     * @param userModel     * @return     */    @PostMapping("/mobile-regist")    public WrapperResult<Boolean> mobileRegit(@Validated(ValidMobile.class) @RequestBody UserModel userModel) {        return WrapperResult.success(true);    }

這時候進行如下請求:

POST http://127.0.0.1:8080/user/mobile-regist

{    "mobile":"12666666666",    "password":"",    "address":"",    "age": null,    "contacts":[    ]}

則會返回結果:

{    "status": 1,    "data": "聯絡人長度只允許1到3之間;手機號格式錯誤;聯絡人不允許為空;",    "msg": "FAIL",    "success": false}

該請求中並沒有傳遞email和username欄位,而且結果中也未校驗出這兩個欄位,符合預期結果。

六、手動校驗

此處的手動校驗並非是使用if/else進行簡單的手動校驗,而是使用Validation自帶的校驗工具對使用了@NotNull等註解的實體物件進行屬性校驗。

首先先獲取Valiation物件:

private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
1. 全屬性校驗
/**  * 驗證某個物件所有欄位  *  * @param obj  * @param <T>  * @return  */public static <T> ValidationResult validateEntity(T obj) {    ValidationResult result = new ValidationResult();    Set<ConstraintViolation<T>> set = validator.validate(obj, Default.class);    if (!CollectionUtils.isEmpty(set)) {        result.setHasErrors(true);        Map<String, String> errorMsg = new HashMap<>();        for (ConstraintViolation<T> cv : set) {            errorMsg.put(cv.getPropertyPath().toString(), cv.getMessage());        }        result.setErrorMsg(errorMsg);    }    return result;}
2.某個欄位的單獨校驗
/*** 驗證某個物件某個欄位** @param obj* @param propertyName* @param <T>* @return*/public static <T> ValidationResult validateProperty(T obj, String propertyName) {    ValidationResult result = new ValidationResult();    Set<ConstraintViolation<T>> set = validator.validateProperty(obj, propertyName, Default.class);    if (!CollectionUtils.isEmpty(set)) {        result.setHasErrors(true);        Map<String, String> errorMsg = new HashMap<>();        for (ConstraintViolation<T> cv : set) {            errorMsg.put(propertyName, cv.getMessage());        }        result.setErrorMsg(errorMsg);    }    return result;}

ValidationResult的定義如下:

@Data@Builder@NoArgsConstructor@AllArgsConstructor@Accessors(chain = true)public class ValidationResult {    private Boolean hasErrors;    private Map<String, String> errorMsg;}
七、檔案上傳校驗1.tomcat容器下檔案上傳校驗

在springboot+tomcat架構下的檔案上傳校驗,假如已經有了如下的配置:

spring:    servlet:    multipart:      max-file-size: 1MB      max-request-size: 1MB

這表示只允許上傳小於1MB大小的檔案,如果不指定異常處理器,預設會報前端400,在 ValidationAdvice 類中新增如下程式碼可以自定義返回結果:

//檔案上傳檔案大小超出限制    @ExceptionHandler(MaxUploadSizeExceededException.class)    @ResponseBody    public WrapperResult<Map<String,Object>> fileSizeException(MaxUploadSizeExceededException exception) {        log.error("檔案太大,上傳失敗",exception);        return WrapperResult.faild("只允許上傳不大於"+exception.getMaxUploadSize()+"的檔案");    }
2.其它容器

在Jetty容器中1中的方法可能會失效,未驗證;在undertow容器中是一定會失效,已經驗證。undertow容器畢竟和spring-boot沒有完全打磨好,不建議現階段使用。

八、附錄1.所有校驗規則註解說明2.校驗規則註解例子
// 空和非空檢查: @Null、@NotNull、@NotBlank、@NotEmpty@Null(message = "驗證是否為 null")private Integer isNull;@NotNull(message = "驗證是否不為 null, 但無法查檢長度為0的空字串")private Integer id;@NotBlank(message = "檢查字串是不是為 null,以及去除空格後長度是否大於0")private String name;          @NotEmpty(message = "檢查是否為 NULL 或者是 EMPTY")private List<String> stringList;          // Boolean值檢查: @AssertTrue、@AssertFalse@AssertTrue(message = " 驗證 Boolean引數是否為 true")private Boolean isTrue;          @AssertFalse(message = "驗證 Boolean 引數是否為 false ")private Boolean isFalse;          // 長度檢查: @Size、@Length@Size(min = 1, max = 2, message = "驗證(Array,Collection,Map,String)長度是否在給定範圍內")private List<Integer> integerList;      @Length(min = 8, max = 30, message = "驗證字串長度是否在給定範圍內")private String address;      // 日期檢查: @Future、@FutureOrPresent、@Past、@PastOrPresent@Future(message = "驗證日期是否在當前時間之後")private Date futureDate;      @FutureOrPresent(message = "驗證日期是否為當前時間或之後")private Date futureOrPresentDate;      @Past(message = "驗證日期是否在當前時間之前")private Date pastDate;      @PastOrPresent(message = "驗證日期是否為當前時間或之前")private Date pastOrPresentDate;      // 其它檢查: @Email、@CreditCardNumber、@URL、@Pattern、@ScriptAssert、@UniqueElements@Email(message = "校驗是否為正確的郵箱格式")private String email;      @CreditCardNumber(message = "校驗是否為正確的信用卡號")private String creditCardNumber;      @URL(protocol = "http", host = "127.0.0.1", port = 8080, message= "校驗是否為正確的URL地址")private String url;      @Pattern(regexp = "^1[3|4|5|7|8][0-9]{9}$", message = "正則校驗是否為正確的手機號")private String phone;         // 對關聯物件元素進行遞迴校驗檢查@Valid@UniqueElements(message = "校驗集合中的元素是否唯一")private List<CalendarEvent> calendarEvent;@Data@ScriptAssert(lang = "javascript", script ="_this.startDate.before(_this.endDate)",message = "透過指令碼表示式校驗引數")private class CalendarEvent {  private Date startDate;  private Date endDate;}// 數值檢查: @Min、@Max、@Range、@DecimalMin、@DecimalMax、@Digits@Min(value = 0, message = "驗證數值是否大於等於指定值")@Max(value = 100, message = "驗證數值是否小於等於指定值")@Range(min = 0, max = 100, message = "驗證數值是否在指定值區間範圍內")private Integer score;@DecimalMin(value = "10.01", inclusive = false, message = "驗證數值是否大於等於指定值")@DecimalMax(value = "199.99", message = "驗證數值是否小於等於指定值")@Digits(integer = 3, fraction = 2, message = "限制整數位最多為3,小數位最多為2")private BigDecimal money;

來源:https://www.tuicool.com/articles/jQbyyeM

23
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 【20201022】程式設計基礎第一課,框架