SpringBoot 入参校验

一、添加依赖

基于SpringBoot,只需要给项目添加spring-boot-starter-web 依赖就够了,但在SpringBoot 2.3.1之后,spring-boot-starter-validation已经不包括在spring-boot-starter-web中,需要手动加上!

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

二、验证

1.校验参数

/**
 * @author Ian Zhao
 * @description
 * @date 2021/6/18 10:40 上午
 * ^string : 匹配以 string 开头的字符串
 * string$ :匹配以 string 结尾的字符串
 * ^string$ :精确匹配 string 字符串
 * (^Man$|^Woman$|^UGM$) : 值只能在 Man,Woman,UGM 这三个值中选择
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PersonRequest {

    @NotNull(message = "classId 不能为空")
    @Email
    private String classId;

    @Size(max = 10)
    @NotNull(message = "name 不能为空")
    private String name;

    @Pattern(regexp = "(^Man$|^Woman$|^UGM$)", message = "sex 值不在可选范围内")
    @NotNull(message = "sex 不能为空")
    private String sex;
}

2.自定义捕获异常

GlobalExceptionHandler
自定义异常处理器可以帮助我们捕获异常,并进行一些简单的处理。

/**
 * @author Ian Zhao
 * @description 自定义异常处理器
 * @date 2021/6/17 4:49 下午
 */
@ControllerAdvice(assignableTypes = {PersonController.class})
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationException(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getAllErrors().forEach(
                error -> {
                    String fieldName = ((FieldError) error).getField();
                    String errorMessage = error.getDefaultMessage();
                    errors.put(fieldName, errorMessage);
                }
        );
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors);
    }
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<String> handleConstraintViolationException(ConstraintViolationException e) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
    }
}

3.验证请求体

验证请求体即被@RequestBody注解标记的方法参数。

PersonController
我们在需要验证的参数上加上了@Valid注解,如果验证失败,它将抛出MethodArgumentNotValidException。默认情况下,Spring 会将此异常转换为 HTTP Status 400(错误请求)。

/**
 * @author Ian Zhao
 * @description TODO
 * @date 2021/6/18 10:39 上午
 */
@RestController
@RequestMapping("/api/person")
@Validated
public class PersonController {

    @PostMapping
    public ResponseEntity<PersonRequest> save(@RequestBody @Valid PersonRequest personRequest) {
        return ResponseEntity.ok().body(personRequest);
    }
}

4.postman测试

在这里插入图片描述

三、自定义Validator

如果自带的校验注解无法满足你的需求的话,你还可以自定义实现注解。

案例一:校验特定字段的值是否在可选范围
比如我们现在多了这样一个需求:PersonRequest 类多了一个 Region 字段,Region 字段只能是China、China-Taiwan、China-HongKong这三个中的一个。

第一步,你需要创建一个注解 Region。

/**
 * 自定义注解
 * @author zhaodashuai7
 */
@Target({FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = RegionValidator.class)
@Documented
public @interface Region {

    String message() default "Region 值不在可选范围内";

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

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

第二步,你需要实现 ConstraintValidator接口,并重写isValid 方法。

/**
 * @Author: Ian Zhao
 * @Date: 2021/6/18 11:10 上午
 */
public class RegionValidator implements ConstraintValidator<Region,String> {

    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        HashSet<Object> regions = new HashSet<>();
        regions.add("China");
        regions.add("China-Taiwan");
        regions.add("China-HongKong");
        return regions.contains(value);
    }
}

现在你就可以使用这个注解:

    @Region
    private String region;

案例二:校验电话号码
校验我们的电话号码是否合法,这个可以通过正则表达式来做,相关的正则表达式都可以在网上搜到,你甚至可以搜索到针对特定运营商电话号码段的正则表达式。

PhoneNumber.java

/**
 * @Author: Ian Zhao
 * @Date: 2021/6/18 11:31 上午
 * @Description TODO
 */
@Documented
@Constraint(validatedBy = PhoneNumberValidator.class)
@Target({FIELD,PARAMETER})
@Retention(RUNTIME)
public @interface PhoneNumber {

    String message() default "Invalid phone number";

    Class[] groups() default {};

    Class[] payload() default {};

}

PhoneNumberValidator.java

/**
 * @Description
 * @Author: Ian Zhao
 * @Date: 2021/6/18 11:35 上午
 */
public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber,String> {
    @Override
    public boolean isValid(String phoneField, ConstraintValidatorContext constraintValidatorContext) {
        if (StrUtil.isBlankIfStr(phoneField)) {
            return true;
        }

        String regExp = "^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$";
        return phoneField.matches(regExp);
    }
}

使用注解:

    @PhoneNumber(message = "phoneNumber 格式不正确")
    @NotNull(message = "phoneNumber 不能为空")
    private String phoneNumber;

测试
在这里插入图片描述

四、总结

1、注解

常用校验注解总结
JSR303 定义了 Bean Validation(校验)的标准 validation-api,并没有提供实现。Hibernate Validation是对这个规范/规范的实现 hibernate-validator,并且增加了 @Email、@Length、@Range 等注解。Spring Validation 底层依赖的就是Hibernate Validation。

JSR 提供的校验注解:

@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
Hibernate Validator 提供的校验注解:

@NotBlank(message =) 验证字符串非 null,且长度必须大于 0
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内

2、注意

@Validated 放在方法上
@Valid 放在参数上
在这里插入图片描述

3、拓展

经常有小伙伴问到:“@NotNull 和 @Column(nullable = false) 两者有什么区别?”

我这里简单回答一下:

@NotNull是 JSR 303 Bean 验证批注,它与数据库约束本身无关。
@Column(nullable = false) : 是 JPA 声明列为非空的方法。
总结来说就是即前者用于验证,而后者则用于指示数据库创建表的时候对表的约束。