一.数据校验
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
被注释的元素必须是电子邮箱地址
@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);
}
}