参数校验

112 阅读3分钟

引入依赖

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

快速失败配置

 @Configuration
 public class ValidatorConf {
     @Bean
     public Validator validator() {
         ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
                 .configure()
                 .failFast( true )
                 .buildValidatorFactory();
         return validatorFactory.getValidator();
     }
 }

单个参数校验:

 @RestController
 @Validated
 public class PingController {
     @GetMapping("/getUser")
     public String getUserStr(@NotNull(message = "name 不能为空") String name,
                              @Max(value = 99, message = "不能大于99岁") Integer age) {
         return "name: " + name + " ,age:" + age;
     }
 }

实体对象校验

 @PostMapping("/test2")
 public SysUser test2(@RequestBody @Valid SysUser sysUser) {
     SysUser sysUser1 = new SysUser();
     BeanUtils.copyProperties(sysUser, sysUser1);
     sysUser1.setCreateTime(LocalDateTime.now());
     return sysUser1;
 }
 public class SysUser {
 ​
     @Null
     @JsonAlias("user-Id")
     private Integer userId;
     @NotNull
     @Length(min = 10, max = 20, message = "长度最小10位,最长20位")
     private String userName;
     @Positive
     private Integer age;
     @PastOrPresent
     private LocalDateTime createTime;
 }

级联验证

 public class SysUser {
 ​
     @Null
     @JsonAlias("user-Id")
     private Integer userId;
 ​
     @NotNull
     @Length(min = 10, max = 20, message = "长度最小10位,最长20位")
     private String userName;
     @Positive
     private Integer age;
     @PastOrPresent
     private LocalDateTime createTime;
     @Valid //级联验证
     private Department department;
 }
 public class Department {
 ​
     
     private Integer id;
     @NotNull
     @Length(min = 3,max = 10,message = "长度大于3 小于10")
     private String name;
     @Valid //级联验证
     private List<SysUser> sysUsers;
 }

分组验证

自定义分组接口

 public interface Add extends Default {
 }
  
 public interface Update extends Default{
 }

将自定义分组接口应用到实体类上

 public class SysUser {
     @Null(groups = Add.class)
     @NotNull(groups = Update.class)
     private Integer userId;
 }

在Controller中进行校验

 @PostMapping("/add")
 public SysUser add(@RequestBody @Validated(Add.class) SysUser sysUser) {
     SysUser sysUser1 = new SysUser();
     BeanUtils.copyProperties(sysUser, sysUser1);
     sysUser1.setCreateTime(LocalDateTime.now());
     return sysUser1;
 }
 ​
 @PostMapping("/update")
 public SysUser update(@RequestBody @Validated(Update.class) SysUser sysUser) {
     SysUser sysUser1 = new SysUser();
     BeanUtils.copyProperties(sysUser, sysUser1);
     sysUser1.setCreateTime(LocalDateTime.now());
     return sysUser1;
 }

自定义注解验证

list中做分组校验

不使用分组

 @RestController
 @RequestMapping("/sysUser")
 @Validated
 public class SysUserController {
     @PostMapping("/batchProcess")
     public List<SysUser> batchProcess(@RequestBody @Valid List<SysUser> sysUser) {
         return sysUser;
     }
 }

使用分组

自定义注解

 /**
  * 支持 list 中的分组校验
  */
 @Target({FIELD,PARAMETER})
 @Retention(RUNTIME)
 @Documented
 @Constraint(validatedBy = {ValidListValidator.class})
 public @interface ValidList {
     /**
      * 要验证的分组
      */
     Class<?>[] groupings() default {Default.class};
 ​
     boolean quickFail() default false;
 ​
 ​
     String message() default "";
 ​
     Class<?>[] groups() default {};
 ​
     Class<? extends Payload>[] payload() default {};
 }
 ​

自定义校验器

 public class ValidListValidator implements ConstraintValidator<ValidList, List> {
 ​
     Class<?>[] groupings;
     boolean quickFail;
 ​
     @Override
     public void initialize(ValidList validList) {
         groupings = validList.groupings();
         quickFail = validList.quickFail();
     }
     @Override
     public boolean isValid(List list, ConstraintValidatorContext context) {
         Map<Integer, Set<ConstraintViolation<Object>>> errors = new HashMap<>();
         for (int i = 0; i < list.size(); i++) {
             Object object = list.get(i);
             Set<ConstraintViolation<Object>> error = ValidatorUtils.validator.validate(object, groupings);
             if (error.size()>0) {
                 errors.put(i, error);
                 if (quickFail){
                     throw new ListValidException(errors);
                 }
             }
         }
 ​
         if (errors.size()>0){
             throw new ListValidException(errors);
         }
 ​
         return true;
     }
 }
 ​

工具类

 @Component
 public class ValidatorUtils {
 ​
     public static Validator validator;
 ​
     @Autowired
     public void setValidator(Validator validator) {
         ValidatorUtils.validator = validator;
     }
 }

使用自定义注解校验List参数

 @RestController
 @RequestMapping("/sysUser")
 @Validated
 public class SysUserController {
     @PostMapping("/batchProcessAddGroup")
     public List<SysUser> batchProcessAddGroup(@RequestBody  @ValidList(groupings = {Add.class}) List<SysUser> sysUser) {
         return sysUser;
     }
 ​
     @PostMapping("/batchProcessUpdateGroup")
     public List<SysUser> batchProcessUpdateGroup(@RequestBody  @ValidList(groupings = {Update.class}) List<SysUser> sysUser) {
         return sysUser;
     }

bean参数间逻辑校验

需求:身份证号码倒数第二位是奇数,性别是男,身份证号码倒数第二位是偶数,性别是女

 @GroupSequenceProvider(value = SysUserGroupSequenceProvider.class)
 public class SysUser {
 ​
     public interface Women { }
     public interface Man { }
 ​
     @Null(groups = Add.class)
     @NotNull(groups = Update.class)
     private Integer userId;
     //身份证号码
     private String certCode;
     //性别
     @Pattern(regexp = "^\u7537$",groups = Man.class) //男
     @Pattern(regexp = "^\u5973$",groups = Women.class) //女
     private String sex;
 }
 public class SysUserGroupSequenceProvider implements DefaultGroupSequenceProvider<SysUser> {
 ​
     /**
      * 举例
      * 1、SysUser 的身份证号码倒数第二位是奇数,性别必须是男
      * 2、SysUser 的身份证号码倒数第二位是偶数,性别必须是女
      */
 ​
     @Override
     public List<Class<?>> getValidationGroups(SysUser sysUser) {
         List<Class<?>> defaultGroupSequence = new ArrayList<>();
         defaultGroupSequence.add(SysUser.class);//  相当于添加了默认组 javax.validation.groups.Default
 ​
         if(sysUser == null){
             return defaultGroupSequence;
         }
         if(StrUtil.isBlank(sysUser.getCertCode())){
             return defaultGroupSequence;
         }
         String certCode = sysUser.getCertCode();
         int second = certCode.charAt(certCode.length()-2);
         if(second % 2 == 0){
             //身份证号码倒数第二位是偶数 性别 女
             defaultGroupSequence.add(SysUser.Women.class);
         }
         if(second % 2 == 1){
             //身份证号码倒数第二位是奇数 性别 男
             defaultGroupSequence.add(SysUser.Man.class);
         }
 ​
         return defaultGroupSequence;
     }
 ​
 }

统一异常处理

 @RestControllerAdvice
 @Slf4j
 public class DefualtExceptionHandlerConfig {
 ​
     @ExceptionHandler(value = {MethodArgumentNotValidException.class})
     public ResponseEntity<ServerResponseEntity<Map<String, String>>> methodArgValidExceptionHandler(MethodArgumentNotValidException e) {
         log.error("参数校验异常:", e);
         Map<String, String> collect = e.getBindingResult().getFieldErrors().stream()
                 .collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
 ​
         return ResponseEntity.status(HttpStatus.OK)
                 .body(ServerResponseEntity.fail(ResponseEnum.METHOD_ARGUMENT_NOT_VALID, collect));
     }
 ​
     @ExceptionHandler
     public ResponseEntity<ServerResponseEntity<Map<Path, String>>> exceptionHandler(ConstraintViolationException e) {
         log.error("参数校验异常:", e);
         Map<Path, String> collect = e.getConstraintViolations().stream()
                 .collect(Collectors.toMap(ConstraintViolation::getPropertyPath, ConstraintViolation::getMessage));
         return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.fail(ResponseEnum.METHOD_ARGUMENT_NOT_VALID, collect));
     }
 ​
     @ExceptionHandler
     public ResponseEntity<ServerResponseEntity<Map<Integer, Map<Path, String>>>> exceptionHandler(ValidationException e){
         log.error("参数校验异常:", e);
         Map<Integer, Map<Path, String>> map = new HashMap<>();
         ((ListValidException)e.getCause()).getErrors().forEach((integer, constraintViolations) -> {
             map.put(integer, constraintViolations.stream()
                     .collect(Collectors.toMap(ConstraintViolation::getPropertyPath, ConstraintViolation::getMessage)));
         });
         return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.fail(ResponseEnum.METHOD_ARGUMENT_NOT_VALID, map));
     }
 }
 @Valid //标注在参数,属性上
 @Validated //标注在bean组件上,如controller 或service ;也可以标注在接口,所有实现类都会被校验
 ​
 @NotNull
 @Null
 //时间
 @FutureOrPresent
 @Past
 @Future
 @PastOrPresent
 //字符串
 @NotBlank
 @NotEmpty
 @Length
 @URL
 //字符串或数字
 @Range
 //数字
 @Max
 @Min
 @Negative
 @NegativeOrZero
 @Positive
 @PositiveOrZero
 //布尔值
 @AssertFalse
 @AssertTrue
 //浮点数
 @DecimalMax
 @DecimalMin
 @Digits
 @Email
 //正则
 @Pattern
 //数组,集合
 @Size
 @ConvertGroup

参考链接:5、分组验证哔哩哔哩bilibili