封装一个优雅的自定义的字典验证器,让API字典参数验证更湿滑

前一篇文章 《巧用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": "头像"
		}
	]
}

嗯,很不错,美滋滋,一看就知道传错了字典值,而且列出了所有的可选字典值,这样前端就可以选择正确字典值提交了。

总结

通过封装一个字典验证器,让字典验证更简单,我们只需要在实体类上标记字典注解,然后在控制器中绑定对应的验证组即可。

完整代码可以参考开源项目:

github.com/HammCn/AirP...

又水了一篇,美滋滋。

Bye~

相关推荐
無限進步D4 小时前
Java 运行原理
java·开发语言·入门
難釋懷4 小时前
安装Canal
java
是苏浙4 小时前
JDK17新增特性
java·开发语言
不光头强4 小时前
spring cloud知识总结
后端·spring·spring cloud
GetcharZp7 小时前
告别 Python 依赖!用 LangChainGo 打造高性能大模型应用,Go 程序员必看!
后端
阿里加多7 小时前
第 4 章:Go 线程模型——GMP 深度解析
java·开发语言·后端·golang
likerhood7 小时前
java中`==`和`.equals()`区别
java·开发语言·python
小小李程序员8 小时前
Langchain4j工具调用获取不到ThreadLocal
java·后端·ai