还在为字典、枚举校验烦恼?不妨试试这个....

267 阅读7分钟

引言

兄弟们,马上过年了,你们放假了没?是否都已到家了呢,我提前祝兄弟们新年快乐!!

继上篇封装了excel封装,掘金上点赞和收藏量都过百了,看来兄弟们还是喜欢封装,这里我将常量值、字典值通过一个注解进行校验的封装,也给兄弟们分享一波,主要有两个效果,

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

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

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

一、环境准备

先准备一个实体类

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 注解,我们才能做下面的操作

二、使用

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

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

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

image.png 对应的类型有以下三种

image.png

测试一、校验code

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

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

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

我们传3试下

image.png

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

来一个正常的示例:

传0试下,接口正常

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

测试二、校验label

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

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

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

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

image.png

很明显以上校验不通过的

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

image.png

此时校验通过

总结,通过以上对词典值的校验,我们写的枚举类如果要是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类别内

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

image.png

测试二、校验label

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

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

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

image.png

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

image.png

三、封装过程

先来看下我们的这个注解

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接口,哈哈,我们只需要找出规律,照猫画虎就行了

image.png

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

package com.dfec.framework.dict.core.constant;

/**
 * @author trg
 * @title 枚举基类
 */
public interface Element<E> {

    /**
     * 获取所有的枚举常量值
     *
     * @return E[]
     * @author trg
     * @date 2023/11/3 16:55
     */
    E[] elements();

    /**
     * 获取枚举常量的代码
     *
     * @return java.lang.Long
     * @author trg
     * @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 trg
 * @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

注:因作者能力有限,如果各位看官有更好的意见, 还请指教一下

微信关注博主,有更多精彩内容哦,更新频率频繁,经常更新面试题目

image.png