引言
这里我将常量值校验的封装,给兄弟们分享一波,主要有两个效果,
第一个是可校验当前字段的值与我们定义的枚举类中的值是否一致
第二个是可校验当前字段的值与我们库中的词典的值是否一致
老规矩,我们还是先看效果,再看封装
一、环境准备
先准备一个实体类
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