zoukankan      html  css  js  c++  java
  • Validation(4)-临时

    使用Hibernate-Validator优雅的校验参数

     版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/java_collect/article/details/85534054

    何为Hibernate-Validator

           在RESTful 的接口服务中,会有各种各样的入参,我们不可能完全不做任何校验就直接进入到业务处理的环节,通常我们会有一个基础的数据验证的机制,待这些验证过程完毕,结果无误后,参数才会进入到正式的业务处理中。而数据验证又分为两种,一种是无业务关联的规则性验证,一种是根据现有数据进行的联动性数据验证(简单来说,参数的合理性,需要查数据库)。而Hibernate-Validator则适合做无业务关联的规则性验证,而这类验证的代码大多是可复用的。

           如果项目的框架是spring boot的话,在spring-boot-starter-web 中已经包含了Hibernate-validator的依赖。Hibernate-Validator的主要使用的方式就是注解的形式,并且是“零配置”的,无需配置也可以使用。下面展示一个最简单的案例。

    1. Hibernate-Validator 最基本的使用

    1. 添加一个普通的接口信息,参数是@RequestParam类型的,传入的参数是id,且id不能小于10。
        @RestController
        @RequestMapping("/example")
        @Validated
        public class ExampleController {
        
            /**
             *  用于测试
             * @param id id数不能小于10 @RequestParam类型的参数需要在Controller上增加@Validated
             * @return
             */
            @RequestMapping(value = "/info",method = RequestMethod.GET)
            public String test(@Min(value = 10, message = "id最小只能是10") @RequestParam("id")
                                           Integer id){
                return "恭喜你拿到参数了";
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    1. 在全局异常拦截中添加验证异常的处理
    /**
     * 统一异常处理类
     */
    @RestControllerAdvice
    public class GlobalExceptionHandler {
    
        @ExceptionHandler(MethodArgumentNotValidException.class)
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        public BaseResponse<String> handleMethodArgumentNotValidException(MethodArgumentNotValidException exception) {
            StringBuilder errorInfo = new StringBuilder();
            BindingResult bindingResult = exception.getBindingResult();
            for(int i = 0; i < bindingResult.getFieldErrors().size(); i++){
                if(i > 0){
                    errorInfo.append(",");
                }
                FieldError fieldError = bindingResult.getFieldErrors().get(i);
                errorInfo.append(fieldError.getField()).append(" :").append(fieldError.getDefaultMessage());
            }
    
            //返回BaseResponse
            BaseResponse<String> response = new BaseResponse<>();
            response.setMsg(errorInfo.toString());
            response.setCode(DefaultErrorCode.error);
            return response;
        }
    
    
        @ExceptionHandler(ConstraintViolationException.class)
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        public BaseResponse<String> handleConstraintViolationException(ConstraintViolationException exception) {
            StringBuilder errorInfo = new StringBuilder();
            String errorMessage ;
    
            Set<ConstraintViolation<?>> violations = exception.getConstraintViolations();
            for (ConstraintViolation<?> item : violations) {
                errorInfo.append(item.getMessage()).append(",");
            }
            errorMessage = errorInfo.toString().substring(0, errorInfo.toString().length()-1);
    
            BaseResponse<String> response = new BaseResponse<>();
            response.setMsg(errorMessage);
            response.setCode(DefaultErrorCode.error);
            return response;
        }
    
    
    
        @ExceptionHandler(Exception.class)
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        public BaseResponse<String> handleDefaultException(Exception exception) {
    
            BaseResponse<String> response = new BaseResponse<>();
            response.setMsg("其他错误");
            response.setCode(DefaultErrorCode.error);
            return response;
        }
    }
    

    验证复杂参数的案例

    1.添加一个vo的实体信息。

    @Data
    @ApiModel("用户添加对象")
    public class UserAddDto {
    
        @NotBlank(message = "用户id不能为空")
        @ApiModelProperty(name = "id", notes = "用户id",example = "2441634686")
        private String id;
    
    
        @NotBlank(message = "用户名称不能为空")
        @Size(min = 1,max = 20,message = "名称必须在1-20个字符内") //如果只有@Size,name可为空
        @ApiModelProperty(name = "name", notes = "用户名称",example = "张飞")
        private String name;
    
        @NotNull(message = "开始时间不能为空")
        @ApiModelProperty(name = "startDate", notes = "开始时间",example = "2018-08-08")
        private String startDate;
    
        @Size(max = 12,message = "不能超过12个")
        @ApiModelProperty(name = "list", notes = "包含的方向列表")
        @Valid //级联校验
        private List<DirectionDto> list;
    
        @EnumCheck(enumClass = SexEnum.class,message = "用户类型不合法")
        @ApiModelProperty(name = "id", notes = "用户类型",example = "2")
        private Integer type;
    
    }
    

    @Data

    @ApiModel("方向对象")
    public class DirectionDto {
    
        @NotBlank(message = "方向id不能为空")
        @ApiModelProperty(name = "list", notes = "方向唯一标识",example = "45587845465")
        private String id;
    
        @NotBlank(message = "开始日期不能为空")
        @ApiModelProperty(name = "startDate", notes = "开始时间",example = "2019-01-08")
        @Length(max = 8,message = "开始日期不合法")
        private String startDate;
    
        @ApiModelProperty(name = "endDate", notes = "结束时间",example = "2019-01-14")
        private String endDate;
    }
    1. 添加一个POST请求的接口。
        @PostMapping(value = "/add")
        @ApiOperation(value = "添加用户",notes = "添加用户,方向可以为空")
        public BaseResponse add(@Validated @RequestBody UserAddDto addDto) {
    
            BaseResponse<String> response = new BaseResponse<>();
            response.setMsg("添加成功");
            return response;
        }

    个人比较推荐使用全局异常拦截处理的方式去处理Hibernate-Validator的验证失败后的处理流程,这样能能减少Controller层或Services层的代码逻辑处理。虽然它也能在Controller中增加BindingResult的实例来获取数据,但是并不推荐。

    2. 常用的校验注解

    首先列举一下Hibernate-Validator所有的内置验证注解。

    • 常用的
    注解使用
    @NotNull 被注释的元素(任何元素)必须不为 null, 集合为空也是可以的。没啥实际意义
    @NotEmpty 用来校验字符串、集合、map、数组不能为null或空 
    (字符串传入空格也不可以)(集合需至少包含一个元素)
    @NotBlank 只用来校验字符串不能为null,空格也是被允许的 。校验字符串推荐使用@NotEmpty
    -  
    @Size(max=, min=) 指定的字符串、集合、map、数组长度必须在指定的max和min内 
    允许元素为null,字符串允许为空格
    @Length(min=,max=) 只用来校验字符串,长度必须在指定的max和min内 允许元素为null
    @Range(min=,max=) 用来校验数字或字符串的大小必须在指定的min和max内
    字符串会转成数字进行比较,如果不是数字校验不通过
    允许元素为null
    @Min() 校验数字(包括integer short long int 等)的最小值,不支持小数即double和float
    允许元素为null
    @Max() 校验数字(包括integer short long int 等)的最小值,不支持小数即double和float
    允许元素为null
    -  
    @Pattern() 正则表达式匹配,可用来校验年月日格式,是否包含特殊字符(regexp = "^[a-zA-Z0-9u4e00-u9fa5

    除了@Empty要求字符串不能全是空格,其他的字符串校验都是允许空格的。
    message是可以引用常量的,但是如@Size里max不允许引用对象常量,基本类型常量是可以的。
    注意大部分规则校验都是允许参数为null,即当不存在这个值时,就不进行校验了

    • 不常用的
    @Null 被注释的元素必须为 null
    @AssertTrue 被注释的元素必须为 true
    @AssertFalse 被注释的元素必须为 false
    @DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
    @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
    @Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
    @Past 被注释的元素必须是一个过去的日期
    @Future 被注释的元素必须是一个将来的日期
    @Email 被注释的元素必须是电子邮箱地址

    3. 自定义注解

           这些注解能适应我们绝大多数的验证场景,但是为了应对更多的可能性,我们需要增加注解功能配合Hibernate-Validator的其他的特性,来满足验证的需求。

           我们一定会用到这么一个业务场景,vo中的属性必须符合枚举类中的枚举。Hibernate-Validator中还没有关于枚举的验证规则,那么,我们则需要自定义一个枚举的验证注解。

        @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        @Constraint(validatedBy = EnumCheckValidator.class)
        public @interface EnumCheck {
            /**
             * 是否必填 默认是必填的
             * @return
             */
            boolean required() default true;
            /**
             * 验证失败的消息
             * @return
             */
            String message() default "枚举的验证失败";
            /**
             * 分组的内容
             * @return
             */
            Class<?>[] groups() default {};
        
            /**
             * 错误验证的级别
             * @return
             */
            Class<? extends Payload>[] payload() default {};
        
            /**
             * 枚举的Class
             * @return
             */
            Class<? extends Enum<?>> enumClass();
        
            /**
             * 枚举中的验证方法
             * @return
             */
            String enumMethod() default "validation";
        }
    

    注解的业务逻辑实现类

    
    public class EnumCheckValidator implements ConstraintValidator<EnumCheck,Object> {
        private EnumCheck enumCheck;
    
        @Override
        public void initialize(EnumCheck enumCheck) {
            this.enumCheck =enumCheck;
        }
    
        @Override
        public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
            // 注解表明为必选项 则不允许为空,否则可以为空
            if (value == null) {
                return !this.enumCheck.required();
            }
            //最终的返回结果
            Boolean result=Boolean.FALSE;
            // 获取 参数的数据类型
            Class<?> valueClass = value.getClass();
            try {
                Method method = this.enumCheck.enumClass().getMethod(this.enumCheck.enumMethod(), valueClass);
                result = (Boolean)method.invoke(null, value);
                if(result == null){
                    result = Boolean.FALSE;
                }
                //所有异常需要在开发测试阶段发现完毕
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }finally {
                return result;
            }
        }
    }
    

    编写枚举类

    public enum  Sex{
        MAN("男",1),WOMAN("女",2);
    
        private String label;
        private Integer value;
    
        public String getLabel() {
            return label;
        }
    
        public void setLabel(String label) {
            this.label = label;
        }
    
        public Integer getValue() {
            return value;
        }
    
        public void setValue(Integer value) {
            this.value = value;
        }
    
        Sex(String label, int value) {
            this.label = label;
            this.value = value;
        }
    
        /**
         * 判断值是否满足枚举中的value
         * @param value
         * @return
         */
        public static boolean validation(Integer value){
            for(Sex s:Sex.values()){
                if(Objects.equals(s.getValue(),value)){
                    return true;
                }
            }
            return false;
        }
    }
    

    使用方式

       @EnumCheck(message = "只能选男:1或女:2",enumClass = Sex.class)
       private Integer sex;
    

    我们甚至可以在自定义注解中做更加灵活的处理,甚至把与数据库的数据校验的也写成自定义注解,来进行数据验证的调用。

    4. 分组验证

           同一个校验规则,不可能适用于所有的业务场景,对此,对每一个业务场景去编写一个校验规则,又显得特别冗余。这里我们刚好可以用到Hibernate-Validator的分组功能。

    添加一个名为ValidGroupA的接口(接口内容可以是空的,所以就不列举代码)
    添加一个需要分组校验的字段

    @Data
    public class ExampleVo {
    
        @NotNull(message = "主键不允许为空",groups = ValidGroupA.class)
        private Integer id;
    
        @NotBlank(message = "用户名不能为空",groups = Default.class)
        private String userName;
        
        @Range(min = 18,max = 60,message = "只能填报年龄在18~60岁的",groups = Default.class)
        private String age;
    
        @EnumCheck(message = "只能选男:1或女:2",enumClass = Sex.class,groups = Default.class)
        private Integer sex;
    }

    改动接口的内容

     	@RequestMapping(value = "/info1",method = RequestMethod.POST)
        public String test1(@Validated({ValidGroupA.class,Default.class})  @RequestBody ExampleVo vo){
            return "恭喜你拿到参数了";
        }

           这里我们可以注意一下,校验的注解由 @Valid 改成了 @Validated。进行测试,保留ValidGroupA.class和去掉ValidGroupA.class的测试。

           使用分组能极大的复用需要验证的类信息。而不是按业务重复编写冗余的类。然而Hibernate-Validator还提供组序列的形式进行顺序式校验,此处就不重复列举了。我认为顺序化的校验,场景更多的是在业务处理类,例如联动的属性验证,值的有效性很大程度上不能从代码的枚举或常量类中来校验。

    部分引用及参考的文章
    https://www.cnblogs.com/mr-yang-localhost/p/7812038.html#_label3_3

  • 相关阅读:
    Go:获取命令行参数
    Go:文件操作
    Go:类型断言
    GO:interface
    Go:面向"对象"
    Go:工厂模式
    layui中流加载layui.flow
    js显示当前时间
    layui中的分页laypage
    layui中的多图上传
  • 原文地址:https://www.cnblogs.com/longxok/p/10912872.html
Copyright © 2011-2022 走看看