一般我们对前端传输的参数做校验时,可能都是以以下方式进行,如果再加上字段的长度、正则等校验的话就会显得代码很累赘。
// 新增/修改通用参数非空校验
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 |
| 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0) |
@Range(min=最小值, max=最大值) |
| 验证注解的元素值在最小值和最大值之间 |
@NotBlank |
| 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格 |
@Length(min=下限, max=上限) |
| 验证注解的元素值长度在min和max区间内 |
| 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式 |
千万不要试图去研究 研究了很久都整不明白的东西,或许是层次不到,境界未到,也或许是从未在实际的应用场景接触过,这种情况下去研究,只会事倍功半,徒劳一番罢了。能做的就是不断的沉淀知识,保持一颗积极向上的学习心态,相信终有一天所有的困难都会迎刃而解。