緣由
在后臺寫接口的時候,經常會出現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;@Overridepublic void initialize(ValidEnumCode constraintAnnotation) {this.enumClass = constraintAnnotation.enumClass();this.filter = constraintAnnotation.filterEnumName();this.exclude = constraintAnnotation.excludeEnumName();}@Overridepublic 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) {