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格式


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM