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 最大为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 值不能为空
使用案列
@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;
@Override
public void initialize(CaseWhenField caseWhenField) {
this.caseWhenField = caseWhenField;
}
@Override
public 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;
}
@PostConstruct
public 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.java.command");
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 Pattern.compile(getRegexMap().get(type));
}
return Pattern.compile(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 CACHE.computeIfAbsent(aClass, this::findByAnnotation);
}
/**
* 根据注解class查询对应的ConstraintValidatorDescriptor
*/
@Override
public 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;
}
@Override
public 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;
}
}