阅读 61

hibernate-validator扩展自定义注解校验请求参数与请求体

此文章已同步更新至我的个人博客simonting.gitee.io


SpringBoot项目中一般使用hibernate-validator来对请求参数进行校验,但hibernate-validator提供的注解有限,有时候需要根据具体业务扩展自定义注解对参数进行校验。

前言

在实际运用中,hibernate-validator的注解分为校验请求参数参数及请求体,两个校验的情况在处理上有一些差别,下面分开讲解。

校验请求参数

自定义注解

模拟对Id进行校验

@Documented
@Constraint(validatedBy = IdCheck.IdValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR,
        ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface IdCheck {
    String message() default "Id is invalid.";

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

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

    class IdValidator implements ConstraintValidator<IdCheck, String> {
        @Override
        public void initialize(IdCheck constraintAnnotation) {
        }

        @Override
        public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
            // 这里写业务校验逻辑,此处为模拟,id不允许等于001
            return !Objects.equals(s, "001");
        }
    }
}
复制代码

其中:

@Constraint(validatedBy = IdCheck.IdValidator.class) 指定具体校验逻辑实现类。

@Target 指定注解所修饰的对象范围

String message() default "Id is invalid."; 可以自定义校验失败的提示内容

异常统一处理

全局异常统一处理请查阅我之前的文章

当hebernate-validator校验的是请求参数时,校验失败抛出的异常类型为ConstraintViolationException。

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorResult handleConstraintViolationException(ConstraintViolationException ex) {
        Set<ConstraintViolation<?>> errs = ex.getConstraintViolations();
        Set<String> fields = new HashSet<>();
        Set<String> msgs = new HashSet<>();
        for (ConstraintViolation<?> err : errs) {
            String path = err.getPropertyPath().toString();
            String field = StringUtils.substringAfterLast(path, ".");
            if (StringUtils.isEmpty(field)) {
                field = path;
            }
            fields.add(field);
            String msg = err.getMessage();
            msgs.add(msg);
        }
        String params = StringUtils.join(fields.toArray(), ",");
        String cause = StringUtils.join(msgs.toArray(), ",");
        Map<String, Object> errorParams = new HashMap<>();
        errorParams.put("param", params);
        errorParams.put("cause", cause);
        return ErrorResult.builder()
                .errorCode(500)
                .errorMsg("参数校验失败")
                .errorParams(errorParams)
                .build();
    }
}
复制代码

测试

需要特别注意的是,对@PathVariable、@RequestParam参数校验时,需要在类上面加上@Validated注解

校验哪个参数就将注解放在前面即可

1、校验@PathVariable参数

@Slf4j
@Validated //校验@PathVariable、@RequestParam参数时需加上此注解
@RestController
@RequestMapping("/v1")
public class TestController {

    @GetMapping("/check/{id}")
    public ResponseEntity<?> check(@PathVariable @IdCheck String id) {
        log.info("id is {}", id);
        return new ResponseEntity<>(HttpStatus.OK);
    }
}
复制代码

使用postman发起请求测试: 在这里插入图片描述

2、校验@RequestParam参数

新增一个对name参数的校验自定义注解,作用在@RequestParam后 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

3、当@PathVariable参数与@RequestParam参数同时校验失败时

在这里插入图片描述

校验请求体

自定义注解

这里直接使用上面自定义的两个注解IdCheck、NameCehck。

异常统一处理

hibernate-validator在校验请求体时失败抛出的异常与校验请求参数时抛出的异常有差别,类型为MethodArgumentNotValidException,因此要对此类型的异常额外做全局异常统一处理。

@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResult handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
    List<FieldError> errs = ex.getBindingResult().getFieldErrors();
    Set<String> fields = new HashSet<>();
    Set<String> msgs = new HashSet<>();
    for (FieldError err : errs) {
        fields.add(err.getField());
        String msg = err.getDefaultMessage();
        msgs.add(msg);
    }
    String params = StringUtils.join(fields.toArray(), ",");
    String cause = StringUtils.join(msgs.toArray(), ",");
    Map<String, Object> errorParams = new HashMap<>();
    errorParams.put("param", params);
    errorParams.put("cause", cause);
    return ErrorResult.builder()
        .errorCode(500)
        .errorMsg("参数校验失败")
        .errorParams(errorParams)
        .build();
}
复制代码

测试

在需要校验的实体类前加上@Validated注解,在实体类内部各字段前面加上具体的校验注解。

@PostMapping("/checkUser")
public ResponseEntity<?> checkUser(@RequestBody @Validated UserReq userReq) {
    log.info("id is {}", userReq.getId());
    log.info("name is {}", userReq.getName());
    return new ResponseEntity<>(HttpStatus.OK);
}
复制代码
@Data
public class UserReq {
    @IdCheck
    private String id;

    @NameCheck
    private String name;
}
复制代码

postman请求结果:

在这里插入图片描述