关于参数校验,hibernate的validator 的校验

1.注解式校验

1.1 常见校验注解

定义的校验类型

@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@URL(protocol=,host=, port=,regexp=, flags=) ip地址校验

Booelan检查

@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false

长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) Validates that the annotated string is between min and max included.

日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则

数值检查,建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为""时无法转换为int,但可以转换为Stirng为"",Integer为null
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=) Checks whether the annotated value lies between (inclusive) the specified minimum and maximum.

1.2 实现简单的注解式校验栗子

1.2.1 请求实体

@Data
public class CommonReqVO<T> {
    /**
     * 互感器主键
     */
    @NotNull(message = "业务类型不能为空")
    private String type;
    /**
     * 请求数据
     */
    @Valid
    @NotNull(message = "数据不能为空")
    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 = "时间格式错误,格式应为'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,"请求参数不能为空");
        if ("01".equals(userVO.getType())){
            ValidateUtil.validateParams(userVO,"请求参数不能为空",UserVO.S1.class);
        }else if("02".equals(userVO.getType())){
            ValidateUtil.validateParams(userVO,"请求参数不能为空",UserVO.S2.class);
        }else {
            throw new CustomException("请求参数type类型不匹配!");
        }
        return "t3";
    }

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

1.2.3 统一异常处理类及相关类

@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("【系统异常】{}", e);
        return ResultInfoUtil.error(0, e.getMessage());
    }


    /**
     * 处理validation框架中的{@link MethodArgumentNotValidException}异常
     * <p>该异常一般出在用{@code @RequestBody}注解标记的参数校验,在对象中的参数有问题是抛出</p>
     *
     * @param e {@link MethodArgumentNotValidException}
     * @return {@link ResultInfo}
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResultInfo doNoValidExceptionHandler(MethodArgumentNotValidException e) {
        log.info("捕捉到参数校验异常: [{}]", e);
        return validationExceptionHandler(e.getBindingResult(), e.getMessage());
    }

    /**
     * 处理validation框架中的{@link ConstraintDeclarationException}异常
     * <p>该异常一般出在用{@code @RequestParam}注解标记的参数校验</p>
     *
     * @param e {@link ConstraintDeclarationException}
     * @return {@link ResultInfo}
     */
    @ExceptionHandler(ConstraintDeclarationException.class)
    public ResultInfo doNoValidExceptionHandler(ConstraintDeclarationException e) {
        String errorMsg = e.getMessage();
        log.info("捕捉到参数校验异常: [{}]", errorMsg, e);
        return ResultInfoUtil.error(0, e.getMessage());
    }

    /**
     * 处理validation框架中的{@link BindException}异常
     * <p>该异常一般出在用{@code @RequestBody}注解标记的参数校验,在对象嵌套类型参数有问题时抛出</p>
     *
     * @param e {@link BindException}
     * @return {@link ResultInfo}
     */
    @ExceptionHandler(BindException.class)
    public ResultInfo doNoValidExceptionHandler(BindException e) {
        log.info("捕捉到参数校验异常: [{}]", e);
        return validationExceptionHandler(e.getBindingResult(), e.getMessage());
    }


    /**
     * 处理validation框架的异常
     *
     * @param bindingResult {@link BindingResult} 异常绑定的对象信息
     * @param message       异常信息
     * @return {@link ResultInfo}
     */
    private ResultInfo validationExceptionHandler(BindingResult bindingResult, String message) {
        //默认捕获第一个不符合校验规则的错误信息
        //错误字段对象
        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("捕捉到参数校验异常详情: {}", 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。所以会校验全部参数

1.2.4 请求及响应

请求

{}

响应

{
    "code": 0,
    "msg": "[data-数据不能为空, type-业务类型不能为空, startDate-开始时间不能为空!]",
    "data": null
}

2.编程式校验

2.1 校验相关类

@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;
    }

    /**
     * 验证请求参数是否为空
     *
     * @param o   参数对象
     * @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 配置

#校验相关配置
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 为空则不进行格式验证,为空验证可以使用 @NotBlank @NotNull @NotEmpty 等注解来进行控制,职责分离
            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 = "时间格式错误,格式应为'yyyy-MM-dd'")
private String startDate;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容