From 255aede037e79303d9cd5562e5b48f4547223d9d Mon Sep 17 00:00:00 2001 From: zengzefeng <986510453@qq.com> Date: Fri, 16 Apr 2021 17:53:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E7=89=88=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../validator/custom/CustomValidator.java | 135 +++++++++ .../custom/ValidateAnnotationEnum.java | 137 +++++++++ .../validator/custom/ValidateConfig.java | 37 +++ .../validator/custom/ValidatorProxy.java | 96 ++++++ .../validator/custom/handler/MaxHandler.java | 47 +++ .../validator/custom/handler/MinHandler.java | 47 +++ .../custom/handler/NotBlankHandler.java | 32 ++ .../custom/handler/NotEmptyHandler.java | 32 ++ .../custom/handler/NotNullHandler.java | 34 +++ .../handler/ValidateAnnotationHandler.java | 65 ++++ .../custom/handler/ValidateHandlerHelper.java | 282 ++++++++++++++++++ .../validator/custom/package-info.java | 24 ++ .../auth/vo/auth/SysAuthLoginReqVO.java | 11 +- 13 files changed, 974 insertions(+), 5 deletions(-) create mode 100644 src/main/java/cn/iocoder/dashboard/framework/validator/custom/CustomValidator.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/validator/custom/ValidateAnnotationEnum.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/validator/custom/ValidateConfig.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/validator/custom/ValidatorProxy.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/MaxHandler.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/MinHandler.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/NotBlankHandler.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/NotEmptyHandler.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/NotNullHandler.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/ValidateAnnotationHandler.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/ValidateHandlerHelper.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/validator/custom/package-info.java diff --git a/src/main/java/cn/iocoder/dashboard/framework/validator/custom/CustomValidator.java b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/CustomValidator.java new file mode 100644 index 0000000000..1f0cd29322 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/CustomValidator.java @@ -0,0 +1,135 @@ +package cn.iocoder.dashboard.framework.validator.custom; + +import cn.hutool.core.util.ArrayUtil; +import cn.iocoder.dashboard.framework.validator.custom.handler.ValidateHandlerHelper; +import com.alibaba.fastjson.JSON; +import io.swagger.annotations.ApiModelProperty; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.hibernate.validator.internal.util.Contracts; + +import javax.validation.ConstraintViolation; +import javax.validation.Validator; +import javax.validation.constraints.NotNull; +import javax.validation.executable.ExecutableValidator; +import javax.validation.metadata.BeanDescriptor; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; + +import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES; + +/** + * 自定义参数校验器 + *

+ * 愿景:实现自动生成验证失败的消息 + * 1、字段名根据{@link ApiModelProperty#value()}注解获取 + * 2、状态根据{@link NotNull}、{@link javax.validation.constraints.NotEmpty}等生成 + * 3、拼接返回消息 + *

+ * 如果实现后则以后只需要加{@code @NotNull @NotEmpty}注解,不需要再一个个写{@link NotNull#message()}的值 + *

+ * 2021.4.15 此次实现是类似于多数据源的实现,通过{@link ValidatorProxy}代理 + * 默认校验类({@link org.hibernate.validator.internal.engine.ValidatorImpl})和自定义校验类(this) + * · 该类只处理参数校验{@link this#validateParameters(Object, Method, Object[], Class[])} + * · 其他校验都交给默认实现 + * · 因此这个类只实现了参数校验的方法 + * + * @author zzf + * @date 2021/4/8 14:09 + */ +@Slf4j +public class CustomValidator implements Validator, ExecutableValidator { + + @Override + public Set> validate(T object, Class... groups) { + return validate(object, null, new Object[]{object}, groups); + } + + @SneakyThrows + @Override + public Set> validateProperty(T object, String propertyName, Class... groups) { + return null; + } + + @SneakyThrows + @Override + public Set> validateValue(Class beanType, String propertyName, Object value, Class... groups) { + return null; + } + + @Override + public BeanDescriptor getConstraintsForClass(Class clazz) { + return null; + } + + @SneakyThrows + @Override + public T unwrap(Class type) { + return null; + } + + @Override + public ExecutableValidator forExecutables() { + return this; + } + + @Override + public Set> validateParameters(T object, Method method, Object[] parameterValues, Class... groups) { + return validate(object, method, parameterValues, groups); + } + + @Override + public Set> validateReturnValue(T object, Method method, Object returnValue, Class... groups) { + return null; + } + + @SneakyThrows + @Override + public Set> validateConstructorParameters(Constructor constructor, Object[] parameterValues, Class... groups) { + return null; + } + + @SneakyThrows + @Override + public Set> validateConstructorReturnValue(Constructor constructor, T createdObject, Class... groups) { + return null; + } + + + private Set> validate(T object, Executable executable, Object[] parameterValues, Class... groups) { + try { + log.warn("Object: {}, Executable: {}, parameterValues: {}, groups: {}", + JSON.toJSONString(object), + JSON.toJSONString(executable), + JSON.toJSONString(parameterValues), + JSON.toJSONString(groups) + ); + } catch (Exception ignore) { + } + + Set> result = new HashSet<>(); + sanityCheckGroups(groups); + if (ArrayUtil.isNotEmpty(parameterValues)) { + for (Object param : parameterValues) { + Field[] fields = param.getClass().getDeclaredFields(); + for (Field field : fields) { + result.addAll(ValidateHandlerHelper.validate(field, param, object, field.getName())); + } + } + } + return result; + } + + private void sanityCheckGroups(Class[] groups) { + Contracts.assertNotNull(groups, MESSAGES.groupMustNotBeNull()); + for (Class clazz : groups) { + if (clazz == null) { + throw new IllegalArgumentException(MESSAGES.groupMustNotBeNull()); + } + } + } +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/validator/custom/ValidateAnnotationEnum.java b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/ValidateAnnotationEnum.java new file mode 100644 index 0000000000..c4688aad02 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/ValidateAnnotationEnum.java @@ -0,0 +1,137 @@ +package cn.iocoder.dashboard.framework.validator.custom; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.helpers.MessageFormatter; + +import javax.validation.constraints.*; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.util.Date; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * 校验注解枚举 + * + * @author zzf + * @date 2021/4/13 11:35 + */ +@Slf4j +@Getter +@AllArgsConstructor +public enum ValidateAnnotationEnum { + + NOT_NULL(NotNull.class, + "必传!", + (object, param) -> { + return ObjectUtil.isNotNull(object); + } + ), + + NOT_EMPTY(NotEmpty.class, + "内容不能为空!", + (object, param) -> { + return ObjectUtil.isNotEmpty(object); + } + ), + + NOT_BLANK(NotBlank.class, + "内容需包含有效字符!", + (object, param) -> { + if (object instanceof String) { + return StrUtil.isNotBlank(String.valueOf(object)); + } + return false; + } + ), + + MAX(true, + Max.class, + "不能超过{}", + (object, param) -> { + if (object instanceof Integer) { + return param > (Integer) object; + } + if (object instanceof BigDecimal | object instanceof Double | object instanceof Short) { + return new BigDecimal(String.valueOf(param)).compareTo(new BigDecimal(String.valueOf(object))) > 0; + } + if (object instanceof Long) { + return param > (Long) object; + } + if (object instanceof Date) { + return param > ((Date) object).getTime(); + } + return false; + } + ), + + MIN(true, + Min.class, + "不能小于{}", + (object, param) -> { + if (object instanceof Integer) { + return param < (Integer) object; + } + if (object instanceof BigDecimal | object instanceof Double | object instanceof Short) { + return new BigDecimal(String.valueOf(param)).compareTo(new BigDecimal(String.valueOf(object))) < 0; + } + if (object instanceof Long) { + return param < (Long) object; + } + if (object instanceof Date) { + return param < ((Date) object).getTime(); + } + return false; + }); + + private final boolean hadParam; + + private final Class annotation; + + private final String msg; + + private final BiFunction checkFunction; + + ValidateAnnotationEnum(Class annotation, String msg, BiFunction checkFunction) { + this.hadParam = false; + this.annotation = annotation; + this.msg = msg; + this.checkFunction = checkFunction; + } + + public String formatMsg(String fieldName, String... param) { + if (hadParam) { + return fieldName + MessageFormatter.arrayFormat(msg, param).getMessage(); + } + return fieldName + msg; + } + + public boolean checkParam(Field field, Object object, Long... param) throws IllegalAccessException { + if (hadParam && param != null && param.length == 1) { + return checkFunction.apply(field.get(object), param[0]); + } + if (!hadParam) { + return checkFunction.apply(field.get(object), null); + } + log.error("[参数校验失败!], Enum = {}, TargetClass = {}, FieldName = {}, param = {}", + this.toString(), + object.getClass().getSimpleName(), + field.getName(), + param); + return true; + } + + + public interface IfmFunction { + + Boolean apply(Field field, Object object, Function isValid); + + } + + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/validator/custom/ValidateConfig.java b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/ValidateConfig.java new file mode 100644 index 0000000000..09c96a8f96 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/ValidateConfig.java @@ -0,0 +1,37 @@ +package cn.iocoder.dashboard.framework.validator.custom; + +import org.springframework.boot.validation.MessageInterpolatorFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; + +import javax.validation.Validator; + +/** + * 参数校验器配置类 + * + * @author zzf + * @date 2021/4/13 14:13 + */ +@Configuration(proxyBeanMethods = false) +public class ValidateConfig { + + /** + * 配置一个校验器的代理类,自定义选择校验器进行校验 + *

+ * 这里必须加上@Primary注解,否则会跟{@link WebMvcConfigurationSupport#mvcValidator()}的冲突 + */ + @Primary + @Bean + public Validator primaryValidator() { + LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean(); + MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory(); + factoryBean.setMessageInterpolator(interpolatorFactory.getObject()); + CustomValidator customValidator = new CustomValidator(); + factoryBean.afterPropertiesSet(); + return new ValidatorProxy(factoryBean.getValidator(), customValidator); + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/validator/custom/ValidatorProxy.java b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/ValidatorProxy.java new file mode 100644 index 0000000000..8c761837d4 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/ValidatorProxy.java @@ -0,0 +1,96 @@ +package cn.iocoder.dashboard.framework.validator.custom; + +import io.swagger.annotations.ApiModelProperty; +import lombok.SneakyThrows; + +import javax.validation.ConstraintViolation; +import javax.validation.Validator; +import javax.validation.constraints.NotNull; +import javax.validation.executable.ExecutableValidator; +import javax.validation.metadata.BeanDescriptor; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.Set; + +/** + * 自定义参数校验器 + *

+ * ###未完成### + *

+ * 愿景:实现自动生成验证失败的消息 + * 1、字段名根据{@link ApiModelProperty#value()}注解获取 + * 2、状态根据{@link NotNull}、{@link javax.validation.constraints.NotEmpty}等生成 + * 3、拼接返回消息 + *

+ * 如果实现后则以后只需要加{@code @NotNull @NotEmpty}注解,不需要再一个个写{@link NotNull#message()}的值 + * + * @author zzf + * @date 2021/4/8 14:09 + */ +public class ValidatorProxy implements Validator, ExecutableValidator { + + private final Validator defaultValidator; + + private final Validator customValidator; + + public ValidatorProxy(Validator defaultValidator, + Validator customValidator) { + this.defaultValidator = defaultValidator; + this.customValidator = customValidator; + } + + @Override + public Set> validate(T object, Class... groups) { + return customValidator.validate(object, groups); + } + + @SneakyThrows + @Override + public Set> validateProperty(T object, String propertyName, Class... groups) { + return defaultValidator.validateProperty(object, propertyName, groups); + } + + @SneakyThrows + @Override + public Set> validateValue(Class beanType, String propertyName, Object value, Class... groups) { + return defaultValidator.validateValue(beanType, propertyName, value, groups); + } + + @Override + public BeanDescriptor getConstraintsForClass(Class clazz) { + return defaultValidator.getConstraintsForClass(clazz); + } + + @SneakyThrows + @Override + public T unwrap(Class type) { + return type.newInstance(); + } + + @Override + public ExecutableValidator forExecutables() { + return this; + } + + @Override + public Set> validateParameters(T object, Method method, Object[] parameterValues, Class... groups) { + return customValidator.forExecutables().validateParameters(object, method, parameterValues, groups); + } + + @Override + public Set> validateReturnValue(T object, Method method, Object returnValue, Class... groups) { + return defaultValidator.forExecutables().validateReturnValue(object, method, returnValue, groups); + } + + @SneakyThrows + @Override + public Set> validateConstructorParameters(Constructor constructor, Object[] parameterValues, Class... groups) { + return defaultValidator.forExecutables().validateConstructorParameters(constructor, parameterValues, groups); + } + + @SneakyThrows + @Override + public Set> validateConstructorReturnValue(Constructor constructor, T createdObject, Class... groups) { + return defaultValidator.forExecutables().validateConstructorReturnValue(constructor, createdObject, groups); + } +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/MaxHandler.java b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/MaxHandler.java new file mode 100644 index 0000000000..9c3571f878 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/MaxHandler.java @@ -0,0 +1,47 @@ +package cn.iocoder.dashboard.framework.validator.custom.handler; + +import org.slf4j.helpers.MessageFormatter; + +import javax.validation.constraints.Max; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 最大值校验处理类 + * + * @author zzf + * @date 2021/4/15 9:21 + */ +public class MaxHandler implements ValidateAnnotationHandler { + + @Override + public Class getAnnotation() { + return Max.class; + } + + @Override + public String validate(Max validateAnnotation, Object fieldValue) { + long value = validateAnnotation.value(); + boolean valid; + if (fieldValue instanceof Integer) { + valid = value > (Integer) fieldValue; + } else if (fieldValue instanceof BigDecimal | fieldValue instanceof Double | fieldValue instanceof Short) { + valid = new BigDecimal(String.valueOf(value)).compareTo(new BigDecimal(String.valueOf(fieldValue))) > 0; + } else if (fieldValue instanceof Long) { + valid = value > (Long) fieldValue; + } else if (fieldValue instanceof Date) { + valid = value > ((Date) fieldValue).getTime(); + } else { + valid = true; + } + if (!valid) { + MessageFormatter.format(getResultMsgWhenInvalid(), value).getMessage(); + } + return null; + } + + @Override + public String getResultMsgWhenInvalid() { + return "的值必须小于{}!"; + } +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/MinHandler.java b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/MinHandler.java new file mode 100644 index 0000000000..34e7d0decd --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/MinHandler.java @@ -0,0 +1,47 @@ +package cn.iocoder.dashboard.framework.validator.custom.handler; + +import org.slf4j.helpers.MessageFormatter; + +import javax.validation.constraints.Min; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 最小值校验处理类 + * + * @author zzf + * @date 2021/4/15 9:21 + */ +public class MinHandler implements ValidateAnnotationHandler { + + @Override + public Class getAnnotation() { + return Min.class; + } + + @Override + public String validate(Min validateAnnotation, Object fieldValue) { + long value = validateAnnotation.value(); + boolean valid; + if (fieldValue instanceof Integer) { + valid = value < (Integer) fieldValue; + } else if (fieldValue instanceof BigDecimal | fieldValue instanceof Double | fieldValue instanceof Short) { + valid = new BigDecimal(String.valueOf(value)).compareTo(new BigDecimal(String.valueOf(fieldValue))) < 0; + } else if (fieldValue instanceof Long) { + valid = value < (Long) fieldValue; + } else if (fieldValue instanceof Date) { + valid = value < ((Date) fieldValue).getTime(); + } else { + valid = true; + } + if (!valid) { + MessageFormatter.format(getResultMsgWhenInvalid(), value).getMessage(); + } + return null; + } + + @Override + public String getResultMsgWhenInvalid() { + return "的值必须小于{}!"; + } +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/NotBlankHandler.java b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/NotBlankHandler.java new file mode 100644 index 0000000000..3e29ccf039 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/NotBlankHandler.java @@ -0,0 +1,32 @@ +package cn.iocoder.dashboard.framework.validator.custom.handler; + +import cn.hutool.core.util.StrUtil; + +import javax.validation.constraints.NotBlank; + +/** + * 非空字符校验处理类 + * + * @author zzf + * @date 2021/4/15 9:21 + */ +public class NotBlankHandler implements ValidateAnnotationHandler { + + @Override + public Class getAnnotation() { + return NotBlank.class; + } + + @Override + public String validate(NotBlank validateAnnotation, Object fieldValue) { + if(StrUtil.isBlankIfStr(fieldValue)) { + return getResultMsgWhenInvalid(); + } + return null; + } + + @Override + public String getResultMsgWhenInvalid() { + return "格式错误或者没有有效字符!"; + } +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/NotEmptyHandler.java b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/NotEmptyHandler.java new file mode 100644 index 0000000000..bf979c6a40 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/NotEmptyHandler.java @@ -0,0 +1,32 @@ +package cn.iocoder.dashboard.framework.validator.custom.handler; + +import cn.hutool.core.util.ObjectUtil; + +import javax.validation.constraints.NotEmpty; + +/** + * 内容不可空校验处理类 + * + * @author zzf + * @date 2021/4/15 9:21 + */ +public class NotEmptyHandler implements ValidateAnnotationHandler { + + @Override + public Class getAnnotation() { + return NotEmpty.class; + } + + @Override + public String validate(NotEmpty validateAnnotation, Object fieldValue) { + if(ObjectUtil.isEmpty(fieldValue)) { + return getResultMsgWhenInvalid(); + } + return null; + } + + @Override + public String getResultMsgWhenInvalid() { + return "不能为空!!!!"; + } +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/NotNullHandler.java b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/NotNullHandler.java new file mode 100644 index 0000000000..c81a99f9ad --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/NotNullHandler.java @@ -0,0 +1,34 @@ +package cn.iocoder.dashboard.framework.validator.custom.handler; + +import cn.hutool.core.util.ObjectUtil; +import org.springframework.beans.factory.annotation.Value; + +import javax.validation.constraints.NotNull; + +/** + * 必传校验处理类 + * + * @author zzf + * @date 2021/4/15 9:21 + */ +public class NotNullHandler implements ValidateAnnotationHandler { + + + @Override + public Class getAnnotation() { + return NotNull.class; + } + + @Override + public String validate(NotNull validateAnnotation, Object fieldValue) { + if (ObjectUtil.isNull(fieldValue)) { + return getResultMsgWhenInvalid(); + } + return null; + } + + @Override + public String getResultMsgWhenInvalid() { + return "必传!"; + } +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/ValidateAnnotationHandler.java b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/ValidateAnnotationHandler.java new file mode 100644 index 0000000000..aba5875884 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/ValidateAnnotationHandler.java @@ -0,0 +1,65 @@ +package cn.iocoder.dashboard.framework.validator.custom.handler; + +import cn.hutool.core.util.ReflectUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; + +/** + * 校验注解处理抽象接口 + *

+ * ### 实现类要使用起来需要注册到{@link ValidateHandlerHelper}中 ### + * + * @author zzf + * @date 2021/4/15 9:22 + */ +public interface ValidateAnnotationHandler { + + /** + * 获取实现类的具体的注解类对象 + */ + Class getAnnotation(); + + + /** + * 判断参数字段是否存在该注解 + * + * @param field 字段对象 + * @return 是否存在该注解 + */ + default Boolean isAnnotationPresent(Field field) { + return field.isAnnotationPresent(getAnnotation()); + } + + /** + * 校验字段值,如果值合法返回null,不合法返回消息内容 + * + * @param field 需要校验的字段 + * @param targetObject 字段所在的对象 + * @return 值合法返回null,不合法返回消息内容 + */ + default String validate(Field field, Object targetObject) { + T annotation = field.getAnnotation(getAnnotation()); + return validate(annotation, ReflectUtil.getFieldValue(targetObject, field)); + } + + + /** + * 校验字段值,如果值合法返回null,不合法返回消息内容 + *

+ * 这里之所以没有直接返回boolean而是直接返回msg + * 是为了适配如{@link javax.validation.constraints.Max}{@link javax.validation.constraints.Min}等 + * 消息中需要获取注解属性值的注解 + * + * @param validateAnnotation 标记的注解 + * @param fieldValue 字段值 + * @return 如果值合法返回null,不合法返回消息内容 + */ + String validate(T validateAnnotation, Object fieldValue); + + /** + * @return 消息模板 + */ + String getResultMsgWhenInvalid(); + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/ValidateHandlerHelper.java b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/ValidateHandlerHelper.java new file mode 100644 index 0000000000..45f7bf898e --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/handler/ValidateHandlerHelper.java @@ -0,0 +1,282 @@ +package cn.iocoder.dashboard.framework.validator.custom.handler; + +import cn.hutool.core.collection.CollectionUtil; +import io.swagger.annotations.ApiModelProperty; +import lombok.Setter; +import org.hibernate.validator.internal.engine.path.PathImpl; + +import javax.validation.*; +import javax.validation.metadata.ConstraintDescriptor; +import javax.validation.metadata.ValidateUnwrappedValue; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.*; + +/** + * 校验处理辅助类 + * + * @author zzf + * @date 2021/4/15 9:49 + */ +public class ValidateHandlerHelper { + + private static final Set> handlerSet = new HashSet<>(10); + + /** + * 初始化注册校验处理类 + */ + static { + handlerSet.add(new NotNullHandler()); + handlerSet.add(new NotEmptyHandler()); + handlerSet.add(new NotBlankHandler()); + handlerSet.add(new MaxHandler()); + handlerSet.add(new MinHandler()); + } + + + /** + * 校验指定字段值是否合法 + *

+ * 这里针对集合类型字段做递归校验 + * 实现深层对象参数值校验 + * + * @param field 字段对象 + * @param paramObject 需要校验的字段所在的对象 + * @param rootBean 需要校验的方法所在的类对象 + * @param propertyPath 属性路径 + * @param 需要校验的方法所在的类 + * @return 校验结果集 + */ + public static Set> validate(Field field, + Object paramObject, + T rootBean, + String propertyPath) { + Set> result = new HashSet<>(); + + Object fieldValue; + try { + field.setAccessible(true); + fieldValue = field.get(paramObject); + } catch (IllegalAccessException e) { + return result; + } + if (fieldValue instanceof Collection) { + Collection collectionFieldValue = (Collection) fieldValue; + if (CollectionUtil.isNotEmpty(collectionFieldValue)) { + for (Object collectionElement : collectionFieldValue) { + Field[] fields = collectionElement.getClass().getDeclaredFields(); + for (Field collectionFieldElementField : fields) { + result.addAll( + validate( + collectionFieldElementField, + collectionElement, + rootBean, + propertyPath + "." + collectionFieldElementField.getName() + ) + ); + } + } + } + } + + CustomConstraintViolation violation = validateField(field, paramObject, rootBean, propertyPath, fieldValue); + if (violation != null) { + result.add(violation); + } + return result; + + } + + + /** + * 校验字段值,如果值合法返回null,不合法返回消息内容 + * + * @param field 需要校验的字段 + * @param targetObject 字段所在的对象 + * @return 值合法返回null,不合法返回消息内容 + */ + private static CustomConstraintViolation validateField(Field field, + Object targetObject, + T rootBean, + String propertyPath, + Object fieldValue) { + Optional> handlerOptional = + handlerSet.stream().filter(s -> s.isAnnotationPresent(field)).findFirst(); + if (handlerOptional.isPresent()) { + String validate = handlerOptional.get().validate(field, targetObject); + if (validate != null) { + String fieldComment; + if (field.isAnnotationPresent(ApiModelProperty.class)) { + ApiModelProperty annotation = field.getAnnotation(ApiModelProperty.class); + fieldComment = annotation.value(); + } else { + fieldComment = field.getName(); + } + String msg = fieldComment + handlerOptional.get().getResultMsgWhenInvalid(); + CustomConstraintDescriptor customConstraintDescriptor = + new CustomConstraintDescriptor<>(field.getAnnotation(handlerOptional.get().getAnnotation())); + return CustomConstraintViolation.of(rootBean, msg, propertyPath, fieldValue, customConstraintDescriptor); + } + } + return null; + } + + + /** + * 校验不通过的返回值对象 + * + * @param 需要校验的方法所在的类 + */ + public static class CustomConstraintViolation implements ConstraintViolation { + + public static CustomConstraintViolation of(T rootBean, + String msg, + String path, + Object invalidValue, + ConstraintDescriptor constraintDescriptor) { + CustomConstraintViolation constraintViolation = new CustomConstraintViolation<>(); + constraintViolation.setMessage(msg); + constraintViolation.setRootBean(rootBean); + constraintViolation.setPropertyPath(PathImpl.createPathFromString(path)); + constraintViolation.setInvalidValue(invalidValue); + constraintViolation.setConstraintDescriptor(constraintDescriptor); + return constraintViolation; + } + + @Setter + private String message; + + @Setter + private T rootBean; + + @Setter + private Path propertyPath; + + @Setter + private Object invalidValue; + + @Setter + private ConstraintDescriptor constraintDescriptor; + + + @Override + public String getMessage() { + return message; + } + + @Override + public String getMessageTemplate() { + return null; + } + + @Override + public T getRootBean() { + return rootBean; + } + + @Override + public Class getRootBeanClass() { + return null; + } + + @Override + public Object getLeafBean() { + return null; + } + + @Override + public Object[] getExecutableParameters() { + return new Object[0]; + } + + @Override + public Object getExecutableReturnValue() { + return null; + } + + @Override + public Path getPropertyPath() { + return propertyPath; + } + + @Override + public Object getInvalidValue() { + return invalidValue; + } + + @Override + public ConstraintDescriptor getConstraintDescriptor() { + return constraintDescriptor; + } + + @Override + public U unwrap(Class type) { + return null; + } + } + + public static class CustomConstraintDescriptor implements ConstraintDescriptor { + + private final R annotation; + + public CustomConstraintDescriptor(R annotation) { + this.annotation = annotation; + } + + @Override + public R getAnnotation() { + return annotation; + } + + @Override + public String getMessageTemplate() { + return null; + } + + @Override + public Set> getGroups() { + return null; + } + + @Override + public Set> getPayload() { + return null; + } + + @Override + public ConstraintTarget getValidationAppliesTo() { + return null; + } + + @Override + public List>> getConstraintValidatorClasses() { + return null; + } + + @Override + public Map getAttributes() { + return new HashMap<>(); + } + + @Override + public Set> getComposingConstraints() { + return null; + } + + @Override + public boolean isReportAsSingleViolation() { + return false; + } + + @Override + public ValidateUnwrappedValue getValueUnwrapping() { + return null; + } + + @Override + public U unwrap(Class type) { + return null; + } + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/validator/custom/package-info.java b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/package-info.java new file mode 100644 index 0000000000..e75043cfd3 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/validator/custom/package-info.java @@ -0,0 +1,24 @@ +package cn.iocoder.dashboard.framework.validator.custom; + +/** + * 参数校验实现 + *

+ * 功能说明:https://blog.csdn.net/qq_38688267/article/details/115720412 + *

+ * 环境搭建: + * 1、实现{@link cn.iocoder.dashboard.framework.validator.custom.handler.ValidateAnnotationHandler} + * 2、将实现类注册到{@link cn.iocoder.dashboard.framework.validator.custom.handler.ValidateHandlerHelper}中 + * 3、实现全局异常拦截{@link javax.validation.ConstraintViolationException}异常 + * + * 使用步骤: + * 1、给存在需要校验方法的Controller加上{@link javax.validation.Validator} 注解 + * 2、给需要校验的方法的参数加上 {@link javax.validation.Valid} 注解 + * 3、给需要校验的参数属性加上想要校验的注解, + * 如{@link javax.validation.constraints.NotNull} {@link javax.validation.constraints.NotEmpty}等 + *

+ * DEMO: + * {@link cn.iocoder.dashboard.modules.system.controller.auth.SysAuthController#login(cn.iocoder.dashboard.modules.system.controller.auth.vo.auth.SysAuthLoginReqVO)} + *

+ * 亮点: + * 校验过程中有实现对集合类型对象的递归校验,实现深层校验 + */ \ No newline at end of file diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/vo/auth/SysAuthLoginReqVO.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/vo/auth/SysAuthLoginReqVO.java index e3842acd71..3737e488d3 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/vo/auth/SysAuthLoginReqVO.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/vo/auth/SysAuthLoginReqVO.java @@ -10,31 +10,32 @@ import org.hibernate.validator.constraints.Length; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.Pattern; +import java.io.Serializable; @ApiModel("账号密码登陆 Request VO") @Data @NoArgsConstructor @AllArgsConstructor @Builder -public class SysAuthLoginReqVO { +public class SysAuthLoginReqVO implements Serializable { @ApiModelProperty(value = "账号", required = true, example = "yudaoyuanma") - @NotEmpty(message = "登陆账号不能为空") + @NotEmpty @Length(min = 4, max = 16, message = "账号长度为 4-16 位") @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母") private String username; @ApiModelProperty(value = "密码", required = true, example = "buzhidao") - @NotEmpty(message = "密码不能为空") + @NotEmpty @Length(min = 4, max = 16, message = "密码长度为 4-16 位") private String password; @ApiModelProperty(value = "验证码", required = true, example = "1024") - @NotEmpty(message = "验证码不能为空") + @NotEmpty private String code; @ApiModelProperty(value = "验证码的唯一标识", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62") - @NotEmpty(message = "唯一标识不能为空") + @NotEmpty private String uuid; }