java 校验参数接收类 当A字段符合条件时,B字段也需符合什么条件
java 校验参数接收类 当A字段符合条件时,B字段也需符合什么条件 或 B字段需满足 某个注解的条件
使用介绍
参数校验注解 当 caseField 被标记的字段 值符合 thenValues 设定的条件时 则进行 whenField 字段的校验 使用场景 如 :当 A字段 接收的值满足 thenValues 中的条件时,则 进行校验B字段 接收的值 是否满足 whenFieldValues 中的条件,如不满足,则返回提示信息 eg:1 @CaseWhenField(caseField = "param1", thenValues = {"123", "你猜"}, whenField = "param2",whenFieldValues = {"吧啦啦能量", "小魔仙", "全身", "吧啦啦能量测"},whenFieldValidatoeClass = Size.class,checkMap = "{'min':3,'max':5}"),message = "当param1等于123或你猜时,param2 需为吧啦啦能量、小魔仙、全身" !!!校验优先级 首先检测 whenFieldValidatoeClass 值 如有 则 whenFieldValues 不校验 !!!前提条件:caseField 被标记的字段接收值,满足 thenValues 才进行下面的校验 一、whenFieldValidatoeClass:如满足则进行whenFieldValidatoeClass 中指定的注解 @Size的校验, 由 checkMap 做 注解set值,如 {'min':3,'max':5}则标识 param2 字段的值长度 最小为3 最大为5whenFieldValidatoeClass 可不进行校验 默认不校验================================================================================================;@Size 注解也可为 @Pattern 则 checkMap = "{'pattern':RoleEnum2}" Pattern 为一个正则 校验注解 如其中的正则不满足需求 1、可自定义枚举类 实现 EnumInterface {@link EnumInterface},则在项目启动时,会扫描 存入;================================================================================================使用方法 : 如 自定枚举为 RoleEnum 则 checkMap = "{'pattern':RoleEnum2}" 为 枚举名称 加 枚举中定义的 key 拼接 获取正则表达式进行校验二、无whenFieldValidatoeClass 注解校验 以上面eg为例:当 param1 接收到的值为 123 或 你猜 时,进行校验 param2 的值是否为 "吧啦啦能量", "小魔仙", "全身", "吧啦啦能量测" ,如不满足则返回message另一种情况 :当 whenFieldValues 为默认值 时,则会校验 当 param1 接收到的值为 123 或 你猜 时 ,param2 值不能为空
使用案列
@CaseWhenField(caseField = "param1",thenValues = {"123", "你猜"},whenField = "param2",whenFieldValues = {"吧啦啦能量", "小魔仙", "全身", "吧啦啦能量测",""},whenFieldValidatoeClass = FixLength.class,message = "当param1等于123或你猜时,param2 需为吧啦啦能量、小魔仙、全身",checkMap = "{'length':[12,13,15]}")
//@CaseWhenField(caseField = "param1", thenValues = {"123", "你猜", "333"}, whenField = "param2", whenFieldValues = {"吧啦啦能量", "小魔仙", "全身变"},
// message = "当param1等于123或你猜时,param2 需为吧啦啦能量、小魔仙、全身变")
//@CaseWhenField(caseField = "param1", thenValues = {"112323111", "333"}, whenField = "param4", message = "当param1为112323111或333时,param4不能为空")
//@CaseWhenField(caseField = "param1", thenValues = {"112323111", "333"}, whenField = "param5",whenFieldValues = {"1"},message = "当param1为112323111或333时,param5需要为1")
@Data
//@AtLeastOneNotEmpty(fields = {"name", "file"}, message = "name、file至少有一个不能为空")
//@AtLeastOneNotEmpty(fields = {"param1", "param2"}, message = "param1、param2至少有一个不能为空")
//@AtLeastOneNotEmpty(fields = {"param3", "param4"}, message = "param3、param4至少有一个不能为空")
public class MyClass extends BaseProcess {@Length(max = 20,min = 10)private String name;private String file;private String param1;private String param2;private String param3;private String param4;private String param5;
}
上代码
1、注解类
/*** 参数校验注解* 当 caseField 被标记的字段 值符合 thenValues 设定的条件时 则进行 whenField 字段的校验* 使用场景 如 :* 当 A字段 接收的值满足 thenValues 中的条件时,则 进行校验B字段 接收的值 是否满足 whenFieldValues 中的条件,如不满足,则返回提示信息* eg:* 1 @CaseWhenField(caseField = "param1", thenValues = {"123", "你猜"}, whenField = "param2",* whenFieldValues = {"吧啦啦能量", "小魔仙", "全身", "吧啦啦能量测"},* whenFieldValidatoeClass = Size.class,* checkMap = "{'min':3,'max':5}"),* message = "当param1等于123或你猜时,param2 需为吧啦啦能量、小魔仙、全身"** !!!校验优先级 首先检测 whenFieldValidatoeClass 值 如有 则 whenFieldValues 不校验** !!!前提条件:caseField 被标记的字段接收值,满足 thenValues 才进行下面的校验** 一、whenFieldValidatoeClass:* 如满足则进行whenFieldValidatoeClass 中指定的注解 @Size的校验, 由 checkMap 做 注解set值,如 {'min':3,'max':5}* 则标识 param2 字段的值长度 最小为3 最大为5** whenFieldValidatoeClass 可不进行校验 默认不校验** ================================================================================================* ;@Size 注解也可为 @Pattern 则 checkMap = "{'pattern':RoleEnum2}" Pattern 为一个正则 校验注解 如其中的正则不满足需求 有两种添加自定义枚举正则的方式** 1、可自定义枚举类 实现 EnumInterface {@link EnumInterface},则在项目启动时,会扫描 存入;* ================================================================================================* 使用方法 : 如 自定枚举为 RoleEnum 则 checkMap = "{'pattern':RoleEnum2}" 为 枚举名称 加 枚举中定义的 key 拼接 获取正则表达式进行校验** 二、无whenFieldValidatoeClass 注解校验 以上面eg为例:* 当 param1 接收到的值为 123 或 你猜 时,进行校验 param2 的值是否为 "吧啦啦能量", "小魔仙", "全身", "吧啦啦能量测" ,如不满足则返回message* 另一种情况 :* 当 whenFieldValues 为默认值 时,则会校验 当 param1 接收到的值为 123 或 你猜 时 ,param2 值不能为空** @author: chenjiaxiang* @create: 2023/4/10 09:30**/
@Target({ ElementType.TYPE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {CaseWhenFieldValidator.class})
@Repeatable(CaseWhenField.CaseWhenFields.class)
public @interface CaseWhenField {/*** 字段名 A字段*/String caseField();/*** A字段的值*/String[] thenValues() default "";/*** 要校验的字段名*/String whenField();/*** 要校验的字段值*/String[] whenFieldValues() default "";/*** 要检验字段使用的注解*/Class<? extends Annotation> whenFieldValidatoeClass() default Null.class;/*** 校验失败时的提示信息*/String message() default "";Class<?>[] groups() default {};// 约束注解的有效负载Class<? extends Payload>[] payload() default {};String checkMap() default "{}" ;/*** 支持同类多使用*/@Target({ElementType.TYPE})@Retention(RUNTIME)@Documented@interface CaseWhenFields {CaseWhenField[] value();}
}
2、注解实现方法类
/*** @author chenjiaxiang* @create 2023/4/10 09:30**/@Slf4j
@Component
public class CaseWhenFieldValidator extends AbstractFactoryMy implements ConstraintValidator<CaseWhenField, Object>{private CaseWhenField caseWhenField;@Overridepublic void initialize(CaseWhenField caseWhenField) {this.caseWhenField = caseWhenField;}@Overridepublic boolean isValid(Object value, ConstraintValidatorContext context) {return validateWithDepends(value, context);}public boolean validateWithDepends(Object validateObject, ConstraintValidatorContext context) {Field dependsOnField;Field onField;try {dependsOnField = validateObject.getClass().getDeclaredField(caseWhenField.whenField());onField = validateObject.getClass().getDeclaredField(caseWhenField.caseField());ReflectionUtils.makeAccessible(dependsOnField);ReflectionUtils.makeAccessible(onField);//要检验的字段值Object value = dependsOnField.get(validateObject);Object onFieldVal = onField.get(validateObject);//====================List<Object> list = new ArrayList<>(Arrays.asList(caseWhenField.thenValues())).stream().filter(x -> !org.springframework.util.StringUtils.isEmpty(x)).collect(Collectors.toList());List<Object> dependsOnFieldValues = new ArrayList<>(Arrays.asList(caseWhenField.whenFieldValues())).stream().filter(x -> !org.springframework.util.StringUtils.isEmpty(x)).collect(Collectors.toList());//当指定A字段不为空且指定A字段的值不为指定值时跳过校验if (!CollectionUtils.isEmpty(list) && !list.contains(onFieldVal)) {return true;}Class<? extends Annotation> validatoeClass = caseWhenField.whenFieldValidatoeClass();//满足A字段不为空且为指定值时,则进行判断是否对B字段进行进行注解校验if (!validatoeClass.equals(Null.class)) {if (super.checkFieldWithAnnotation(validatoeClass)) {boolean b = checkWithSetClass(context, caseWhenField, dependsOnField, value);log.info("字段:{},进行注解:{}校验,验证结果:{}", dependsOnField.getName(), validatoeClass.getName(), b);return b;}String message = "注解:" + validatoeClass.getTypeName() + ",不支持在FIELD上操作";throw new UnsupportedOperationException(message);}//当注解指定值为空时,则判定该字段不能为空 ,则提示用户if (CollectionUtils.isEmpty(dependsOnFieldValues) && org.springframework.util.StringUtils.isEmpty(value)) {return false;}//当注解指定值不为空时,则判定指定字段的值是否包含,如不包含,则提示用户if (!CollectionUtils.isEmpty(dependsOnFieldValues) && !dependsOnFieldValues.contains(value)) {return false;}} catch (UnsupportedOperationException unsupportedOperationException) {throw unsupportedOperationException;} catch (Exception e) {log.error(e.getMessage(), e);return false;}return true;}private boolean checkWithSetClass(ConstraintValidatorContext context, CaseWhenField caseWhenField, Field dependsOnField, Object value) {Class<? extends Annotation> validatoeClass = caseWhenField.whenFieldValidatoeClass();if (!CollectionUtils.isEmpty(this.getCache(validatoeClass))) {ConstraintValidator<Annotation, Object> instance = getConstraintValidatorByValidatoeClass(validatoeClass, dependsOnField.getType().getTypeName());if (Objects.isNull(instance)){log.error("注解:{},未查询到验证注解类",validatoeClass.getTypeName());return false;}Map<String, Field> typeWithFiledList = getTypeWithFiledList(instance);Map<String, Object> pram = GSON.fromJson(caseWhenField.checkMap(), new TypeToken<Map<String, Object>>() {});pram.forEach((k, v) -> {Field field = typeWithFiledList.get(k);Optional.ofNullable(field).ifPresent(t -> {ReflectionUtils.makeAccessible(t);try {Class<?> type = field.getType();boolean basicType = ClassUtil.isBasicType(type);Object convert;if (basicType) {convert = Convert.convert(type, v);} else if (type.isAssignableFrom(Pattern.class)) {String regexType = String.valueOf(v);convert = getPatternByType(regexType);} else {convert = v;}ReflectionUtils.setField(t, instance, convert);} catch (Exception e) {log.error(e.getMessage(), e);}});});typeWithFiledList.clear();return instance.isValid(value, context);}return false;}@PostConstructpublic static void initRegexMaps() {StopWatch stopWatch = new StopWatch("initRegexMap");stopWatch.start();initMap();stopWatch.stop();log.info("自动注入,初始化枚举map成功,耗时:{}ms", stopWatch.getLastTaskTimeMillis());}private static void initMap() {String thisDef = PatternEnum.class.getPackage().getName();SCAN_PACKAGE.add(thisDef);String startupClass = System.getProperty("sun.javamand");Class<Object> objectClass = ClassUtil.loadClass(startupClass, false);SpringBootApplication annotation = objectClass.getAnnotation(SpringBootApplication.class);Set<String> collect = Arrays.stream(annotation.scanBasePackages()).filter(startupClass::startsWith).collect(Collectors.toSet());if (!CollectionUtils.isEmpty(collect)) {SCAN_PACKAGE.addAll(collect);}log.info("当前要扫描的包:{}", SCAN_PACKAGE);SCAN_PACKAGE.forEach(path -> {Set<Class<?>> classSet = ClassUtil.scanPackageBySuper(path, EnumInterface.class);if (CollectionUtils.isEmpty(classSet)) {log.info("包:{},未扫描到继承EnumInterface类的枚举", path);} else {addEnumToRegexMap(classSet);}});}/*** 外部添加正则方法*/public static Map<String, String> getRegexMap() {if (CollectionUtils.isEmpty(REGEX)) {initMap();}return REGEX;}/*** 通过传入的正则类型获取对应的正则表达式,如果未查询到则直接使用当前字符串为正则*/public Object getPatternByType(String type) {if (getRegexMap().containsKey(type)) {return Patternpile(getRegexMap().get(type));}return Patternpile(type);}
}
3、注解使用到的类
@Slf4j
public abstract class AbstractFactoryMy implements MyValidatorFactory {protected static final Map<Class<? extends Annotation>, List<? extends ConstraintValidatorDescriptor<? extends Annotation>>> CACHE = Maps.newConcurrentMap();/*** 正则*/protected static final Map<String, String> REGEX = Maps.newConcurrentMap();protected static final Set<String> SCAN_PACKAGE = new HashSet<>();public static final Gson GSON = new GsonBuilder().registerTypeAdapter(new TypeToken<Map<String, Object>>() {}.getType(), new DataTypeAdapter()).create();private static final ConstraintValidatorFactory DEFAULT_VALIDATOR_FACTORY = Validation.byDefaultProvider().configure().getDefaultConstraintValidatorFactory();/*** 添加正则枚举值到RegexMap中*/protected static void addEnumToRegexMap(Set<Class<?>> classSet) {classSet.stream().filter(ClassUtil::isEnum).forEach(t -> {try {Object[] enumConstants = t.getEnumConstants();Method getKey = t.getMethod("getKey");Method getRegex = t.getMethod("getRegex");for (Object enumConstant : enumConstants) {String key = t.getSimpleName() + getKey.invoke(enumConstant);String regex = String.valueOf(getRegex.invoke(enumConstant));log.info("key:{},regex:{}", key, regex);if (!REGEX.containsKey(key)) {REGEX.put(key, StringUtils.isEmpty(regex) ? "*" : regex);}}} catch (Exception e) {log.error("添加枚举{}失败:", t.getName(), e);}});}/*** 确定当前注解是否可以使用在字段上*/protected boolean checkFieldWithAnnotation(Class<? extends Annotation> validatoeClass) {ElementType[] elementTypes = validatoeClass.getAnnotation(Target.class).value();for (ElementType elementType : elementTypes) {if (elementType.equals(ElementType.FIELD)) {return true;}}return false;}/*** 从CACHE中获取对应注解类的ConstraintValidatorDescriptor*/protected List<? extends ConstraintValidatorDescriptor<? extends Annotation>> getCache(Class<? extends Annotation> aClass) {return CACHEputeIfAbsent(aClass, this::findByAnnotation);}/*** 根据注解class查询对应的ConstraintValidatorDescriptor*/@Overridepublic List<? extends ConstraintValidatorDescriptor<? extends Annotation>> findByAnnotation(Class<? extends Annotation> aClass) {Set<String> pathSet = new HashSet<>();pathSet.add(aClass.getTypeName());ConstraintHelper constraintHelper = ConstraintHelper.forBuiltinConstraints(pathSet);List<? extends ConstraintValidatorDescriptor<? extends Annotation>> allValidatorDescriptors = constraintHelper.getAllValidatorDescriptors(aClass);if (!CollectionUtils.isEmpty(allValidatorDescriptors)) {log.info("添加注解:{}对应的ConstraintValidatorDescriptor", aClass.getTypeName());}return allValidatorDescriptors;}/*** 获取ConstraintValidator对应的字段*/protected Map<String, Field> getTypeWithFiledList(ConstraintValidator<Annotation, Object> instance) {Map<String, Field> fieldMap = new HashMap<>(16);Class<?> tempClass = instance.getClass();while (tempClass != null) {Field[] declaredFields = tempClass.getDeclaredFields();if (!CollectionUtils.isEmpty(Arrays.asList(declaredFields))) {Arrays.asList(declaredFields).forEach(t -> fieldMap.put(t.getName(), t));}tempClass = tempClass.getSuperclass();}return fieldMap;}@Overridepublic ConstraintValidator<Annotation, Object> getConstraintValidatorByValidatoeClass(Class<? extends Annotation> validatoeClass, String typeName) {List<? extends ConstraintValidatorDescriptor<? extends Annotation>> cache = this.getCache(validatoeClass);Class<Object> objectClass = ClassUtil.loadClass(typeName);return cache.stream().filter(descriptor -> ((Class<?>) descriptor.getValidatedType()).isAssignableFrom(objectClass)).findFirst().map(constraintValidatorDescriptor ->(ConstraintValidator<Annotation, Object>) DEFAULT_VALIDATOR_FACTORY.getInstance(constraintValidatorDescriptor.getValidatorClass())).orElse(null);}
}
public interface MyValidatorFactory {/*** 根据注解class查询对应的ConstraintValidatorDescriptor** @param aClass 注解class类* @return 注解对应的ConstraintValidatorDescriptor集合*/List<? extends ConstraintValidatorDescriptor<? extends Annotation>> findByAnnotation(Class<? extends Annotation> aClass);/*** 根据校验注解获取对应的ConstraintValidator** @param validatoeClass 校验注解class* @param typeName 当前校验字段的类型 Field.getType().getTypeName()* @return 注解对应的实现校验类*/ConstraintValidator<Annotation, Object> getConstraintValidatorByValidatoeClass(Class<? extends Annotation> validatoeClass, String typeName);
}
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Null {}
4、默认注册到缓存中的正则规则
@Getter
public enum PatternEnum implements EnumInterface {/*** 邮箱*/EMAIL("1","^\\s*\\w+(?:\\.{0,1}[\\w-]+)*@[a-zA-Z0-9]+(?:[-.][a-zA-Z0-9]+)*\\.[a-zA-Z]+\\s*$"),/*** 身份证*/ID_CARD("2","^\\d{6}((((((19|20)\\d{2})(0[13-9]|1[012])(0[1-9]|[12]\\d|30))|(((19|20)\\d{2})(0[13578]|1[02])31)|((19|20)\\d{2})02(0[1-9]|1\\d|2[0-8])|((((19|20)([13579][26]|[2468][048]|0[48]))|(2000))0229))\\d{3})|((((\\d{2})(0[13-9]|1[012])(0[1-9]|[12]\\d|30))|((\\d{2})(0[13578]|1[02])31)|((\\d{2})02(0[1-9]|1\\d|2[0-8]))|(([13579][26]|[2468][048]|0[048])0229))\\d{2}))(\\d|X|x)$"),/*** 手机号*/PHONE("3",""),;/*** map Key*/private final String key;/*** map value 正则*/private final String regex;PatternEnum(String key, String regex) {this.key = key;this.regex = regex;}public int getThisOrd(){return this.ordinal();}
}
5、增加其他规则示例
@Getter
public enum RoleEnum implements EnumInterface {/*** 检校合作省院管理员*/PROVINCE_JXHZGLY("68", "22323"),;private String key;private String regex;RoleEnum(String key, String regex) {this.key = key;this.regex = regex;}
}
java 校验参数接收类 当A字段符合条件时,B字段也需符合什么条件
java 校验参数接收类 当A字段符合条件时,B字段也需符合什么条件 或 B字段需满足 某个注解的条件
使用介绍
参数校验注解 当 caseField 被标记的字段 值符合 thenValues 设定的条件时 则进行 whenField 字段的校验 使用场景 如 :当 A字段 接收的值满足 thenValues 中的条件时,则 进行校验B字段 接收的值 是否满足 whenFieldValues 中的条件,如不满足,则返回提示信息 eg:1 @CaseWhenField(caseField = "param1", thenValues = {"123", "你猜"}, whenField = "param2",whenFieldValues = {"吧啦啦能量", "小魔仙", "全身", "吧啦啦能量测"},whenFieldValidatoeClass = Size.class,checkMap = "{'min':3,'max':5}"),message = "当param1等于123或你猜时,param2 需为吧啦啦能量、小魔仙、全身" !!!校验优先级 首先检测 whenFieldValidatoeClass 值 如有 则 whenFieldValues 不校验 !!!前提条件:caseField 被标记的字段接收值,满足 thenValues 才进行下面的校验 一、whenFieldValidatoeClass:如满足则进行whenFieldValidatoeClass 中指定的注解 @Size的校验, 由 checkMap 做 注解set值,如 {'min':3,'max':5}则标识 param2 字段的值长度 最小为3 最大为5whenFieldValidatoeClass 可不进行校验 默认不校验================================================================================================;@Size 注解也可为 @Pattern 则 checkMap = "{'pattern':RoleEnum2}" Pattern 为一个正则 校验注解 如其中的正则不满足需求 1、可自定义枚举类 实现 EnumInterface {@link EnumInterface},则在项目启动时,会扫描 存入;================================================================================================使用方法 : 如 自定枚举为 RoleEnum 则 checkMap = "{'pattern':RoleEnum2}" 为 枚举名称 加 枚举中定义的 key 拼接 获取正则表达式进行校验二、无whenFieldValidatoeClass 注解校验 以上面eg为例:当 param1 接收到的值为 123 或 你猜 时,进行校验 param2 的值是否为 "吧啦啦能量", "小魔仙", "全身", "吧啦啦能量测" ,如不满足则返回message另一种情况 :当 whenFieldValues 为默认值 时,则会校验 当 param1 接收到的值为 123 或 你猜 时 ,param2 值不能为空
使用案列
@CaseWhenField(caseField = "param1",thenValues = {"123", "你猜"},whenField = "param2",whenFieldValues = {"吧啦啦能量", "小魔仙", "全身", "吧啦啦能量测",""},whenFieldValidatoeClass = FixLength.class,message = "当param1等于123或你猜时,param2 需为吧啦啦能量、小魔仙、全身",checkMap = "{'length':[12,13,15]}")
//@CaseWhenField(caseField = "param1", thenValues = {"123", "你猜", "333"}, whenField = "param2", whenFieldValues = {"吧啦啦能量", "小魔仙", "全身变"},
// message = "当param1等于123或你猜时,param2 需为吧啦啦能量、小魔仙、全身变")
//@CaseWhenField(caseField = "param1", thenValues = {"112323111", "333"}, whenField = "param4", message = "当param1为112323111或333时,param4不能为空")
//@CaseWhenField(caseField = "param1", thenValues = {"112323111", "333"}, whenField = "param5",whenFieldValues = {"1"},message = "当param1为112323111或333时,param5需要为1")
@Data
//@AtLeastOneNotEmpty(fields = {"name", "file"}, message = "name、file至少有一个不能为空")
//@AtLeastOneNotEmpty(fields = {"param1", "param2"}, message = "param1、param2至少有一个不能为空")
//@AtLeastOneNotEmpty(fields = {"param3", "param4"}, message = "param3、param4至少有一个不能为空")
public class MyClass extends BaseProcess {@Length(max = 20,min = 10)private String name;private String file;private String param1;private String param2;private String param3;private String param4;private String param5;
}
上代码
1、注解类
/*** 参数校验注解* 当 caseField 被标记的字段 值符合 thenValues 设定的条件时 则进行 whenField 字段的校验* 使用场景 如 :* 当 A字段 接收的值满足 thenValues 中的条件时,则 进行校验B字段 接收的值 是否满足 whenFieldValues 中的条件,如不满足,则返回提示信息* eg:* 1 @CaseWhenField(caseField = "param1", thenValues = {"123", "你猜"}, whenField = "param2",* whenFieldValues = {"吧啦啦能量", "小魔仙", "全身", "吧啦啦能量测"},* whenFieldValidatoeClass = Size.class,* checkMap = "{'min':3,'max':5}"),* message = "当param1等于123或你猜时,param2 需为吧啦啦能量、小魔仙、全身"** !!!校验优先级 首先检测 whenFieldValidatoeClass 值 如有 则 whenFieldValues 不校验** !!!前提条件:caseField 被标记的字段接收值,满足 thenValues 才进行下面的校验** 一、whenFieldValidatoeClass:* 如满足则进行whenFieldValidatoeClass 中指定的注解 @Size的校验, 由 checkMap 做 注解set值,如 {'min':3,'max':5}* 则标识 param2 字段的值长度 最小为3 最大为5** whenFieldValidatoeClass 可不进行校验 默认不校验** ================================================================================================* ;@Size 注解也可为 @Pattern 则 checkMap = "{'pattern':RoleEnum2}" Pattern 为一个正则 校验注解 如其中的正则不满足需求 有两种添加自定义枚举正则的方式** 1、可自定义枚举类 实现 EnumInterface {@link EnumInterface},则在项目启动时,会扫描 存入;* ================================================================================================* 使用方法 : 如 自定枚举为 RoleEnum 则 checkMap = "{'pattern':RoleEnum2}" 为 枚举名称 加 枚举中定义的 key 拼接 获取正则表达式进行校验** 二、无whenFieldValidatoeClass 注解校验 以上面eg为例:* 当 param1 接收到的值为 123 或 你猜 时,进行校验 param2 的值是否为 "吧啦啦能量", "小魔仙", "全身", "吧啦啦能量测" ,如不满足则返回message* 另一种情况 :* 当 whenFieldValues 为默认值 时,则会校验 当 param1 接收到的值为 123 或 你猜 时 ,param2 值不能为空** @author: chenjiaxiang* @create: 2023/4/10 09:30**/
@Target({ ElementType.TYPE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {CaseWhenFieldValidator.class})
@Repeatable(CaseWhenField.CaseWhenFields.class)
public @interface CaseWhenField {/*** 字段名 A字段*/String caseField();/*** A字段的值*/String[] thenValues() default "";/*** 要校验的字段名*/String whenField();/*** 要校验的字段值*/String[] whenFieldValues() default "";/*** 要检验字段使用的注解*/Class<? extends Annotation> whenFieldValidatoeClass() default Null.class;/*** 校验失败时的提示信息*/String message() default "";Class<?>[] groups() default {};// 约束注解的有效负载Class<? extends Payload>[] payload() default {};String checkMap() default "{}" ;/*** 支持同类多使用*/@Target({ElementType.TYPE})@Retention(RUNTIME)@Documented@interface CaseWhenFields {CaseWhenField[] value();}
}
2、注解实现方法类
/*** @author chenjiaxiang* @create 2023/4/10 09:30**/@Slf4j
@Component
public class CaseWhenFieldValidator extends AbstractFactoryMy implements ConstraintValidator<CaseWhenField, Object>{private CaseWhenField caseWhenField;@Overridepublic void initialize(CaseWhenField caseWhenField) {this.caseWhenField = caseWhenField;}@Overridepublic boolean isValid(Object value, ConstraintValidatorContext context) {return validateWithDepends(value, context);}public boolean validateWithDepends(Object validateObject, ConstraintValidatorContext context) {Field dependsOnField;Field onField;try {dependsOnField = validateObject.getClass().getDeclaredField(caseWhenField.whenField());onField = validateObject.getClass().getDeclaredField(caseWhenField.caseField());ReflectionUtils.makeAccessible(dependsOnField);ReflectionUtils.makeAccessible(onField);//要检验的字段值Object value = dependsOnField.get(validateObject);Object onFieldVal = onField.get(validateObject);//====================List<Object> list = new ArrayList<>(Arrays.asList(caseWhenField.thenValues())).stream().filter(x -> !org.springframework.util.StringUtils.isEmpty(x)).collect(Collectors.toList());List<Object> dependsOnFieldValues = new ArrayList<>(Arrays.asList(caseWhenField.whenFieldValues())).stream().filter(x -> !org.springframework.util.StringUtils.isEmpty(x)).collect(Collectors.toList());//当指定A字段不为空且指定A字段的值不为指定值时跳过校验if (!CollectionUtils.isEmpty(list) && !list.contains(onFieldVal)) {return true;}Class<? extends Annotation> validatoeClass = caseWhenField.whenFieldValidatoeClass();//满足A字段不为空且为指定值时,则进行判断是否对B字段进行进行注解校验if (!validatoeClass.equals(Null.class)) {if (super.checkFieldWithAnnotation(validatoeClass)) {boolean b = checkWithSetClass(context, caseWhenField, dependsOnField, value);log.info("字段:{},进行注解:{}校验,验证结果:{}", dependsOnField.getName(), validatoeClass.getName(), b);return b;}String message = "注解:" + validatoeClass.getTypeName() + ",不支持在FIELD上操作";throw new UnsupportedOperationException(message);}//当注解指定值为空时,则判定该字段不能为空 ,则提示用户if (CollectionUtils.isEmpty(dependsOnFieldValues) && org.springframework.util.StringUtils.isEmpty(value)) {return false;}//当注解指定值不为空时,则判定指定字段的值是否包含,如不包含,则提示用户if (!CollectionUtils.isEmpty(dependsOnFieldValues) && !dependsOnFieldValues.contains(value)) {return false;}} catch (UnsupportedOperationException unsupportedOperationException) {throw unsupportedOperationException;} catch (Exception e) {log.error(e.getMessage(), e);return false;}return true;}private boolean checkWithSetClass(ConstraintValidatorContext context, CaseWhenField caseWhenField, Field dependsOnField, Object value) {Class<? extends Annotation> validatoeClass = caseWhenField.whenFieldValidatoeClass();if (!CollectionUtils.isEmpty(this.getCache(validatoeClass))) {ConstraintValidator<Annotation, Object> instance = getConstraintValidatorByValidatoeClass(validatoeClass, dependsOnField.getType().getTypeName());if (Objects.isNull(instance)){log.error("注解:{},未查询到验证注解类",validatoeClass.getTypeName());return false;}Map<String, Field> typeWithFiledList = getTypeWithFiledList(instance);Map<String, Object> pram = GSON.fromJson(caseWhenField.checkMap(), new TypeToken<Map<String, Object>>() {});pram.forEach((k, v) -> {Field field = typeWithFiledList.get(k);Optional.ofNullable(field).ifPresent(t -> {ReflectionUtils.makeAccessible(t);try {Class<?> type = field.getType();boolean basicType = ClassUtil.isBasicType(type);Object convert;if (basicType) {convert = Convert.convert(type, v);} else if (type.isAssignableFrom(Pattern.class)) {String regexType = String.valueOf(v);convert = getPatternByType(regexType);} else {convert = v;}ReflectionUtils.setField(t, instance, convert);} catch (Exception e) {log.error(e.getMessage(), e);}});});typeWithFiledList.clear();return instance.isValid(value, context);}return false;}@PostConstructpublic static void initRegexMaps() {StopWatch stopWatch = new StopWatch("initRegexMap");stopWatch.start();initMap();stopWatch.stop();log.info("自动注入,初始化枚举map成功,耗时:{}ms", stopWatch.getLastTaskTimeMillis());}private static void initMap() {String thisDef = PatternEnum.class.getPackage().getName();SCAN_PACKAGE.add(thisDef);String startupClass = System.getProperty("sun.javamand");Class<Object> objectClass = ClassUtil.loadClass(startupClass, false);SpringBootApplication annotation = objectClass.getAnnotation(SpringBootApplication.class);Set<String> collect = Arrays.stream(annotation.scanBasePackages()).filter(startupClass::startsWith).collect(Collectors.toSet());if (!CollectionUtils.isEmpty(collect)) {SCAN_PACKAGE.addAll(collect);}log.info("当前要扫描的包:{}", SCAN_PACKAGE);SCAN_PACKAGE.forEach(path -> {Set<Class<?>> classSet = ClassUtil.scanPackageBySuper(path, EnumInterface.class);if (CollectionUtils.isEmpty(classSet)) {log.info("包:{},未扫描到继承EnumInterface类的枚举", path);} else {addEnumToRegexMap(classSet);}});}/*** 外部添加正则方法*/public static Map<String, String> getRegexMap() {if (CollectionUtils.isEmpty(REGEX)) {initMap();}return REGEX;}/*** 通过传入的正则类型获取对应的正则表达式,如果未查询到则直接使用当前字符串为正则*/public Object getPatternByType(String type) {if (getRegexMap().containsKey(type)) {return Patternpile(getRegexMap().get(type));}return Patternpile(type);}
}
3、注解使用到的类
@Slf4j
public abstract class AbstractFactoryMy implements MyValidatorFactory {protected static final Map<Class<? extends Annotation>, List<? extends ConstraintValidatorDescriptor<? extends Annotation>>> CACHE = Maps.newConcurrentMap();/*** 正则*/protected static final Map<String, String> REGEX = Maps.newConcurrentMap();protected static final Set<String> SCAN_PACKAGE = new HashSet<>();public static final Gson GSON = new GsonBuilder().registerTypeAdapter(new TypeToken<Map<String, Object>>() {}.getType(), new DataTypeAdapter()).create();private static final ConstraintValidatorFactory DEFAULT_VALIDATOR_FACTORY = Validation.byDefaultProvider().configure().getDefaultConstraintValidatorFactory();/*** 添加正则枚举值到RegexMap中*/protected static void addEnumToRegexMap(Set<Class<?>> classSet) {classSet.stream().filter(ClassUtil::isEnum).forEach(t -> {try {Object[] enumConstants = t.getEnumConstants();Method getKey = t.getMethod("getKey");Method getRegex = t.getMethod("getRegex");for (Object enumConstant : enumConstants) {String key = t.getSimpleName() + getKey.invoke(enumConstant);String regex = String.valueOf(getRegex.invoke(enumConstant));log.info("key:{},regex:{}", key, regex);if (!REGEX.containsKey(key)) {REGEX.put(key, StringUtils.isEmpty(regex) ? "*" : regex);}}} catch (Exception e) {log.error("添加枚举{}失败:", t.getName(), e);}});}/*** 确定当前注解是否可以使用在字段上*/protected boolean checkFieldWithAnnotation(Class<? extends Annotation> validatoeClass) {ElementType[] elementTypes = validatoeClass.getAnnotation(Target.class).value();for (ElementType elementType : elementTypes) {if (elementType.equals(ElementType.FIELD)) {return true;}}return false;}/*** 从CACHE中获取对应注解类的ConstraintValidatorDescriptor*/protected List<? extends ConstraintValidatorDescriptor<? extends Annotation>> getCache(Class<? extends Annotation> aClass) {return CACHEputeIfAbsent(aClass, this::findByAnnotation);}/*** 根据注解class查询对应的ConstraintValidatorDescriptor*/@Overridepublic List<? extends ConstraintValidatorDescriptor<? extends Annotation>> findByAnnotation(Class<? extends Annotation> aClass) {Set<String> pathSet = new HashSet<>();pathSet.add(aClass.getTypeName());ConstraintHelper constraintHelper = ConstraintHelper.forBuiltinConstraints(pathSet);List<? extends ConstraintValidatorDescriptor<? extends Annotation>> allValidatorDescriptors = constraintHelper.getAllValidatorDescriptors(aClass);if (!CollectionUtils.isEmpty(allValidatorDescriptors)) {log.info("添加注解:{}对应的ConstraintValidatorDescriptor", aClass.getTypeName());}return allValidatorDescriptors;}/*** 获取ConstraintValidator对应的字段*/protected Map<String, Field> getTypeWithFiledList(ConstraintValidator<Annotation, Object> instance) {Map<String, Field> fieldMap = new HashMap<>(16);Class<?> tempClass = instance.getClass();while (tempClass != null) {Field[] declaredFields = tempClass.getDeclaredFields();if (!CollectionUtils.isEmpty(Arrays.asList(declaredFields))) {Arrays.asList(declaredFields).forEach(t -> fieldMap.put(t.getName(), t));}tempClass = tempClass.getSuperclass();}return fieldMap;}@Overridepublic ConstraintValidator<Annotation, Object> getConstraintValidatorByValidatoeClass(Class<? extends Annotation> validatoeClass, String typeName) {List<? extends ConstraintValidatorDescriptor<? extends Annotation>> cache = this.getCache(validatoeClass);Class<Object> objectClass = ClassUtil.loadClass(typeName);return cache.stream().filter(descriptor -> ((Class<?>) descriptor.getValidatedType()).isAssignableFrom(objectClass)).findFirst().map(constraintValidatorDescriptor ->(ConstraintValidator<Annotation, Object>) DEFAULT_VALIDATOR_FACTORY.getInstance(constraintValidatorDescriptor.getValidatorClass())).orElse(null);}
}
public interface MyValidatorFactory {/*** 根据注解class查询对应的ConstraintValidatorDescriptor** @param aClass 注解class类* @return 注解对应的ConstraintValidatorDescriptor集合*/List<? extends ConstraintValidatorDescriptor<? extends Annotation>> findByAnnotation(Class<? extends Annotation> aClass);/*** 根据校验注解获取对应的ConstraintValidator** @param validatoeClass 校验注解class* @param typeName 当前校验字段的类型 Field.getType().getTypeName()* @return 注解对应的实现校验类*/ConstraintValidator<Annotation, Object> getConstraintValidatorByValidatoeClass(Class<? extends Annotation> validatoeClass, String typeName);
}
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Null {}
4、默认注册到缓存中的正则规则
@Getter
public enum PatternEnum implements EnumInterface {/*** 邮箱*/EMAIL("1","^\\s*\\w+(?:\\.{0,1}[\\w-]+)*@[a-zA-Z0-9]+(?:[-.][a-zA-Z0-9]+)*\\.[a-zA-Z]+\\s*$"),/*** 身份证*/ID_CARD("2","^\\d{6}((((((19|20)\\d{2})(0[13-9]|1[012])(0[1-9]|[12]\\d|30))|(((19|20)\\d{2})(0[13578]|1[02])31)|((19|20)\\d{2})02(0[1-9]|1\\d|2[0-8])|((((19|20)([13579][26]|[2468][048]|0[48]))|(2000))0229))\\d{3})|((((\\d{2})(0[13-9]|1[012])(0[1-9]|[12]\\d|30))|((\\d{2})(0[13578]|1[02])31)|((\\d{2})02(0[1-9]|1\\d|2[0-8]))|(([13579][26]|[2468][048]|0[048])0229))\\d{2}))(\\d|X|x)$"),/*** 手机号*/PHONE("3",""),;/*** map Key*/private final String key;/*** map value 正则*/private final String regex;PatternEnum(String key, String regex) {this.key = key;this.regex = regex;}public int getThisOrd(){return this.ordinal();}
}
5、增加其他规则示例
@Getter
public enum RoleEnum implements EnumInterface {/*** 检校合作省院管理员*/PROVINCE_JXHZGLY("68", "22323"),;private String key;private String regex;RoleEnum(String key, String regex) {this.key = key;this.regex = regex;}
}
发布评论