统一校验

此文代码托管地址: https://gitee.com/ZomiCC/code/tree/master/validate

我们平时都会碰到很多通用校验的场景:比如字段非空校验、字段长度校验等等。如下所示:

  • @Null:被注释的元素必须为null
  • @NotNull:被注释的元素不能为null
  • @AssertTrue:该字段只能为true
  • @AssertFalse:该字段的值只能为false
  • @Min(value):被注释的元素必须是一个数字,其值必须大于等于指定的最小值
  • @Max(value):被注释的元素必须是一个数字,其值必须小于等于指定的最大值
  • @DecimalMin(“value”):被注释的元素必须是一个数字,验证小数的最小值
  • @DecimalMax(“value”):被注释的元素必须是一个数字,验证小数的最大值
  • @Size(max,min):查该字段的size是否在min和max之间,可以是字符串、数组、集合、Map等
  • @Past:被注释的元素必须是一个过去的日期
  • @Future:被注释的元素必须是一个将来的日期
  • @Pattern(regexp = “[abc]”):被注释的元素必须符合指定的正则表达式。
  • @Email:被注释的元素必须是电子邮件地址
  • @Length(max=5,min=1,message=“长度在1~5”):检查所属的字段的长度是否在min和max之间,只能用于字符串
  • @NotEmpty:被注释的字符串必须非空
  • @Range:被注释的元素必须在合适的范围内
  • @NotBlank:不能为空,检查时会将空格忽略
  • @NotEmpty:不能为空,这里的空是指空字符串
    具体是如何实现的呢,话不多说,直接看使用方式。

两种校验方式

一、@Validated + BindingResult controller校验

请求实体与BindingResult一一对应。
依赖的注解:

展开
        <!-- valid校验 -->
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
        <!-- 想要BindResult生效,这个依赖一定要有 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
实体类:
点击查看代码
@Data
@ToString
public class ReqDTO {
    @NotBlank(message = "param1不能为空")
    private String param1;
    @NotBlank(message = "param2不能为空")
    private String param2;
    @NotBlank(message = "param3不能为空")
    private String param3;
}
controller:
点击查看代码
    @PostMapping("/param1")
    public String param1(@Validated @RequestBody ReqDTO reqDTO, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            FieldError fieldError = bindingResult.getFieldError();
            assert fieldError != null;
            return fieldError.getDefaultMessage();
        }
        return "success";
    }
验证效果: 请求

响应

二、自定义Validator校验工具类

不局限于controller层,在任何地方都可以使用。
工具类:

点击查看代码
package com.example.validate.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@Component
public class ParamCheckUtil {
    private static Validator validator;
    @Autowired
    private Validator injectValidator;

    @PostConstruct
    public void preInit() {
        validator = injectValidator;
    }

    public static <T> String checkByGroupsGetOneMessage(T t, Class<?>... groups) {
        if (t == null) {
            return null;
        }
        Set<ConstraintViolation<T>> validate = validator.validate(t, groups);
        if (validate.size() == 0) {
            return null;
        }
        return validate.stream().findFirst().get().getMessage();
    }

    public static <T> String checkByGroupsGetAllMessage(T t, Class<?>... groups) {
        if (t == null) {
            return null;
        }
        Set<ConstraintViolation<T>> validate = validator.validate(t, groups);
        if (validate.size() == 0) {
            return null;
        }
        List<String> msgList = new ArrayList<>();
        validate.forEach(cv -> msgList.add(cv.getMessage()));
        return String.join(";", msgList);
    }
}

应用举例:
点击查看代码
    @PostMapping("/param2")
    public String param2(@RequestBody ReqDTO reqDTO) {
        String message = ParamCheckUtil.checkByGroupsGetAllMessage(reqDTO);
        if (message != null) {
            return message;
        }
        return "success";
    }

其他扩展校验

一、分组校验

当一个实体类应用于多个场景的请求入参时,对入参要求又不一样,我们没必要每次都新建一个冗余的实体类。那怎么办呢,可以给校验属性添加上分组。默认分组是Default。
实体类:

点击查看代码
@Data
@ToString
public class GroupReqDTO {
    @NotBlank(message = "param1不能为空")
    private String param1;
    @NotBlank(message = "param2不能为空", groups = A.class)
    private String param2;
    @NotBlank(message = "param3不能为空", groups = B.class)
    private String param3;

    public interface A {}
    public interface B {}
}
controller:
点击查看代码
    @PostMapping("/param4")
    public String param4(@Validated({GroupReqDTO.A.class}) @RequestBody GroupReqDTO reqDTO, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            FieldError fieldError = bindingResult.getFieldError();
            assert fieldError != null;
            return fieldError.getDefaultMessage();
        }
        return "success";
    }

二、嵌套校验

当存在嵌套引用对象时,可以使用嵌套校验。嵌套类型需要使用@Valid注解标识。
实体类:

点击查看代码
@Data
public class NestedReqDTO {
    @NotBlank(message = "param1不能为空")
    private String param1;
    @NotBlank(message = "param2不能为空")
    private String param2;
    @NotBlank(message = "param3不能为空")
    private String param3;
    @Valid
    @NotNull(message = "param4不能为空")
    private OtherEntity param4;

    @Data
    class OtherEntity {
        @NotBlank(message = "属性1不能为空")
        private String property1;
    }
}
controller:
点击查看代码
    @PostMapping("/param5")
    public String param5(@Validated @RequestBody NestedReqDTO reqDTO, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            FieldError fieldError = bindingResult.getFieldError();
            assert fieldError != null;
            return fieldError.getDefaultMessage();
        }
        return "success";
    }

三、复杂关联校验

借助DefaultGroupSequenceProvider,可以实现属性间依赖关系的复杂校验。具体实现参考如下代码:
实体类:

点击查看代码
@Data
@ToString
@GroupSequenceProvider(MyGroupSequenceProvider.class)
public class MutiReqDTO {
    @NotBlank(message = "param1不能为空")
    private String param1;
    @NotBlank(message = "param2不能为空")
    private String param2;
    @NotBlank(message = "当param2为1时,param3不能为空", groups = Param2.class)
    private String param3;

    public interface Param2 {}
}

分组配置器:

点击查看代码
public class MyGroupSequenceProvider implements DefaultGroupSequenceProvider<MutiReqDTO> {
    @Override
    public List<Class<?>> getValidationGroups(MutiReqDTO mutiReqDTO) {
        List<Class<?>> classes = new ArrayList<>();
        classes.add(MutiReqDTO.class);
        if (mutiReqDTO == null) {
            return classes;
        }
        // 如果param2参数为“1”,则需要校验param3
        if ("1".equals(mutiReqDTO.getParam2())) {
            classes.add(MutiReqDTO.Param2.class);
        }
        return classes;
    }
}

controller测试:

点击查看代码
    @PostMapping("/param3")
    public String param3(@RequestBody MutiReqDTO reqDTO) {
        String message = ParamCheckUtil.checkByGroupsGetAllMessage(reqDTO);
        if (message != null) {
            return message;
        }
        return "success";
    }

验证效果:

高级校验(自定义注解校验)

一、自定义注解校验

有时候javax.validation.constraints包中提供的注解不够我们使用,我们也可以自定义注解。分三步:

  1. 自定义校验注解
  2. 实现ConstraintValidator<A,T>校验器
  3. 将自定义校验注解与校验器绑定
  4. 应用:使用注解修饰校验属性

下面我们通过常用的枚举类型校验进行举例说明(详细代码参考:EnumValue和AgeValue自定义注解举例):

自定义注解
package com.example.validate.annote;

import com.example.validate.validate.EnumValueValidator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {EnumValueValidator.class})
public @interface EnumValue {
    String message() default "请输入指定值";
    int[] ints() default {};
    String[] strings() default {};
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

自定义校验器
package com.example.validate.validate;

import com.example.validate.annote.EnumValue;
import org.springframework.util.ObjectUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;

public class EnumValueValidator implements ConstraintValidator<EnumValue, Object> {
    private int[] intValues;
    private String[] stringValues;

    /**
     * 初始化
     */
    @Override
    public void initialize(EnumValue constraintAnnotation) {
        this.intValues = constraintAnnotation.ints();
        this.stringValues = constraintAnnotation.strings();
    }

    /**
     * 校验逻辑
     */
    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (ObjectUtils.isEmpty(value)) {
            return true; //空值不校验
        }
        if (value instanceof String) {
            return Arrays.asList(stringValues).contains(value);
        }
        return Arrays.asList(intValues).contains(value);
    }
}

  • 注解与校验器绑定

  • 应用
实体类
package com.example.validate.dto;

import com.example.validate.annote.AgeValue;
import com.example.validate.annote.EnumValue;
import lombok.Data;

@Data
public class ValidatorEntity {
    @EnumValue(strings = {"1", "2"}, message = "性别取值必须是[1,2]")
    private String sex;
    @AgeValue(value = 18, message = "年龄必须大于18")
    private int age;
}

controller
@RestController
public class ValidatorController {
    @PostMapping("/validate")
    public String validate(@RequestBody ValidatorEntity request) {
        String message = ParamCheckUtil.checkByGroupsGetAllMessage(request);
        return message == null ? "校验通过" : "校验不通过" + message;
    }
}

posted @ 2022-09-03 11:18  zomicc  阅读(72)  评论(0编辑  收藏  举报