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

相关推荐
returnShitBoy38 分钟前
Go语言中的垃圾回收是如何工作的?
java·jvm·golang
Asthenia041238 分钟前
详细解析Canal如何解析MySQL Binlog+Json格式的细节
后端
有什么东东1 小时前
山东大学软件学院创新项目实训开发日志(9)之测试前后端连接
java
zhangpeng4555479401 小时前
用Java写一个MVCC例子
java·开发语言
续亮~1 小时前
ANP协议深度解析:智能体网络协议的演进与革新
网络·后端·网络协议·ai·ai编程
谦行1 小时前
前端视角 Java Web 入门手册 5.1:真实世界 Web 开发——初识 Spring Boot
java·后端
自在如风。1 小时前
Java 设计模式:策略模式详解
java·设计模式·策略模式
!!!5251 小时前
Spring Boot 整合 MongoDB:分页查询详解 (新手友好)
spring boot·后端·mongodb
普通网友1 小时前
如何在CentOS部署青龙面板并实现无公网IP远程访问本地面板
开发语言·后端·golang
小杨4042 小时前
springboot框架项目实践应用十八(nacos高级特性)
spring boot·后端·spring cloud