还在为字典值、枚举值校验烦恼吗,不妨试试这个

简介: 本文介绍了如何在Java中实现常量值校验的封装,主要包括两个方面:字典值类型的校验和枚举类型的校验。首先,作者提到在进行数据验证时,实体类字段需要添加`@Valid`注解。然后,对于字典值类型的校验,可以通过`@DictVaild`注解检查当前字段值是否在数据库中的字典值类别内,或者与预定义的枚举类中的值相匹配。在进行校验时,可以设置`dictType`参数为`DictType.CODE`或`DictType.LABEL`来分别验证代码值或标签值。

引言

这里我将常量值校验的封装,给兄弟们分享一波,主要有两个效果,

第一个是可校验当前字段的值与我们定义的枚举类中的值是否一致

第二个是可校验当前字段的值与我们库中的词典的值是否一致

老规矩,我们还是先看效果,再看封装

一、环境准备

先准备一个实体类

package com.dfec.server.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.dfec.framework.dict.core.annotation.DictVaild;
import com.dfec.framework.dict.core.constant.DefaultStatus;
import com.dfec.framework.dict.enums.DictType;
import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import org.checkerframework.checker.units.qual.Length;
import java.math.BigDecimal;
/**
 * 【请填写功能名称】对象 test
 *
 * @author trg
 * @date Fri Jan 19 14:14:08 CST 2024
 */
@TableName("test")
@Data
public class TestEntity {
    /**
     * 数字
     */
    @Schema(description = "数字")
    @ExcelProperty("数字")
    private BigDecimal num;
    /**
     * 性别
     */
    @Schema(description = "性别")
    @ExcelProperty("性别")
    private String sex;
    /**
     * 姓名
     */
    @Schema(description = "姓名")
    @NotNull(message = "姓名不能为空")
    @ExcelProperty("姓名")
    private String name;
    /**
     * 身高
     */
    @Schema(description = "身高")
    @ExcelProperty("身高")
    private String tHeight;
 
}

再准备一个save的接口

@Tag(name = "测试")
@RestController
@RequestMapping("module/test")
@RequiredArgsConstructor
public class TestController extends AbstractController {
    
    /**
     * 保存
     */
    @SysLog(title = "新增", businessType = BusinessType.INSERT)
    @PostMapping("/save")
    @Operation(summary = "新增")
    public AjaxResult save(@RequestBody @Valid TestEntity test) {  
        testService.save(test);
        return AjaxResult.ok();
    }
     
    
    }

注意:这里的接口实体类之前一定要加@Valid 注解,我们才能做下面的操作

二、使用

场景一 、字典值类型的校验

我们在库中定义了一个字典值,当调用保存接口的时候,我们需要对某个字段做校验,要求传过来的入参必须要在我们库中的字典值类别内

以下是定义的词典值,我就拿性别来做示例说明了

对应的类型有以下三种

测试一、校验code

我们先给字段加上@DictVaild注解,以下为实体字段示例

/**
 * 性别
 */
@Schema(description = "性别")
@ExcelProperty("性别")
@DictVaild(value ="sys_user_sex", message = "性别字典类型匹配错误")
private String sex;

我们使用apifox做接口调用,因为该字段对应的键值为0,1,2

我们传3试下

这个时候是校验了字典键值即我们常说的code的值,因为3不在词典值范围内,所以匹配不上。

来一个正常的示例:

传0试下,接口正常

接下来,我们对label进行校验

测试二、校验label

这种情况是应对入参中传过来的是label的值,而不是code,此时我们的入参也就变成了 男、女、未知三种情况了

修改注解入参,添加dictType = DictType.LABEL

/**
 * 性别
 */
@Schema(description = "性别")
@ExcelProperty("性别")
@DictVaild(value ="sys_user_sex", message = "性别字典类型匹配错误",dictType = DictType.LABEL)
private String sex;

同理,我们传一个其它试下

很明显以上校验不通过的

同理,我们传一个正常值男试下

此时校验通过

总结,通过以上对词典值的校验,我们写的枚举类实际上也是key,val形式的话,也是可以做校验,所以请看下面对枚举类的校验

场景二、枚举类型的校验

以下是我写的性别的枚举类,这种情况是假设性别我们没入库,只写了一个枚举类,当然我还是建议同一个词典值类别的要么入库,要么写成枚举类

public enum SexEnum implements Element<SexEnum> {
    MAN(0L,  "男"),
    WOMAN(1L,  "女"),
    OTHER(2L, "未知");
    /**
     * 字典项代码
     */
    private final Long code;
    /**
     * 字典项名称
     */
    @Getter
    private final String name;
    /**
     * 构造器
     *
     * @param code 代码
     * @param name 显示的名称
     */
    SexEnum(Long code, String name) {
        this.code = code;
        this.name = name;
    }
    /**
     * 根据code获取枚举常量
     *
     * @param code 代码
     * @return AuditStatus 常量枚举
     */
    public static SexEnum of(Long code) {
        for (SexEnum value : values()) {
            if (value.code.equals(code)) {
                return value;
            }
        }
        return null;
    }
    @Override
    public SexEnum[] elements() {
        return SexEnum.values();
    }
    @Override
    public Long getCode() {
        return code;
    }
    @Override
    public String getName() {
        return name;
    }
}

测试一、校验 code

我们继续修改注解@DictVaild

/**
 * 性别
 */
@Schema(description = "性别")
@ExcelProperty("性别")
@DictVaild(constant = SexEnum.class, message = "性别字典类型匹配错误")
private String sex;

我们继续传3试一下,校验不通过,因为3不在我们的枚举类指定的code类别内

来个正常的 2试下,测试通过

测试二、校验label

同理,我们对注解添加参数dictType = DictType.LABEL

/**
 * 性别
 */
@Schema(description = "性别")
@ExcelProperty("性别")
@DictVaild(constant = SexEnum.class, message = "性别字典类型匹配错误",dictType = DictType.LABEL)
private String sex;

我们传一个其它试下,校验不通过

我们传一个未知试下,则校验通过

三、封装过程

先来看下我们的这个注解

package com.dfec.framework.dict.core.annotation;
import com.dfec.framework.dict.core.constant.DefaultStatus;
import com.dfec.framework.dict.core.constant.Element;
import com.dfec.framework.dict.core.vaild.DictValidator;
import com.dfec.framework.dict.enums.DictType;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
/**
 * @author trg
 * @title: DataPermission
 * @description: 字典值类型校验,主要适用与入参校验,校验实体字段的值是不是和数据字典中配置的一致
 * @date 2023/7/3 14:23
 */
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = DictValidator.class)
@Documented
public @interface DictVaild {
    /**
     * 字典值类别的类型
     */
    String value() default "";
    /**
     * 枚举类
     */
    Class<? extends Element<?>> constant() default DefaultStatus.class;
    /**
     * 校验类别,用code校验还是label校验
     */
    DictType dictType() default DictType.CODE;
    /**
     * msg信息
     */
    String message() default "type mismatch";
    /**
     * 分组
     */
    Class<?>[] groups() default {};
    /**
     *
     */
    Class<? extends Payload>[] payload() default {};
}

然后再看一波处理注解的类

package com.dfec.framework.dict.core.vaild;
import com.dfec.common.utils.str.StringUtils;
import com.dfec.framework.dict.core.annotation.DictVaild;
import com.dfec.framework.dict.core.constant.DefaultStatus;
import com.dfec.framework.dict.core.constant.Element;
import com.dfec.framework.dict.core.util.DictFrameworkUtils;
import com.dfec.framework.dict.enums.DictType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
/**
 * DictValidator
 *
 * @author trg
 * @className DictValidator
 * @date 2024/2/5 10:55
 **/
public class DictValidator implements ConstraintValidator<DictVaild, String> {
    private static final Logger LOGGER = LoggerFactory.getLogger(DictValidator.class);
    private String dictType;
    private DictType vaildType;
    private List<Element<?>> list;
    @Override
    public void initialize(DictVaild constraintAnnotation) {
        // 获取注解中的值
        this.dictType = constraintAnnotation.value();
        this.vaildType = constraintAnnotation.dictType();
        // 获取注解中的值
        Class<? extends Element<?>> clazz = constraintAnnotation.constant();
        if (!clazz.equals(DefaultStatus.class)) {
            Method valuesMethod = null;
            try {
                valuesMethod = clazz.getMethod("values");
                Element<?>[] elements = (Element<?>[]) valuesMethod.invoke(clazz);
                list = Arrays.asList(elements);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                LOGGER.error("常量校验失败", e);
            }
        }
    }
    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        if (list == null) {
            if (vaildType.equals(DictType.CODE)) {
                return !StringUtils.isBlank(DictFrameworkUtils.getDictDataLabel(dictType, value));
            } else {
                return !StringUtils.isBlank(DictFrameworkUtils.parseDictDataValue(dictType, value));
            }
        }
        for (Element<?> element : list) {
            if (vaildType.equals(DictType.CODE)) {
                if(element.getCode().equals(Long.valueOf(value))){
                    return true;
                }
            } else {
                if(element.getName().equals(value)){
                    return true;
                }
            }
        }
        return false;
    }
}

兄弟们,这里提一嘴,此类主要是实现了javax.validation.ConstraintValidator

此类要是比较迷糊,给各位看个截图,你就一下子明白了

这个类是来处理@Max()注解的,他也是实现了ConstraintValidator接口,哈哈,我们只需要找出规律,照猫画虎就行了

另外一个就是我们得要求如果需要使用枚举类做校验的话,必须使用我们自定义的超类枚举,不然获取不到对应的值,无法去做校验的工作

package com.dfec.framework.dict.core.constant;
/**
 * @author trg
 * @title 枚举基类
 */
public interface Element<E> {
    /**
     * 获取所有的枚举常量值
     *
     * @return E[]
     * @author yxr
     * @date 2023/11/3 16:55
     */
    E[] elements();
    /**
     * 获取枚举常量的代码
     *
     * @return java.lang.Long
     * @author yxr
     * @date 2023/11/3 16:55
     */
    Long getCode();
    /**
     * 获取枚举常量的名称
     *
     * @return java.lang.Long
     * @author trg
     * @date 2023/11/3 16:55
     */
    String getName();
}

这里再给一个默认的实现

package com.dfec.framework.dict.core.constant;
import lombok.Getter;
/**
 * @author yxr
 * @title 资源审核状态
 * @date 2023-10-31 14:21:17
 */
public enum DefaultStatus implements Element<DefaultStatus> {
    SAVE(0L,  "待审核"),
    PASS(10L,  "审核通过"),
    UN_PASS(-10L, "审核不通过");
    // @formatter:on
    /**
     * 字典项代码
     */
    private final Long code;
    /**
     * 字典项名称
     */
    @Getter
    private final String name;
    /**
     * 构造器
     *
     * @param code 代码
     * @param name 显示的名称
     */
    DefaultStatus(Long code, String name) {
        this.code = code;
        this.name = name;
    }
    /**
     * 根据code获取枚举常量
     *
     * @param code 代码
     * @return AuditStatus 常量枚举
     */
    public static DefaultStatus of(Long code) {
        for (DefaultStatus value : values()) {
            if (value.code.equals(code)) {
                return value;
            }
        }
        return null;
    }
    @Override
    public DefaultStatus[] elements() {
        return DefaultStatus.values();
    }
    @Override
    public Long getCode() {
        return code;
    }
    @Override
    public String getName() {
        return name;
    }
}

基本到这里,我们的封装工作就完成了,是不是比较简单啊。

四、遇到的问题

1、@Vaild注解不生效

网上查阅说是springboot版本在2.3以上的时候,得加上一个依赖,我的版本是2.7+,实测通过

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>

2、枚举类的值获取不到

起初我是打算使用枚举类的基类Enum来搞定的,但是发现获取不到值,所以自定义了一个超类枚举类

3、查询字典值的

这个说是个问题,倒不如说是个建议吧,这里建议词典值从缓存中去获取,我是使用了芋道中定义的这个工具类DictFrameworkUtils.java

目录
相关文章
|
运维 Shell Python
【运维知识高级篇】超详细的Shell编程讲解2(变量切片+统计变量长度+字串删除+字串替换+七种方法进行数值运算+整数比较+多整数比较+文件判断+字符串比对+正则比对+配合三剑客的高阶用法)(一)
【运维知识高级篇】超详细的Shell编程讲解2(变量切片+统计变量长度+字串删除+字串替换+七种方法进行数值运算+整数比较+多整数比较+文件判断+字符串比对+正则比对+配合三剑客的高阶用法)
132 0
|
运维 Shell Perl
【运维知识高级篇】超详细的Shell编程讲解2(变量切片+统计变量长度+字串删除+字串替换+七种方法进行数值运算+整数比较+多整数比较+文件判断+字符串比对+正则比对+配合三剑客的高阶用法)(二)
【运维知识高级篇】超详细的Shell编程讲解2(变量切片+统计变量长度+字串删除+字串替换+七种方法进行数值运算+整数比较+多整数比较+文件判断+字符串比对+正则比对+配合三剑客的高阶用法)(二)
133 0
|
JSON 数据格式
【解决方案 十七】序列化反序列化时枚举值如何显示为字符串
【解决方案 十七】序列化反序列化时枚举值如何显示为字符串
152 0
|
数据采集 JSON 数据格式
一日一技:如何处理配置文件中的重复值?
一日一技:如何处理配置文件中的重复值?
129 0
还在写if?试试枚举策略
日常开发中或者代码优化过程中,一定会遇到不少的if语句,如果判断逻辑多了,会导致代码极其冗余,阅读性也会大打折扣,&quot;消灭&quot;if语句的方式有很多,也分不同的场景,本文将使用枚举策略的方式优化繁琐的if语句,你可以参考下文。
108 1
还在写if?试试枚举策略
|
Swift
Swift实用小册07:枚举的创建、使用、遍历、关联值、原始值
Swift实用小册07:枚举的创建、使用、遍历、关联值、原始值
425 0
Swift实用小册07:枚举的创建、使用、遍历、关联值、原始值
|
前端开发
前端工作总结120-解决key值不唯一的报错
前端工作总结120-解决key值不唯一的报错
149 0
【蓝桥杯】求1+2+3+...+n的值。(特别注意)
【蓝桥杯】求1+2+3+...+n的值。(特别注意)
【蓝桥杯】求1+2+3+...+n的值。(特别注意)
|
算法 Java
灵魂拷问:如何检查Java数组中是否包含某个值 ?(2)
灵魂拷问:如何检查Java数组中是否包含某个值 ?
154 0