zoukankan      html  css  js  c++  java
  • Hibernate Validator实现统一表单验证(含自定义枚举验证)

    一般我们对前端传输的参数做校验时,可能都是以以下方式进行,如果再加上字段的长度、正则等校验的话就会显得代码很累赘。

    // 新增/修改通用参数非空校验
    if (StringUtil.isBlank(menu.getParentId())) {
      throw new LsException(ResultEnum.PARAM_MISSING_ERROR, "父级菜单ID不能为空!");
    }
    if (StringUtil.isBlank(menu.getMenuName())) {
      throw new LsException(ResultEnum.PARAM_MISSING_ERROR, "菜单名称不能为空!");
    }
    if (StringUtil.isBlank(menu.getMenuName())) {
      throw new LsException(ResultEnum.PARAM_MISSING_ERROR, "菜单类型不能为空!");
    }
    /**>>>>>>>>>>>>>>>>>>>>>>>>枚举类字段校验开始<<<<<<<<<<<<<<<<<<<<<<<<<**/
    // 校验菜单类型是否存在
    if (Objects.nonNull(model.getMenuType())) {
      MenuEnum.MenuType menuType = MenuEnum.MenuType.getByType(model.getMenuType());
      if (Objects.isNull(menuType)) {
        throw new LsException(ResultEnum.PARAM_CHECKED_ERROR, "此菜单类型不存在!");
      }
    }
    // 校验打开方式是否存在
    if (Objects.nonNull(model.getTarget())) {
      MenuEnum.Target target = MenuEnum.Target.getByTarget(model.getTarget());
      if (Objects.isNull(target)) {
        throw new LsException(ResultEnum.PARAM_CHECKED_ERROR, "此打开方式不存在!");
      }
    }
    // 校验菜单状态是否存在
    if (Objects.nonNull(model.getVisible())) {
      MenuEnum.Visible visible = MenuEnum.Visible.getByVisible(model.getVisible());
      if (Objects.isNull(visible)) {
        throw new LsException(ResultEnum.PARAM_CHECKED_ERROR, "此菜单状态不存在!");
      }
    }
    /**>>>>>>>>>>>>>>>>>>>>>>>>枚举类字段校验结束<<<<<<<<<<<<<<<<<<<<<<<<<**/

     改进方案,使用Hibernate Validator嵌入式注解处理器(概念可参考《深入理解Java虚拟机》——周志明第10.4节)进行表单验证:

    1、表单验证工具类ModelValidator

    /**
     * 软件版权:流沙~~
     * 修改日期   修改人员     修改说明
     * =========  ===========  =====================
     * 2019/9/30    liusha   新增
     * =========  ===========  =====================
     */
    package com.sand.common.util.validator;
    
    import com.sand.common.enums.CodeEnum;
    import com.sand.common.exception.BusinessException;
    import com.sand.common.util.lang3.StringUtil;
    import com.sand.common.util.spring.SpringUtil;
    import lombok.extern.slf4j.Slf4j;
    
    import javax.validation.ConstraintViolation;
    import javax.validation.Validator;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    
    /**
     * 功能说明:表单验证器
     * 开发人员:@author liusha
     * 开发日期:2019/9/30 13:57
     * 功能描述:表单校验,验证非空、长度、正则等等
     */
    @Slf4j
    public class ModelValidator {
      /**
       * 校验失败条数key值
       */
      public static final String CHECKED_FAIL_NUM = "checkedFailNum";
      /**
       * 校验失败原因key值
       */
      public static final String CHECKED_FAIL_MSG = "checkedFailMsg";
      /**
       * 校验通过的数据列表key值
       */
      public static final String CHECKED_ENTITY_LIST = "checkedEntityList";
    
      /**
       * 表单验证(对应实体类配置注解)
       *
       * @param entity 校验对象
       * @param <T>
       */
      public static <T extends Object> void checkModel(T entity) {
        checkModel(entity, null);
      }
    
      /**
       * 表单验证指定字段(对应实体类配置注解)
       *
       * @param entity 校验对象
       * @param <T>
       */
      public static <T extends Object> void checkModel(T entity, String fieldName) {
        try {
          Set<ConstraintViolation<T>> violationSet;
          if (StringUtil.isNotBlank(fieldName)) {
            violationSet = SpringUtil.getBean(Validator.class).validateProperty(entity, fieldName);
          } else {
            violationSet = SpringUtil.getBean(Validator.class).validate(entity);
          }
          if (violationSet.size() > 0) {
            String msg = violationSet.iterator().next().getMessage();
            if (StringUtil.isBlank(msg)) {
              msg = "请求参数有误";
            }
            throw new BusinessException(CodeEnum.PARAM_CHECKED_ERROR, msg);
          }
        } catch (Exception e) {
          log.error("表单验证出错,", e);
          String errorMsg = (e instanceof BusinessException) ? e.getMessage() : "表单验证出错";
          throw new BusinessException(CodeEnum.PARAM_CHECKED_ERROR, errorMsg);
        }
      }
    
      /**
       * 批量表单验证(对应实体类配置注解)
       *
       * @param entityList 校验对象列表
       * @param <T>
       * @return 校验结果
       */
      public static <T extends Object> Map<String, Object> checkModelList(List<T> entityList) {
        // 校验失败条数
        int checkedFailNum = 0;
        // 校验失败原因
        StringBuilder checkedFailMsg = new StringBuilder();
        // 校验通过的数据列表
        List<T> checkedEntityList = new ArrayList<>();
        for (int i = 0; i < entityList.size(); i++) {
          try {
            checkModel(entityList.get(i));
          } catch (Exception e) {
            checkedFailNum++;
            checkedFailMsg.append("第" + (i + 1) + "条数据:" + e.getMessage() + ";");
            continue;
          }
          checkedEntityList.add(entityList.get(i));
        }
        Map<String, Object> checkedMap = new HashMap<>();
        checkedMap.put(CHECKED_FAIL_NUM, checkedFailNum);
        checkedMap.put(CHECKED_FAIL_MSG, checkedFailMsg);
        checkedMap.put(CHECKED_ENTITY_LIST, checkedEntityList);
        return checkedMap;
      }
    }
    View Code

    2、自定义枚举验证

    1)、枚举注解@EnumValidAnnotation

    /**
     * 软件版权:流沙~~
     * 修改日期   修改人员     修改说明
     * =========  ===========  =====================
     * 2019/9/26    liusha   新增
     * =========  ===========  =====================
     */
    package com.sand.base.annotation;
    
    import com.sand.base.util.lang3.StringUtil;
    import com.sand.base.util.validator.EnumValidator;
    
    import javax.validation.Constraint;
    import javax.validation.Payload;
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 功能说明:验证枚举类
     * 开发人员:@author liusha
     * 开发日期:2019/9/26 13:46
     * 功能描述:对表单中存在枚举类型的字段进行校验
     */
    @Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = {EnumValidator.class})
    @Documented
    public @interface EnumValidAnnotation {
      /**
       * 提示消息
       *
       * @return
       */
      String message() default StringUtil.EMPTY;
    
      /**
       * 对应的枚举类
       *
       * @return
       */
      Class<?>[] target() default {};
    
      Class<?>[] groups() default {};
    
      Class<? extends Payload>[] payload() default {};
    }
    View Code

    2)、枚举验证器EnumValidator

    /**
     * 软件版权:流沙~~
     * 修改日期   修改人员     修改说明
     * =========  ===========  =====================
     * 2019/9/26    liusha   新增
     * =========  ===========  =====================
     */
    package com.sand.base.util.validator;
    
    import com.sand.base.annotation.EnumValidAnnotation;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    import java.lang.reflect.Field;
    import java.util.Objects;
    
    /**
     * 功能说明:枚举验证器
     * 开发人员:@author liusha
     * 开发日期:2019/9/26 13:51
     * 功能描述:自定义枚举验证器
     */
    public class EnumValidator implements ConstraintValidator<EnumValidAnnotation, String> {
      /**
       * 枚举类
       */
      Class<?>[] clzs;
    
      @Override
      public void initialize(EnumValidAnnotation constraintAnnotation) {
        clzs = constraintAnnotation.target();
      }
    
      @Override
      public boolean isValid(String value, ConstraintValidatorContext context) {
        if (clzs.length > 0) {
          try {
            for (Class<?> clz : clzs) {
              if (clz.isEnum()) {
                // 枚举类验证
                Object[] objs = clz.getEnumConstants();
                for (Object obj : objs) {
                  Class<?> enumClz = obj.getClass();
                  Field[] fields = enumClz.getDeclaredFields();
                  for (Field field : fields) {
                    // 访问私有成员属性开关
                    field.setAccessible(true);
                    EnumValidAnnotation enumValidAnnotation = field.getAnnotation(EnumValidAnnotation.class);
                    if (Objects.nonNull(enumValidAnnotation)) {
                      // 获取成员属性的值
                      Object enumValue = field.get(obj);
                      if (value.equals(enumValue.toString())) {
                        return true;
                      }
                    }
                  }
                }
              }
            }
          } catch (IllegalAccessException e) {
            e.printStackTrace();
          }
        } else {
          return true;
        }
        return false;
      }
    }
    View Code

    3)、枚举类中要转换的成员属性也需要添加@EnumValidAnnotation(主要是为了通用)

    /**
     * 软件版权:流沙~~
     * 修改日期   修改人员     修改说明
     * =========  ===========  =====================
     * 2019/8/28    liusha   新增
     * =========  ===========  =====================
     */
    package com.sand.sys.enums;
    
    import com.sand.base.annotation.EnumValidAnnotation;
    import lombok.AllArgsConstructor;
    import lombok.Getter;
    
    import java.util.Objects;
    
    /**
     * 功能说明:存放系统菜单枚举类
     * 开发人员:@author liusha
     * 开发日期:2019/8/28 19:48
     * 功能描述:使用@EnumValidAnnotation可用于表单校验,使用getBy*返回给客户端转译等
     */
    public final class MenuEnum {
      @Getter
      @AllArgsConstructor
      public enum MenuType {
        // 菜单类型
        M("M", "目录"),
        C("C", "菜单"),
        F("F", "按钮");
    
        @EnumValidAnnotation
        private final String type;
        private final String description;
    
        public static MenuType getByType(String type) {
          for (MenuType item : MenuType.values()) {
            if (Objects.equals(type, item.getType())) {
              return item;
            }
          }
          return null;
        }
      }
    
      @Getter
      @AllArgsConstructor
      public enum Target {
        // 打开方式
        ITEM("_item", "页签中打开"),
        BLANK("_blank", "新窗口打开"),
        CURRENT("_current", "当前窗口打开");
    
        @EnumValidAnnotation
        private final String target;
        private final String description;
    
        public static Target getByTarget(String target) {
          for (Target item : Target.values()) {
            if (Objects.equals(target, item.getTarget())) {
              return item;
            }
          }
          return null;
        }
      }
    
      @Getter
      @AllArgsConstructor
      public enum Visible {
        // 菜单状态
        SHOW("0", "显示"),
        HIDE("1", "隐藏");
    
        @EnumValidAnnotation
        private final String visible;
        private final String description;
    
        public static Visible getByVisible(String visible) {
          for (Visible item : Visible.values()) {
            if (Objects.equals(visible, item.getVisible())) {
              return item;
            }
          }
          return null;
        }
      }
    }
    View Code

    3、需要验证的实体类添加相应注解,@NotBlank、@Length以及自定义枚举注解@EnumValidAnnotation

    /**
     * 软件版权:流沙~~
     * 修改日期   修改人员     修改说明
     * =========  ===========  =====================
     * 2019/8/26    liusha   新增
     * =========  ===========  =====================
     */
    package com.sand.sys.entity;
    
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import com.sand.base.annotation.EnumValidAnnotation;
    import com.sand.base.constant.Constant;
    import com.sand.base.core.entity.BaseEntity;
    import com.sand.sys.enums.MenuEnum;
    import lombok.Data;
    import lombok.EqualsAndHashCode;
    import lombok.ToString;
    import lombok.experimental.Accessors;
    import org.hibernate.validator.constraints.Length;
    
    import javax.validation.constraints.NotBlank;
    
    /**
     * 功能说明:系统菜单
     * 开发人员:@author liusha
     * 开发日期:2019/8/26 13:38
     * 功能描述:系统菜单
     */
    @Data
    @Accessors(chain = true)
    @ToString(callSuper = true)
    @EqualsAndHashCode(callSuper = true)
    @TableName(Constant.TABLE_PREFIX_SYS + "menu")
    public class SysMenu extends BaseEntity {
      private static final long serialVersionUID = -2854114810573968874L;
      /**
       * 菜单ID
       */
      @TableId
      private String menuId;
      /**
       * 父菜单ID
       */
      @NotBlank(message = "[父级菜单ID]不能为空哟!")
      private String parentId;
      /**
       * 菜单名称
       */
      @NotBlank(message = "[菜单名称]不能为空哟!")
      @Length(max = 64, message = "[菜单名称]不能超过64个字符呢!")
      private String menuName;
      /**
       * 菜单类型(M目录 C菜单 F按钮)
       */
      @NotBlank(message = "[菜单类型]不能为空哟!")
      @EnumValidAnnotation(message = "[菜单类型]不存在!", target = MenuEnum.MenuType.class)
      private String menuType;
      /**
       * 菜单URL
       */
      @Length(max = 128, message = "[菜单URL]不能超过128个字符呢!")
      private String menuUrl;
      /**
       * 显示顺序
       */
      private int orderNum;
      /**
       * 打开方式(_item 页签中打开,_blank 新窗口打开,_current 当前窗口打开)
       */
      @EnumValidAnnotation(message = "[打开方式]不存在!", target = MenuEnum.Target.class)
      private String target;
      /**
       * 菜单状态(0显示 1隐藏)
       */
      @EnumValidAnnotation(message = "[菜单状态]不存在!", target = MenuEnum.Visible.class)
      private String visible;
      /**
       * 权限标识
       */
      @Length(max = 128, message = "[权限标识]不能超过128个字符呢!")
      private String purview;
      /**
       * 菜单图标
       */
      private String icon;
    
    }
    View Code

     4、如果需要使用表单验证,可以在对应的controller或者service上加上:

    // 表单注解验证,非空,长度,正则等校验
    ModelValidator.checkModel(model);

    列举一些常用的Hibernate注解标签:

    注解

    适用的数据类型

    说明

    @AssertFalse

    Boolean, boolean

    验证注解的元素值是false

    @AssertTrue

    Boolean, boolean

    验证注解的元素值是true

    @DecimalMax(value=x)

    BigDecimal, BigInteger, String, byte,short, int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number andCharSequence.

    验证注解的元素值小于等于@ DecimalMax指定的value值

    @DecimalMin(value=x)

    BigDecimal, BigInteger, String, byte,short, int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number andCharSequence.

    验证注解的元素值小于等于@ DecimalMin指定的value值

    @Digits(integer=整数位数, fraction=小数位数)

    BigDecimal, BigInteger, String, byte,short, int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number andCharSequence.

    验证注解的元素值的整数位数和小数位数上限

    @Future

    java.util.Date, java.util.Calendar; Additionally supported by HV, if theJoda Time date/time API is on the class path: any implementations ofReadablePartial andReadableInstant.

    验证注解的元素值(日期类型)比当前时间晚

    @Max(value=x)

    BigDecimal, BigInteger, byte, short,int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type ofCharSequence (the numeric value represented by the character sequence is evaluated), any sub-type of Number.

    验证注解的元素值小于等于@Max指定的value值

    @Min(value=x)

    BigDecimal, BigInteger, byte, short,int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of CharSequence (the numeric value represented by the char sequence is evaluated), any sub-type of Number.

    验证注解的元素值大于等于@Min指定的value值

    @NotNull

    Any type

    验证注解的元素值不是null

    @Null

    Any type

    验证注解的元素值是null

    @Past

    java.util.Date, java.util.Calendar; Additionally supported by HV, if theJoda Time date/time API is on the class path: any implementations ofReadablePartial andReadableInstant.

    验证注解的元素值(日期类型)比当前时间早

    @Pattern(regex=正则表达式, flag=)

    String. Additionally supported by HV: any sub-type of CharSequence.

    验证注解的元素值与指定的正则表达式匹配

    @Size(min=最小值, max=最大值)

    String, Collection, Map and arrays. Additionally supported by HV: any sub-type of CharSequence.

    验证注解的元素值的在min和max(包含)指定区间之内,如字符长度、集合大小

    @Valid

    Any non-primitive type(引用类型)

    验证关联的对象,如账户对象里有一个订单对象,指定验证订单对象

    @NotEmpty

    CharSequence,CollectionMap and Arrays

    验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)

    @Range(min=最小值, max=最大值)

    CharSequence, Collection, Map and Arrays,BigDecimal, BigInteger, CharSequence, byte, short, int, long and the respective wrappers of the primitive types

    验证注解的元素值在最小值和最大值之间

    @NotBlank

    CharSequence

    验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格

    @Length(min=下限, max=上限)

    CharSequence

    验证注解的元素值长度在min和max区间内

    @Email

    CharSequence

    验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

        千万不要试图去研究 研究了很久都整不明白的东西,或许是层次不到,境界未到,也或许是从未在实际的应用场景接触过,这种情况下去研究,只会事倍功半,徒劳一番罢了。能做的就是不断的沉淀知识,保持一颗积极向上的学习心态,相信终有一天所有的困难都会迎刃而解。
  • 相关阅读:
    Spring中的@AliasFor标签
    Redisson基本用法
    阿里开源的缓存框架JetCache
    动态页面技术(JSP)
    会话技术cookie&session
    HttpServletRequest
    HttpServletResponse
    request和response的中文乱码问题
    WEB简单的登录注册功能(分层)
    Servlet
  • 原文地址:https://www.cnblogs.com/54hsh/p/11614803.html
Copyright © 2011-2022 走看看