封装一个优雅的自定义的字典验证器,让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~

相关推荐
葫芦和十三1 小时前
图解 MongoDB 21|选举与 failover:Primary 是怎么选出来的
后端·mongodb·agent
GetcharZp2 小时前
26k Star 开源内网穿透神器 NetBird,一分钟实现全球设备互联!
后端
考虑考虑2 小时前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯3 小时前
GoF设计模式——中介者模式
java·后端·spring·设计模式
lizhongxuan5 小时前
多Agent之间的区别
后端
青石路7 小时前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java
杨充7 小时前
1.面向对象设计思想
后端
IT_陈寒7 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
systemPro8 小时前
2.6亿条设备数据,历史查询从超时到50ms,我做了什么
后端
要阿尔卑斯吗8 小时前
提示词优化启示:为什么“按顺序输出“比“关键度评分“更有效
后端