数据校验


前端校验

element-ui 官方文档

https://element.eleme.cn/#/zh-CN/component/form

<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
    <el-form-item label="检索首字母" prop="firstLetter">
      <el-input v-model="dataForm.firstLetter" placeholder="检索首字母"></el-input>
    </el-form-item>
</el-form>
<script>
    export default {
        data () {
          var validate1 = (rule, value, callback) => {
            if (value === '') {
              callback(new Error("首字母不能为空"))
            }else if(!/^[a-zA-Z]$/.test(value)){
              callback(new Error("首字母必须是a-z或A-Z"))
            }else {
              callback();
            }
        };
        return {
            dataForm: {
              firstLetter: ''
            },
            dataRule: {
              name: [
                  firstLetter: [
                    { validator: validate1, trigger: 'blur' }
                  ]
              ]
            }
        }    
    }
</script>

后端校验 JSR303

前端校验目的:优化用户的感受

后端检验目的:防止越过前端,直接使用错误数据访问后端,例如用 postman

1、依赖

<!-- JSR303校验 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
    <version>2.4.5</version>
</dependency>

2、注解

注解 描述
@Pattern 自定义校验,参数为正则表达式
@Positive 正数
@PositiveOrZero 0和正数
@Negative 负数
@NegativeOrZero 0和负数
@NotBlank 非空,用于字符序列(一个以上非空字符)
@NotEmpty 非空,用于字符序列(一个以上非空字符)、集合、map、数组
@NotNull 不为null,用于所有类型
@Size(max = 20,min = 0) 长度,用于字符序列(一个以上非空字符)、集合、map、数组
@Length 同上,不过只能用于字符串
@URL 校验是否为url,用于String

3、实体类定义校验规则

使用 javax.validation.constraints 包下的检验注解

@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 品牌id
     */
    @TableId
    private Long brandId;
    /**
     * 品牌名
     * 表单验证不为空
     */
    @NotBlank(message = "品牌名不能为空")
    private String name;
    /**
     * 品牌logo地址
     * 表单验证必须为 url 地址
     */
    @URL(message = "品牌的logo地址格式错误")
    private String logo;
    /**
     * 介绍
     */
    @NotBlank(message = "品牌的介绍不能为空")
    private String descript;
    /**
     * 显示状态[0-不显示;1-显示]
     */
    @TableLogic
    private Integer showStatus;
    /**
     * 检索首字母
     * 校验首字母必须是 a-z或者A-Z
     */
    @Pattern(regexp="^[a-zA-Z]$",message = "检索首字母必须是一个字母")
    private String firstLetter;
    /**
     * 排序
     * 校验不为空 并且值大于0
     */
    @NotNull
    @Min(value = 0,message = "排序必须大于等于0")
    private Integer sort;

}

4.1、局部校验方法

@Valid : 开启校验功能,效果:校验错误以后会有默认的响应;

BindingResult 给校验的bean后紧跟一个BindingResult,就可以获取到校验的结果

/**
* 保存
*/
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
    if (result.hasErrors()){
        Map<String,String> map = new HashMap<>();
        //1、获取校验的错误结果
        result.getFieldErrors().forEach((item)->{
            //FieldError 获取到错误提示
            String message = item.getDefaultMessage();
            //获取错误的属性的名字
            String field = item.getField();
            map.put(field,message);
        });

        return R.error(400,"提交的数据不合法").put("data",map);
    }
    brandService.save(brand);

    return R.ok();
}

4.2、全局校验

@PostMapping("/saveUser2")
public R saveUser2(@Valid @RequestBody User user){
    return R.ok().put("data",user);
}
/**
 * 全局异常处理
 */
@Slf4j
@RestControllerAdvice(basePackages = "com.rewind.jsr303.controller")
public class MyExceptionHandler {

    /**
     * 数据校验异常捕获
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R validException(MethodArgumentNotValidException e){
        log.error("数据校验出现问题:{},异常类型:{}",e.getMessage(),e.getClass());
        // 校验错误信息
        BindingResult result = e.getBindingResult();

        HashMap<String, String> map = new HashMap<>();
        // 获取出现异常的字段
        List<FieldError> errors = result.getFieldErrors();
        for (FieldError error : errors) {
            map.put(error.getField(),error.getDefaultMessage());
        }
        return R.error(400,"数据校验出现异常").put("data",map);
    }

    /**
     *  所有异常捕获
     * @param e
     */
    @ExceptionHandler
    public void globalException(Exception e){
        log.error("全局异常{}",e.getMessage());
    }
}

4.3、分组校验

场景:新增时,id 字段必须为空,而修改时,id 字段必须不为空。

(1)创建分组接口

/**
 * 新增校验分组
 */
public interface AddGroup {
}
/**
 * 修改校验分组
 */
public interface UpdateGroup {
}

(2)定义分组规则

@Data
public class User {

    // 分组为新增时才执行校验
    @Null(message = "新增时主键必须为空",groups = {AddGroup.class})
    // 分组为修改时才执行校验
    @NotNull(message = "修改时主键不能为空",groups = {UpdateGroup.class})
    private Long id;
}    

(3)分组

@Validated : 是Spring提供的,使用该注解后,没加分组的校验注解将不起作用

    @PostMapping("/saveUser3")
    public R saveUser3(@Validated(AddGroup.class) @RequestBody User user){
        return R.ok().put("data",user);
    }

    @PostMapping("/updateUser")
    public R updateUser(@Validated(UpdateGroup.class) @RequestBody User user){
        return R.ok().put("data",user);
    }

(4)异常处理

同4.2全局校验

5、自定义校验注解

案例:该自定义注解校验对象只能是指定的一个或多个值

(1)自定义注解

/**
 * 自定义校验注解
 */
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
// 指定自定义校验器
// 数组,可指定多个校验器,即当需要该注解对多种类型的字段生效时,可指定多个校验器
// 会自动使用类型适配的校验器进行校验
@Constraint(validatedBy = { ListValueConstraintValidator.class })
public @interface ListValue {

    // 指定默认的message信息,当前自定义注解的全包名+message
    // 在ValidationMessages.properties中定义
    String message() default "{com.rewind.jsr303.valid.ListValue.message}";
    // 可直接指定字符串
    //String message() default "必须是指定的值哦!!!";

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

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

    // 注解的自定义属性
    int[] values() default {};
}

(2)自定义校验器

/**
 * 自定义校验器
 * 泛型1:自定义注解
 * 泛型2:自定义注解可使用的字段类型
 */
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {

    private HashSet set = new HashSet();

    /**
     * 初始化方法
     */
    @Override
    public void initialize(ListValue constraintAnnotation) {
        // 获取自定义注解的详细参数信息,参数values的值
        int[] values = constraintAnnotation.values();
        for (int value : values) {
            set.add(value);
        }
    }

    /**
     * 判断是否校验成功
     * @param value 被校验字段的值
     * @param context 校验的上下文环境信息
     * @return 是否校验成功
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        // 返回 set是否包含被校验字段
        return set.contains(value);
    }
}

(3)message配置文件

在resource目录下创建 ValidationMessages.properties 配置文件,编码为utf8

用于指定默认的报错信息

com.rewind.jsr303.valid.ListValue.message = Must be the specified value

(4)使用注解

@Data
public class User {
    // 自定义校验注解
    @ListValue(values = {0,1,2},groups = AddGroup.class)
    private Integer status;
}    

  目录