springBoot中自定义一个validation注解,实现指定枚举值校验

发布于:2025-05-07 ⋅ 阅读:(26) ⋅ 点赞:(0)

缘由

在后台写接口的时候,经常会出现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接口
在这里插入图片描述

常见的三种情况

  1. 必须是TransmissionType枚举类中的属性值
     /**
      * @see TransmissionType#getCode()
      */
     @ValidEnumCode(enumClass = TransmissionType.class)
     private Integer transmissionType;
    
  2. 必须是TransmissionType枚举类中的属性值,且名称为STRATEGYNEWS
     public R<List<LinkVO>> newArticle(@RequestParam("articleType")
                                       @ValidEnumCode(enumClass = ArticleType.class,
                                               filterEnumName = {"STRATEGY", "NEWS"}) Integer articleType) {
    
  3. 必须是TransmissionType枚举类中的属性值,且排除掉名称为STRATEGYNEWS
     public R<ArticlesVO> getInfoByArticleType(@RequestParam("articleType")
                                               @ValidEnumCode(enumClass = ArticleType.class,
                                                       excludeEnumName = {"STRATEGY", "NEWS"}) Integer articleType) {
    

网站公告

今日签到

点亮在社区的每一天
去签到