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

相关推荐
一只爱撸猫的程序猿21 分钟前
创建一个使用Spring AI配合Dify构建的代码生成工具实例
spring boot·aigc·ai编程
呼哧呼哧.31 分钟前
Java 8特性(一)
java·开发语言
Runing_WoNiu33 分钟前
Golang 与Java 单例模式、工厂模式比较
java·单例模式·golang
Armyyyyy丶1 小时前
Sentinel原理之责任链详解
java·sentinel·熔断限流
Code季风2 小时前
Redis 缓存:应对缓存雪崩、缓存击穿和缓存穿透
数据库·redis·分布式·后端·缓存·微服务·mybatis
xingkongvv122 小时前
C# 异步编程
java·服务器·开发语言·前端·javascript
●VON4 小时前
重生之我在暑假学习微服务第十一天《配置篇》+网关篇错误订正
java·学习·微服务·云原生·暑假
别来无恙1498 小时前
Spring Boot + ECharts 极简整合指南:从零实现动态数据可视化大屏
spring boot·信息可视化·echarts
刘火锅9 小时前
Bug 记录:SecureRandom.getInstanceStrong()导致验证码获取阻塞
spring boot·spring·spring cloud·bug
hrrrrb9 小时前
【Spring Boot 快速入门】八、登录认证(一)基础登录与认证校验
spring boot·后端