spring boot2.x 使用validator进行数据校验以及扩展自定义参数校验

一、前言

在平时开发中,前端提交表单时,通常会校验一些数据的可行性,比如是否为空,长度,身份证,邮箱等等,不过这样的验证是否就足够了呢,答案肯定是否定的。一个可靠的系统,不仅仅要依靠前端的数据验证,后端的验证也是必不可少的。

以前验证数据的合法性,需要通过写大量的if…else…条件判断,有没有更简便快捷的方法呢,答案是有的,就是使用validator进行数据校验。

spring boot的validation模块已经为我们提供了许多默认直接可以使用的注解,注意:spring boot2.3.x以后版本需要手动导入spring-boot-starter-validation。

常见注解如下:

  • @AssertTrue : 用于Boolean字段,该字段只能是true;
  • @AssertFalse : 用于Boolean字段,该字段只能是false;
  • @CreditCardNumber : 对信用卡号做一个大致的验证;
  • @DecimalMax : 以传入字符串构建一个BigDecimal,规定值要小于或等于该值;
  • @DecimalMin : 只能大于或等于该值;
  • @Max : 只能小于或等于该值;
  • @Min :只能大于或等于该值;
  • @Digits(integer = , fraction = ): 无参数,验证字符串是否合法,存在参数时,检查数字,整数位和小数位是否满足需求(整数精度小于integer ;小数部分精度小于fraction );
  • @Email : 检查是否是一个有效的email地址;
  • @Future : 检查该字段日期是否属于将来的日期;
  • @Length : 检查字段长度是否在min到max之间,只能用于字符串;
  • @NotNull : 不能为null;
  • @NotBlank :不能为空,检查时会将空格忽略;
  • @NotEmpty : 不能为空,验证字符串不为空或者null;
  • @Past : 验证该字段日期在过去;
  • @Size(min = ,max = ) : 检查该字段的size是否在min和max之间,可是字符串,数组,集合,Map等;
  • @URL : 检查是否是一个有效的URL;
  • @Null : 只能是null;
  • @Pattern(regexp=) : 验证字符串满足正则;
  • @Range(min=,max=) : 检查数字是否在范围之间,这些都包括边界值;
  • @Vaild 递归验证,用于对象、数组和集合,会对对象的元素、数组的元素进行逐个校验。

二、数据校验演示

  1. 创建一个spring boot项目,项目结构如下:

在这里插入图片描述
2. 导入maven依赖:

		<!--spring boot2.3.x以后版本需要手动导入spring-boot-starter-validation-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
  1. 创建一个用于需要验证的实体类:
@Data
public class Admin {

    //用于Boolean字段,该字段只能是true
    @AssertTrue(message = "必须是管理员")
    private Boolean isAdmin;

    //用于Boolean字段,该字段只能是false
    @AssertFalse(message = "不能是普通用户")
    private Boolean isUser;

    // 对信用卡号做一个大致的验证
    @CreditCardNumber(message = "信用卡号非法")
    private String creditCardNumber;

    //  只能小于或等于该值
    //@DecimalMax(value = "55", message = "超过最大年龄")
    @Max(value = 55)
    private Integer maxAge;

    // 只能大于或等于该值
    @DecimalMin(value = "25", message = "小于最小年龄")
    private Integer minAge;

    // 检查数字,整数位和小数位是否满足需求
    @Digits(integer = 5, fraction = 2, message = "工资数据格式有误")
    private Double salary;

    // 检查是否是一个有效的email地址
    @Email(message = "邮箱格式非法")
    private String email;

    // 检查该字段日期是否属于将来的日期
    @Future(message = "过期时间设置错误")
    private LocalDateTime expire;

    // 检查字段长度是否在min到max之间,只能用于字符串
    @Length(min = 1, max = 20, message = "管理员名称长度请在1-20之间")
    private String adminName;

    //不能为null
    @NotNull(message = "密码不能为空")
    private String password;

    private Boolean isOldFirend;//是否是老员工

    private LocalDateTime registerDate;// 注册时间

}
  1. 测试,为了方便创建一个TestController类,用于测试:
@RestController
@RequestMapping("/test")
public class TestController {

    /**
     * 使用@Validated注解开启对Admin对象的校验
     * 
     * 所有Admin对象的校验信息会存储到BindingResult对象中,从bindingResult中可以获取校验的错误信息
     */
    @PostMapping("validateAdmin")
    public String validateAdmin(@Validated Admin admin, BindingResult bindingResult) {
        String result = "success";
        // 判断是否有错误信息
        if (bindingResult.hasErrors()) {
            StringBuffer msg = new StringBuffer();
            //获取错误字段集合
            List<FieldError> fieldErrors = bindingResult.getFieldErrors();
            for (int i = 0, len = fieldErrors.size(); i < len; i++) {
                FieldError fieldError = fieldErrors.get(i);
                String fieldName = fieldError.getField();
                String message = fieldError.getDefaultMessage();
                msg.append(fieldError.getObjectName()).append("的字段").append(fieldName).append("存在错误:").append(message).append("\n");
            }
            result = msg.toString();
        }
        return result;
    }
}

使用Postman进行测试:

在这里插入图片描述
可以看到,返回了许多校验出现错误的信息。

validator数据校验快速失败

上面的测试结果返回了很多的校验错误信息,现在我觉得字段太多了,没必要全部校验完成,只要出现错误就返回给前端,快速失败。这样怎么实现呢?

其实也很简单,增加一个校验配置类就行了,代码如下:

@Configuration
public class ValidatorConfig {

    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                .failFast(true) // 是否快速失败 true
                .buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();
        return validator;
    }

}

再次使用Postman进行测试:

在这里插入图片描述
很明显,配置了快速失败以后,只要出现校验错误就快速返回了,没有进行全部校验,在字段较多的类进行校验时,可以尝试这种方式。

自定义参数校验

现在有一个新需求了,假设刚刚Admin对象中,注册时间大于10年的,isOldFirend(老员工)字段可以设置成true,否则检验失败,给前端提示。

这是复合字段验证,要两个字段一起验证,spring boot并没有给我们提供这样的注解,这就需要我们自己实现了。

参考一下@NotBlank注解:

@Documented
@Constraint(validatedBy = { })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
public @interface NotBlank {

	String message() default "{javax.validation.constraints.NotBlank.message}";

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

	Class<? extends Payload>[] payload() default { };
	
	@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
	@Retention(RUNTIME)
	@Documented
	public @interface List {
		NotBlank[] value();
	}
}
  1. 自定义一个@IsOldFriend注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) // 用于描述类、接口(包括注解类型) 或enum声明
@Documented
@Constraint(validatedBy = IsOldFriendValidator.class)
public @interface IsOldFriend {

    // 校验未通过时的返回信息
    String message() default "该管理员不是老员工";

    // 以下两行为固定模板
    Class<?>[] groups() default {};

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

}

自定义注解参考spring boot提供的注解还是比较简单的,@Constraint注解中需要提供我们针对这个注解的校验类。

  1. 实现自定义的校验类,IsOldFriendValidator:
public class IsOldFriendValidator implements ConstraintValidator<IsOldFriend, Admin> {

    @Override
    public boolean isValid(Admin value, ConstraintValidatorContext context) {
        boolean result = false;
        // 获取注册的时间
        LocalDateTime registerDate = value.getRegisterDate();
        if (registerDate != null) {
            long interval = ChronoUnit.YEARS.between(registerDate, LocalDateTime.now());
            // 如果注册年限大于等于10年,并且标注了是老员工,两者同时满足则是合法的
            if (interval >= 10 && value.getIsOldFirend() == true) {
                result = true;
            }
        }

        return result;
    }
}

自定义的校验类需要实现ConstraintValidator接口,接口的第一个泛型是你自定义的注解,我这里是IsOldFriend注解,第二个泛型表示你自定义注解作用在什么类型上,如果是String类型就改成String,我这里是作用在Admin对象上,所以是Admin。

  1. 使用自定义注解,为了演示效果,我精简一下Admin类:
@Data
@IsOldFriend(message = "你确定这个员工是老打工人嘛") // 验证是否是合法的老员工
public class Admin {

    private Boolean isOldFirend;//是否是老员工

    private LocalDateTime registerDate;// 注册时间

}
  1. 测试,修改一下TestController:
@RestController
@RequestMapping("/test")
public class TestController {

    /**
     * 使用@Validated注解开启对Admin对象的校验
     * 
     * 所有Admin对象的校验信息会存储到BindingResult对象中,从bindingResult中可以获取校验的错误信息
     */
    @PostMapping("validateAdmin")
    public String validateAdmin(@Validated Admin admin, BindingResult bindingResult) {
        String result = "success";
        // 判断是否有错误信息
        if (bindingResult.hasErrors()) {
            StringBuffer msg = new StringBuffer();
            List<ObjectError> errors = bindingResult.getAllErrors();
            for (int i = 0, len = errors.size(); i < len; i++) {
                ObjectError error = errors.get(i);
                msg.append(error.getObjectName()).append("存在错误:").append(error.getDefaultMessage()).append("\n");
            }
            result = msg.toString();
        }
        return result;
    }
}

使用Postman测试结果:

在这里插入图片描述
可以看到,自定义的注解校验是生效了的。

三、总结

本文主要介绍了:

  • spring boot2.x 使用validator进行数据校验;
  • 使用validator进行数据校验时,如果快速失败;
  • 自定义validator校验注解,进行参数校验。
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码探险家_cool

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值