SpringBoot数据校验

一.数据校验

1.已有常用注解JSR规范/hibernate自带

@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=)

被注释的元素必须符合指定的正则表达式

@NotBlank(message =)

验证字符串非 null,且长度必须大于 0

@Email

被注释的元素必须是电子邮箱地址

@Length(min=,max=)

被注释的字符串的大小必须在指定的范围内

@NotEmpty

被注释的字符串的必须非空

@Range(min=,max=,message=)

被注释的元素必须在合适的范围内

@URL(protocol=,host=, port=, regexp=, flags=) hibernate特有

合法的url

2.自定义注解校验

单个校验

package cn.redocloud.common.annotation;

import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.*;
import java.time.format.DateTimeFormatter;

/**
 * @Author: ChenTaoTao
 * @Date: 2021/8/23 14:29
 * @Describe: 日期字符串校验工具
 */
@Documented
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
/**
* validatedBy 中指定要检验器类,可指定多个  如
*/
@Constraint(validatedBy = {DateString.DateStringValidator.class})
public @interface DateString {
    /**
     * 空值是否通过校验
     * @return
     */
    boolean blank() default true;
    String message() default "日期格式错误";
    String format() default "yyyy-MM-dd";
    Class<?>[] groups() default {};
     /**
     * 指定一个继承于Payload的类/接口用于指定校验等级
     * @return
     */
    Class<? extends Payload>[] payload() default {};
    class DateStringValidator implements ConstraintValidator<DateString,String> {

        private DateString dateStrFormat;

        @Override
        public void initialize(DateString localDateTime) {
            this.dateStrFormat = localDateTime;
        }

        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            //如果注解的字段为空则不校验,直接返回true
            if(value == null || value.length() ==0){
                return dateStrFormat.blank();
            }
            String format = dateStrFormat.format();
            //如果被注解字段值的长度和format不一致,则肯定校验不通过返回false
            if(value.length() != format.length()){
                return false;
            }
            try{
                DateTimeFormatter.ofPattern(format).parse(value);
            }catch (Exception e){
                return false;
            }
            return true;
        }
    }
}

枚举类校验:

(1)自定义一个枚举父接口

package common.enums;

import java.io.Serializable;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @Author: ChenTaoTao
 * @Date: 2021/8/23 14:29
 * @Describe: 基础枚举
 */
public interface BaseEnum extends Serializable {
    /**
     * 获取键
     * @return
     */
    String getCode();

    /**
     * 获取值
     * @return
     */
    String getValue();

    /**
     * 转化成StringMap
     * @param clazz
     * @return
     */
    static Map<String, String> toMap(Class<? extends BaseEnum> clazz) {
        return Stream.of(clazz.getEnumConstants()).collect(Collectors.toMap(BaseEnum::getCode, BaseEnum::getValue));
    }

}

(2)枚举校验类:

import common.enums.BaseEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.*;
import java.util.Map;

/**
 * @Author: ChenTaoTao
 * @Date: 2021/8/23 14:53
 * @Describe: 枚举校验类
 */
@Documented
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {EnumValid.Validator.class})
public @interface EnumValid {
    /**
     * 空值是否通过校验
     *
     * @return
     */
    boolean blank() default true;

    String message() default "格式错误";

    Class<? extends BaseEnum> target();

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

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

    @Slf4j
    class Validator implements ConstraintValidator<EnumValid, String> {

        private EnumValid enumValid;

        @Override
        public void initialize(EnumValid constraintAnnotation) {
            this.enumValid = constraintAnnotation;
        }

        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            try {
                if (StringUtils.isBlank(value)) {
                    return enumValid.blank();
                }
                Map<String, String> codes = BaseEnum.toMap(enumValid.target());
                if (CollectionUtils.isEmpty(codes)) {
                    return false;
                }
                if (StringUtils.isNotBlank(codes.get(value))) {
                    return true;
                }
            } catch (Exception e) {
                log.error("校验失败,错误信息{}", e.getMessage());
            }
            return false;
        }
    }
}

3.自定义校验注解使用

/**
 * @Author: ChenTaoTao
 * @Date: 2021/7/23 11:27
 * @Describe: 列表分页查询
 */
@Data
@ApiModel(value = "分页查询条件")
public class AccessBaseQuery extends BaseQuery {
    @ApiModelProperty("主体id")
    @NotBlank(message = "主体id不能为空")
    private String entityId;

    @ApiModelProperty("处理人")
    private String handlePerson;

    @ApiModelProperty("处理状态")
    private Integer handleStatus;

    @ApiModelProperty("处理类型")
    @EnumValid(target = HandleTypeEnum.class,message = "处理类型有误")
    private String handleType;

    @ApiModelProperty("异常类型")
    @EnumValid(target = WarningTypeEnum.class,message = "小类型有误")
    private String smallType;

    @ApiModelProperty("月份 格式为yyyy-mm 不传默认为当月")
    @DateString(format = "yyyy-MM",message = "月份有误")
    private String month;

    @ApiModelProperty("是否只展示扣分项")
    private Integer select;
}

这里只是用了一种方式,比如要做手机号或者身份证id的一个注解校验,可以通过Pattern在校验器类中做正则匹配。

4.分组校验:

groups可以指定你想要校验的组别,比如 增加和编辑 编辑时部分字段是系统不允许做修改的,在注解中加入groups = xxx 指定后,在校验时,只有满足组别相同时,才会校验是否满足; payload是校验的级别 。

如下

@Data
public class User {
 
	@Min(value = 1, message = "ID不可为空", groups = {Update.class})
	private int id;
	
	@NotNull(message = "姓名不可为空", groups = {Insert.class})
	private String name;
	
	@NotNull(message = "英文姓名不可为空", groups = {Insert.class})
	private String enname;
	
	private String mobile;
	
}

controller校验

@PostMapping("/user")
	public Mono<String> insert(@RequestBody @Validated(Insert.class) User user) {
		// 处理新增逻辑
		return Mono.just("注册成功");
	}

5.@Valid和@Validated的区别

@Valid为Java提供的JSR规范(好像是只提供接口,没有做实现)

@Validated为Spring对JSR的扩展(实现)

区别

(1)分组功能

@Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制

@Valid:作为标准JSR-303规范,无分组的功能。

(2)注解作用处

@Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上

@Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上

两者是否能用于成员属性(字段)上直接影响能否提供嵌套验证的功能。

(3)嵌套验证

@Valid // 嵌套验证必须用@Valid

如入参参数中含有一个参数为List类型的参数users,而User类型中同样也有相应的@NotBlank等注解,

此时数据校验仅验证了入参参数,而没有去校验List的内容,这就可能导致错误数据存入数据库。

解决方案:在List users;字段上添加@Valid注解,表示进行层级校验

如下:

public class Item {

    @NotNull(message = "id不能为空")
    @Min(value = 1, message = "id必须为正整数")
    private Long id;

    @Valid // 嵌套验证必须用@Valid
    @NotNull(message = "props不能为空")
    @Size(min = 1, message = "props至少要有一个自定义属性")
    private List<Prop> props;
}

6.校验异常处理

/**
 * @Author: ChenTaoTao
 * @Date: 2021/7/14 12:49
 * @Describe:
 */
@RestControllerAdvice
@Slf4j
public class ServiceExceptionHandler {

    @ExceptionHandler(ConstraintViolationException.class)
    public R validateException(ConstraintViolationException e){
        return R.error("校验异常");
    }

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    private R argumentException(MethodArgumentNotValidException e){
        log.error("操作失败",e.getMessage());
        return R.error(e);
    }

    @ExceptionHandler(value = BindException.class)
    public R bindException(BindException e){
        List<ObjectError> allErrors = e.getAllErrors();
        ObjectError objectError = allErrors.get(0);
        String ms = objectError.getDefaultMessage();
        log.error(ms);
        return R.error(ms);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

SinceThenLater

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

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

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

打赏作者

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

抵扣说明:

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

余额充值