前一篇文章 《巧用Java枚举封装和自定义Function,给前端输出标准的字典》,我们实现了一些字典的标准操作,今天我们来封装一个自定义字典验证器。
创建字典验证注解
我们先提供一个和其他内置验证器一致的 @Dictionary
注解,这个注解需要传入一个实现了 IDictionary
接口的枚举类,然后进行字典校验。
java
/**
* <h1>标记进行字典校验</h1>
*
* @author Hamm.cn
* @apiNote 请注意, 请自行做非空验证, 字典必须实现 {@link IDictionary} 接口
*/
@Constraint(validatedBy = DictionaryAnnotationValidator.class)
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Dictionary {
/**
* <h3>错误信息</h3>
*/
String message() default "不允许的枚举字典值";
/**
* <h3>使用的枚举类</h3>
*
* @see IDictionary
*/
Class<? extends IDictionary> value();
/**
* <h3>验证组</h3>
*/
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
实现字典验证器
在上面的注解中,我们绑定了一个自定义的验证器 DictionaryAnnotationValidator
,我们可以通过继承 ConstraintValidator
类来实现:
java
/**
* <h1>枚举字典验证实现类</h1>
*
* @author Hamm.cn
*/
@Component
public class DictionaryAnnotationValidator implements ConstraintValidator<Dictionary, Integer> {
/**
* <h3>标记的枚举类</h3>
*/
private Class<? extends IDictionary> enumClazz = null;
/**
* <h3>验证</h3>
*
* @param value 验证的值
* @param context 验证器会话
* @return 验证结果
*/
@Contract("null, _ -> true")
@Override
public final boolean isValid(Integer value, ConstraintValidatorContext context) {
if (null == value) {
return true;
}
try {
DictionaryUtil.getDictionary(enumClazz, value);
} catch (Exception e) {
return false;
}
return true;
}
/**
* <h3>初始化</h3>
*
* @param dictionary 字典类
*/
@Contract(mutates = "this")
@Override
public final void initialize(@NotNull Dictionary dictionary) {
enumClazz = dictionary.value();
}
}
在上面的方法中,我们调用了上一篇文章中编写的 DictionaryUtil
工具类,通过传入枚举类和字典值,获取字典对象,如果获取失败,则自动抛出异常后交给异常控制器抛出给前端。
查询字典的方法
java
/**
* <h3>查字典</h3>
*
* @param enumClass 枚举字典类
* @param key 枚举字典值
* @param <D> [泛型] 字典类型
* @return 查到的字典
*/
public static <D extends IDictionary> @NotNull D getDictionary(@NotNull Class<D> enumClass, int key) {
return getDictionary(enumClass, IDictionary::getKey, key);
}
/**
* <h3>查字典</h3>
*
* @param enumClass 枚举字典类
* @param function 获取指定值的方法
* @param value 比较的值
* @param <D> [泛型] 字典类型
* @return 查到的字典
*/
public static <D extends IDictionary> @NotNull D getDictionary(
@NotNull Class<D> enumClass, Function<D, Object> function, Object value
) {
// 取出所有枚举类型
D[] objs = enumClass.getEnumConstants();
try {
for (D obj : objs) {
if (Objects.equals(function.apply(obj), value)) {
return obj;
}
}
} catch (Exception exception) {
log.error(exception.getMessage(), exception);
}
throw new ServiceException(
"传入的值(" + enumClass.getSimpleName() + "=" + value + ")不在字典可选范围内",
getDictionaryList(enumClass)
);
}
// 省略其他方法
如何使用
我们可以在需要校验的实体类上标记字典注解即可,像这样:
java
@Description("物料类型")
@Column(columnDefinition = "bigint UNSIGNED default 1 comment '物料类型'")
@Dictionary(value = MaterialType.class, groups = {WhenAdd.class, WhenUpdate.class})
private Integer materialType;
然后在控制器中绑定这个 group
即可:
java
@Description("添加")
@PostMapping("add")
public Json add(@RequestBody @Validated(WhenAdd.class) E source) {
// 验证过后的 source
}
实现效果
接下来我们看看在提交了错误字典枚举值的异常抛出前端显示如何?
json
{
"code": 500,
"message": "传入的值(FileCategory=3)不在字典可选范围内",
"data": [
{
"key": 0,
"label": "临时文件"
},
{
"key": 1,
"label": "普通文件"
},
{
"key": 1001,
"label": "头像"
}
]
}
嗯,很不错,美滋滋,一看就知道传错了字典值,而且列出了所有的可选字典值,这样前端就可以选择正确字典值提交了。
总结
通过封装一个字典验证器,让字典验证更简单,我们只需要在实体类上标记字典注解,然后在控制器中绑定对应的验证组即可。
完整代码可以参考开源项目:
又水了一篇,美滋滋。
Bye~