引言
兄弟们,马上过年了,你们放假了没?是否都已到家了呢,我提前祝兄弟们新年快乐!!
继上篇封装了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 注解,我们才能做下面的操作
二、使用
场景一 、字典值类型的校验
我们在库中定义了一个字典值,当调用保存接口的时候,我们需要对某个字段做校验,要求传过来的入参必须要在我们库中的字典值类别内
以下是定义的字典值,我就拿性别来做示例说明了
对应的类型有以下三种
测试一、校验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 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
注:因作者能力有限,如果各位看官有更好的意见, 还请指教一下
微信关注博主,有更多精彩内容哦,更新频率频繁,经常更新面试题目