缘由
在后台写接口的时候,经常会出现dto某个属性是映射到一个枚举的情况。有时候还会出现只能映射到枚举类中部分枚举值的情况。以前都是在service里面自行判断,很多地方代码冗余,所以就想着弄一个自定义的validation注解来实现。
例如下面某个DTO的属性transmissionType
,需要映射到TransmissionType
枚举类
/**
* @see TransmissionType#getCode()
*/
private Integer transmissionType;
代码
新建一个接口
新建这个接口是为了后面获取枚举的code
值,大部分时候,枚举的code
都是int
类型的,所以这里也只考虑了这种情况。如果是其他类型的,需要自行改造一下。比如另外建一个类型的接口,在EnumCodeValidator
类里面判断处理。不想再建接口的话,就在此接口里面加一个返回code类型的方法。然后根据类型来决定是调用getCode获取值还是getCodeStr获取。
/**
* 公共的接口,用于在validator里面获取枚举的code值,这里只考虑int类型的。
*/
public interface EnumCode {
int getCode();
}
新建一个注解
这个注解除了指定枚举类之外,还可以指定要包含或排除的枚举名称(是名称,在ConstraintValidator
是通过枚举
的name
方法拿名称的)
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 验证枚举值注解<br>
* 枚举的code需要是int类型
*/
@Constraint(validatedBy = EnumCodeValidator.class)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidEnumCode {
/**
* 对应的枚举类的Class
*/
Class<? extends Enum<?>> enumClass();
/**
* 过滤的枚举名称 表示需要哪些名称的
*/
String[] filterEnumName() default {};
/**
* 排除的名称名称
*/
String[] excludeEnumName() default {};
String message() default "值必须是已存在的枚举值";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
自定义验证器,实现ConstraintValidator接口
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* 具体的校验规则
*/
public class EnumCodeValidator implements ConstraintValidator<ValidEnumCode, Integer> {
private Class<? extends Enum<?>> enumClass;
private String[] filter;
private String[] exclude;
@Override
public void initialize(ValidEnumCode constraintAnnotation) {
this.enumClass = constraintAnnotation.enumClass();
this.filter = constraintAnnotation.filterEnumName();
this.exclude = constraintAnnotation.excludeEnumName();
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
Enum<?>[] enums = enumClass.getEnumConstants();
boolean filterEmpty = ArrayUtil.isEmpty(filter);
boolean excludeNonEmpty = ArrayUtil.isNotEmpty(exclude);
List<Integer> validCodes = Arrays.stream(enums)
.filter(e -> {
if (excludeNonEmpty) {
for (String excludeName : exclude) {
if (StrUtil.equals(excludeName, e.name())) {
return false;
}
}
}
if (filterEmpty) {
return true;
}
for (String filterName : filter) {
if (StrUtil.equals(filterName, e.name())) {
return true;
}
}
return false;
})
.mapToInt(e -> ((EnumCode) e).getCode())
.boxed()
.collect(Collectors.toList());
return validCodes.contains(value);
}
}
使用
枚举类先implements EnumCode
接口
常见的三种情况
- 必须是TransmissionType枚举类中的属性值
/** * @see TransmissionType#getCode() */ @ValidEnumCode(enumClass = TransmissionType.class) private Integer transmissionType;
- 必须是TransmissionType枚举类中的属性值,且名称为
STRATEGY
或NEWS
的public R<List<LinkVO>> newArticle(@RequestParam("articleType") @ValidEnumCode(enumClass = ArticleType.class, filterEnumName = {"STRATEGY", "NEWS"}) Integer articleType) {
- 必须是TransmissionType枚举类中的属性值,且排除掉名称为
STRATEGY
或NEWS
的public R<ArticlesVO> getInfoByArticleType(@RequestParam("articleType") @ValidEnumCode(enumClass = ArticleType.class, excludeEnumName = {"STRATEGY", "NEWS"}) Integer articleType) {