關(guān)于參數(shù)校驗,hibernate的validator 的校驗

1.注解式校驗

1.1 常見校驗注解

定義的校驗類型

@Null 驗證對象是否為null
@NotNull 驗證對象是否不為null, 無法查檢長度為0的字符串
@NotBlank 檢查約束字符串是不是Null還有被Trim的長度是否大于0,只對字符串,且會去掉前后空格.
@NotEmpty 檢查約束元素是否為NULL或者是EMPTY.
@CreditCardNumber信用卡驗證
@Email 驗證是否是郵件地址,如果為null,不進(jìn)行驗證,算通過驗證。
@URL(protocol=,host=, port=,regexp=, flags=) ip地址校驗

Booelan檢查

@AssertTrue 驗證 Boolean 對象是否為 true
@AssertFalse 驗證 Boolean 對象是否為 false

長度檢查
@Size(min=, max=) 驗證對象(Array,Collection,Map,String)長度是否在給定的范圍之內(nèi)
@Length(min=, max=) Validates that the annotated string is between min and max included.

日期檢查
@Past 驗證 Date 和 Calendar 對象是否在當(dāng)前時間之前
@Future 驗證 Date 和 Calendar 對象是否在當(dāng)前時間之后
@Pattern 驗證 String 對象是否符合正則表達(dá)式的規(guī)則

數(shù)值檢查,建議使用在Stirng,Integer類型,不建議使用在int類型上,因為表單值為""時無法轉(zhuǎn)換為int,但可以轉(zhuǎn)換為Stirng為"",Integer為null
@Min 驗證 Number 和 String 對象是否大等于指定的值
@Max 驗證 Number 和 String 對象是否小等于指定的值
@DecimalMax 被標(biāo)注的值必須不大于約束中指定的最大值. 這個約束的參數(shù)是一個通過BigDecimal定義的最大值的字符串表示.小數(shù)存在精度
@DecimalMin 被標(biāo)注的值必須不小于約束中指定的最小值. 這個約束的參數(shù)是一個通過BigDecimal定義的最小值的字符串表示.小數(shù)存在精度
@Digits 驗證 Number 和 String 的構(gòu)成是否合法
@Digits(integer=,fraction=) 驗證字符串是否是符合指定格式的數(shù)字,interger指定整數(shù)精度,fraction指定小數(shù)精度。
@Range(min=, max=) Checks whether the annotated value lies between (inclusive) the specified minimum and maximum.

1.2 實現(xiàn)簡單的注解式校驗栗子

1.2.1 請求實體

@Data
public class CommonReqVO<T> {
    /**
     * 互感器主鍵
     */
    @NotNull(message = "業(yè)務(wù)類型不能為空")
    private String type;
    /**
     * 請求數(shù)據(jù)
     */
    @Valid
    @NotNull(message = "數(shù)據(jù)不能為空")
    private T data;

    @NotNull(message = "開始時間不能為空!")
    @Pattern(regexp = "^((([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8]))))|((([0-9]{2})(0[4]|[2468][048]|[13579][26])|((0[48]|[2468][048]|[3579][26])00))-02-29))\\s+([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$",message = "日期格式不正確")
//    @DateTime(format = "yyyy-MM-dd", message = "時間格式錯誤,格式應(yīng)為'yyyy-MM-dd'")
    private String startDate;
}

@Data
public class UserVO {

    @NotBlank(message = "name不能為空",groups = S1.class)
    private String name;
    @Range(min = 1,max = 120,message = "年齡必須在1-120之間",groups = S1.class)
    private Integer age;
    @NotBlank(message = "address不能為空",groups = S2.class)
    private String address;
    @NotBlank(message = "phone不能為空",groups = S2.class)
    private String phone;


    public interface S1{}
    public interface S2{}
}

1.2.2 controller

@RestController
@RequestMapping("test")
public class TestController {
    
    @PostMapping("t3")
    public String test3(@RequestBody CommonReqVO<UserVO> userVO){
        ValidateUtil.validateParams(userVO,"請求參數(shù)不能為空");
        if ("01".equals(userVO.getType())){
            ValidateUtil.validateParams(userVO,"請求參數(shù)不能為空",UserVO.S1.class);
        }else if("02".equals(userVO.getType())){
            ValidateUtil.validateParams(userVO,"請求參數(shù)不能為空",UserVO.S2.class);
        }else {
            throw new CustomException("請求參數(shù)type類型不匹配!");
        }
        return "t3";
    }

    @PostMapping("t4")
    public String test4(@RequestBody @Validated CommonReqVO<UserVO> userVO){
        return "4";
    }
}

1.2.3 統(tǒng)一異常處理類及相關(guān)類

@RestControllerAdvice
@Slf4j
public class ExceptionHandle {

    @Value("${validator.showField:false}")
    private Boolean showFile;

    @ExceptionHandler(CustomException.class)
    public ResultInfo handleCustomException(CustomException e){
        log.error("【用戶自定義異?!縶}", e);
        return ResultInfoUtil.error(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public ResultInfo handleReqException(HttpMediaTypeNotSupportedException e){
        log.error("【請求類型不支持異?!縶}", e);
        if (e.getMessage().contains("not supported")){
            return ResultInfoUtil.error(0, "請求Content-Type不匹配");
        }
        return ResultInfoUtil.error(0, e.getMessage());
    }
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResultInfo handleReqBodyException(HttpMessageNotReadableException e){
        log.error("【請求體異?!縶}", e);
        if (e.getMessage().contains("Required request body is missing")){
            return ResultInfoUtil.error(0, "請求體不能為空");
        }
        return ResultInfoUtil.error(0, e.getMessage());
    }


    @ExceptionHandler(Exception.class)
    public ResultInfo handleOtherException(Exception e){
        log.error("【系統(tǒng)異常】{}", e);
        return ResultInfoUtil.error(0, e.getMessage());
    }


    /**
     * 處理validation框架中的{@link MethodArgumentNotValidException}異常
     * <p>該異常一般出在用{@code @RequestBody}注解標(biāo)記的參數(shù)校驗,在對象中的參數(shù)有問題是拋出</p>
     *
     * @param e {@link MethodArgumentNotValidException}
     * @return {@link ResultInfo}
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResultInfo doNoValidExceptionHandler(MethodArgumentNotValidException e) {
        log.info("捕捉到參數(shù)校驗異常: [{}]", e);
        return validationExceptionHandler(e.getBindingResult(), e.getMessage());
    }

    /**
     * 處理validation框架中的{@link ConstraintDeclarationException}異常
     * <p>該異常一般出在用{@code @RequestParam}注解標(biāo)記的參數(shù)校驗</p>
     *
     * @param e {@link ConstraintDeclarationException}
     * @return {@link ResultInfo}
     */
    @ExceptionHandler(ConstraintDeclarationException.class)
    public ResultInfo doNoValidExceptionHandler(ConstraintDeclarationException e) {
        String errorMsg = e.getMessage();
        log.info("捕捉到參數(shù)校驗異常: [{}]", errorMsg, e);
        return ResultInfoUtil.error(0, e.getMessage());
    }

    /**
     * 處理validation框架中的{@link BindException}異常
     * <p>該異常一般出在用{@code @RequestBody}注解標(biāo)記的參數(shù)校驗,在對象嵌套類型參數(shù)有問題時拋出</p>
     *
     * @param e {@link BindException}
     * @return {@link ResultInfo}
     */
    @ExceptionHandler(BindException.class)
    public ResultInfo doNoValidExceptionHandler(BindException e) {
        log.info("捕捉到參數(shù)校驗異常: [{}]", e);
        return validationExceptionHandler(e.getBindingResult(), e.getMessage());
    }


    /**
     * 處理validation框架的異常
     *
     * @param bindingResult {@link BindingResult} 異常綁定的對象信息
     * @param message       異常信息
     * @return {@link ResultInfo}
     */
    private ResultInfo validationExceptionHandler(BindingResult bindingResult, String message) {
        //默認(rèn)捕獲第一個不符合校驗規(guī)則的錯誤信息
        //錯誤字段對象
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        List<String> errorMessages = new ArrayList<>(fieldErrors.size());
        if (showFile){
            fieldErrors.stream().forEach(e->{
                errorMessages.add(e.getField()+"-"+e.getDefaultMessage());
            });
        }else {
            fieldErrors.stream().forEach(e->{
                errorMessages.add(e.getDefaultMessage());
            });
        }
        log.info("捕捉到參數(shù)校驗異常詳情: {}", errorMessages);
        return ResultInfoUtil.error(0, errorMessages.toString());
    }

}

@Data
public class ResultInfo<T> {
    /**
     *返回碼
     */
    private Integer code;
    /**
     *返回提示信息
     */
    private String msg;
    /**
     *返回具體對象
     */
    private T data;
}

public class ResultInfoUtil {
    public static ResultInfo success(Object object) {
        ResultInfo result = new ResultInfo();
        ResultInfoEnum successEnum = ResultInfoEnum.SUCCESS;
        result.setCode(successEnum.getCode());
        result.setMsg(successEnum.getMsg());
        result.setData(object);
        return result;
    }

    public static ResultInfo success() {
        return success(null);
    }

    public static ResultInfo error(Integer code, String msg) {
        ResultInfo result = new ResultInfo();
        result.setCode(code);
        result.setMsg(msg);
        return result;
    }
}

@Data
public class CustomException extends RuntimeException {

    private Integer code;

    public CustomException(ResultInfoEnum resultInfoEnum) {
        super(resultInfoEnum.getMsg());
        this.code = resultInfoEnum.getCode();
    }

    public CustomException(String msg) {
        super(msg);
        this.code = ResultInfoEnum.FAILED.getCode();
    }

}

注意上面配置了 failFast 為false。所以會校驗全部參數(shù)

1.2.4 請求及響應(yīng)

請求

{}

響應(yīng)

{
    "code": 0,
    "msg": "[data-數(shù)據(jù)不能為空, type-業(yè)務(wù)類型不能為空, startDate-開始時間不能為空!]",
    "data": null
}

2.編程式校驗

2.1 校驗相關(guān)類

@Configuration
public class ValidateUtil<T> {


    private static Validator validator;
    /**
     * 是否啟用快速失敗
     */
    private static Boolean failFast;
    /**
     * 是否顯示錯誤字段
     */
    private static Boolean showField;

    public static void setFailFast(Boolean failFast) {
        ValidateUtil.failFast = failFast;
    }


    public static void setShowField(Boolean showField) {
        ValidateUtil.showField = showField;
    }

    /**
     * @param obj 校驗的對象
     * @param <T>
     * @return
     */
    public static <T> Set<ConstraintViolation<T>> validate(T obj, Class... groupClasses) {
        Set<ConstraintViolation<T>> constraintViolations;
        if (groupClasses != null && groupClasses.length > 0) {
            constraintViolations = validator.validate(obj, groupClasses);
        } else {
            constraintViolations = validator.validate(obj);
        }
        return constraintViolations;
    }

    /**
     * 驗證請求參數(shù)是否為空
     *
     * @param o   參數(shù)對象
     * @param msg 報錯信息
     */
    public static void checkIsNotNull(Object o, String msg) {
        if (o == null) {
            throw new CustomException(msg);
        }
    }

    public static <T> void validateParams(T t, String msg, Class... groupClasses) {
        checkIsNotNull(t, msg);
        Set<ConstraintViolation<T>> constraintViolationSet = validate(t, groupClasses);
        List<String> resultString = new ArrayList<>(constraintViolationSet.size());
        if (!constraintViolationSet.isEmpty()) {
            if (showField){
                constraintViolationSet.stream().forEach(c -> {
                    resultString.add(c.getPropertyPath().toString() + "-" + c.getMessage());
                });
            }else {
                constraintViolationSet.stream().forEach(c -> {
                    resultString.add(c.getMessage());
                });
            }
            throw new CustomException(resultString.toString());
        }
    }

    /**
     * 注入validator
     *
     * @param validatorBean
     */
    public static void setValidator(Validator validatorBean) {
        validator = validatorBean;
    }


}
@Configuration
public class ValidatorConfiguration {

    @Value("${validator.failFast:true}")
    private Boolean failFast;

    @Value("${validator.showField:false}")
    private Boolean showField;

    @Autowired
    private ServletContext servletContext;

    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                // 快速失敗
                .failFast(failFast)
                .buildValidatorFactory();
        return validatorFactory.getValidator();
    }

    /**
     * 給ValidateUtil初始化
     */
    @PostConstruct
    public void initValidateUtil() {
        ApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
        Validator validator = (Validator) applicationContext.getBean("validator");
        ValidateUtil.setValidator(validator);
        ValidateUtil.setFailFast(failFast);
        ValidateUtil.setShowField(showField);
    }
}

2.2 配置

#校驗相關(guān)配置
validator:
  #快速失敗
  failFast: true
  #錯誤提示是否顯示錯誤字段
  showField: false

3.自定義校驗注解

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = DateTime.DateTimeValidator.class)
public @interface DateTime {

    String message() default "格式錯誤";

    String format() default "yyyyMM";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};


    class DateTimeValidator implements ConstraintValidator<DateTime, String> {

        private DateTime dateTime;

        @Override
        public void initialize(DateTime dateTime) {
            this.dateTime = dateTime;
        }

        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            // 如果 value 為空則不進(jìn)行格式驗證,為空驗證可以使用 @NotBlank @NotNull @NotEmpty 等注解來進(jìn)行控制,職責(zé)分離
            if (value == null) {
                return true;
            }
            String format = dateTime.format();
            if (value.length() != format.length()) {
                return false;
            }
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
            try {
                simpleDateFormat.parse(value);
            } catch (Exception e) {
                return false;
            }
            return true;
        }
    }
}

3.1 使用方法

@DateTime(format = "yyyy-MM-dd", message = "時間格式錯誤,格式應(yīng)為'yyyy-MM-dd'")
private String startDate;
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容