SpringBoot项目自定义校验枚举注解

1. 使用场景

某接口的入参某属性,只希望为系统中某枚举的中定义过的值。


例如:用户类型枚举中定义 10-普通用户 20-管理员 30-游客 40-中级用户 50-高级用户
希望某接口的入参 userType 只能填入这几种type


支持扩展场景:

  1. 只允许填入枚举中定义过的某些值
  2. 不允许填入某些值

2. 技术实现

2.1 实现思路

使用Hibernate Validator校验工具,自定义校验注解及其校验逻辑。


Hibernate Validator官方文档:
https://docs.jboss.org/hibernate/validator/7.0/reference/en-US/html_single/#validator-gettingstarted

2.2 代码实现

2.2.1 引入依赖

<!--springboot2.3.3版本后 参数校验需加上 begin-->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-validation</artifactId>
 </dependency>
 <!--springboot2.3.3版本后 参数校验需加上 end-->

2.2.2 自定义校验注解

/**
 * 校验枚举类值注解
 *
 * @author Gangbb
 * @date 2021/11/6
 **/
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RUNTIME)
@Repeatable(VerifyEnum.List.class)
@Constraint(validatedBy = {EnumValidator.class})
public @interface VerifyEnum {

    /**
     * 枚举类型
     */
    Class<? extends Enum<?>> enumClass();


    /**
     * 枚举中用于校验的属性值
     **/
    String keyColumn();

    /**
     * 只允许填的枚举code值(填了非枚举code值会报错)
     */
    String[] allowedValues() default { };

    /**
     * 不允许填的枚举code值
     */
    String[] notAllowedValues() default { };

    /**
     * 错误消息
     **/
    String message() default "";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default {};

    @Target({ElementType.PARAMETER, ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface List {
        VerifyEnum[] value();
    }
}

2.2.3 校验工具类

/**
 * hibernate Validator校验工具类
 *
 * @author Gangbb
 * @date 2022/1/29
 **/
public class ValidatorUtils {
    /**
     * 验证器工程
     */
    private static ValidatorFactory factory;
    /**
     * 对象验证器
     */
    public static Validator validator;
    /**
     * 方法验证器
     */
    public static ExecutableValidator executableValidator;

    static {
        initValidator();
        clean();
    }

    /**
     * @Author Gangbb
     * @Description 初始化ValidatorFactory和Validator
     * @Date 2021/9/21
     **/
    public static void initValidator() {
        factory = Validation.byProvider(HibernateValidator.class)
                .configure()
                .failFast(true)
                .buildValidatorFactory();
        validator = factory.getValidator();
    }

    /**
     * @Author Gangbb
     * @Description 初始化ValidatorFactory和ExecutableValidator
     * @Date 2021/9/21
     **/
    public static void initExecutableValidator() {
        factory = Validation.buildDefaultValidatorFactory();
        executableValidator = factory.getValidator().forExecutables();
    }

    /**
     * @Author Gangbb
     * @Description 关闭ValidatorFactory工厂
     * @Date 2021/9/21
     **/
    public static void clean() {
        factory.close();
    }

    /**
     * @Author Gangbb
     * @Description 对类中的某方法参数校验
     * @Date 2021/9/21
     **/
    public static<T> Set<ConstraintViolation<T>> validMethod(T t, Method method, Object[] parameterValues){
        return executableValidator.validateParameters(t, method, parameterValues);
    }

    /**
     * 校验对象
     *
     * @Param [object:待检验对象, groups:对象分组]
     * @return void
     * @Author Gangbb
     * @Date 2021/10/25
     **/
    public static void validateObject(Object object, Class<?>... groups) {
        Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
        if(CollectionUtil.isNotEmpty(constraintViolations)){
            String errorMsg = StrUtil.format("对象{}校验异常:{}", object.getClass().getSimpleName(), getErrorMsg(constraintViolations));
            throw new ApiException(ResultEnum.PARAMETER_VERIFICATION_FAIL.getCode(), errorMsg);
        }

    }

    /**
     * 校验对象列表
     *
     * @param objectList 待校验对象列表
     * @param groups 校验分组
     * @date 2022/1/8
     **/
    public static void validateObjectList(List<Object> objectList, Class<?>... groups) {
        for (Object o : objectList) {
            validateObject(o, groups);
        }
    }

    /**
     * 校验填入值是否合法
     * 目前用于:DictValidator、EnumValidator
     *
     * @param allowedValues 允许填入值数组(在codeValues再)
     * @param notAllowedValues 不允许填入值数组
     * @param value 当前填入值须校验的值
     * @return String
     * @author Gangbb
     * @date 2022/01/29
     **/
    public static String validateValues(String[]  allowedValues, String[] notAllowedValues, Object value) {
        // notAllowedValues存在情况
        if (notAllowedValues != null && notAllowedValues.length > 0) {
            List<String> notAllowedList = Arrays.asList(notAllowedValues);
            if (notAllowedList.contains(String.valueOf(value))) {
                return StrUtil.format("不能填写以下值{}", notAllowedList);
            }
        }

        // allowedValues存在情况
        if (allowedValues != null && allowedValues.length > 0) {
            List<String> allowedList = Arrays.asList(allowedValues);
            if (!allowedList.contains(String.valueOf(value))) {
                return StrUtil.format("可填值只能为{}", Arrays.toString(allowedValues));
            }
        }
        return "";
    }

    /**
     * 校验填入值是否合法
     * 目前用于:DictValidator、EnumValidator
     *
     * @param allowedValues 允许填入值数组(在codeValues再)
     * @param notAllowedValues 不允许填入值数组
     * @param value 当前填入值须校验的值
     * @param codeValues 默认可填值
     * @return String
     * @author Gangbb
     * @date 2021/11/18
     **/
    public static String validateValues(String[]  allowedValues, String[] notAllowedValues,
                                        Object value, List<Object> codeValues) {
        // notAllowedValues存在情况
        if(notAllowedValues != null && notAllowedValues.length > 0){
            List<String> notAllowedList = Arrays.asList(notAllowedValues);
            codeValues.removeAll(notAllowedList);
            if(notAllowedList.contains(String.valueOf(value))){
                return StrUtil.format("不能填写以下值{}", notAllowedList);
            }
        }

        // allowedValues存在情况
        if(allowedValues != null && allowedValues.length > 0){
            List<String> allowedList = Arrays.asList(allowedValues);
            // 将codeValues统一转成String
            List<String> stringCodeValues = codeValues.stream().map(String::valueOf).collect(Collectors.toList());
            if(!stringCodeValues.containsAll(allowedList)){
                // @VerifyEnum填入allowedValues存在非枚举code值
                throw new RuntimeException("填入allowedValues存在非允许值");
            }else{
                if(allowedList.contains(String.valueOf(value))){
                    return "";
                }else{
                    return StrUtil.format("可填值只能为{}", Arrays.toString(allowedValues));
                }
            }
        }

        // 校验字段值是否是字典允许数据
        if(codeValues.contains(value)){
            return "";
        }else{
            // 重置错误提示
            return StrUtil.format("可填值只能为{}", codeValues);
        }
    }

    /**
     * 获取校验错误消息
     * @param c
     * @return
     */
    public static String getErrorMsg(Set<ConstraintViolation<Object>> c){
        StringBuffer msg = new StringBuffer();
        if (CollectionUtil.isNotEmpty(c)){
            for (ConstraintViolation<Object> constraintViolation : c) {
                String itemMessage = constraintViolation.getMessage();
                String itemName = constraintViolation.getPropertyPath().toString();
                msg.append("字段<").append(itemName).append(">--").append(itemMessage).append("。");
            }
        }
        return msg.toString();
    }

    /**
     * 拼装单个对象校验信息
     *
     * @Param [s, c]
     * @return void
     * @Author Gangbb
     * @Date 2021/10/25
     **/
    public static void getOneInfo(StringBuffer s, Set<ConstraintViolation<Object>> c){
        if (CollectionUtil.isNotEmpty(c)){
            s.append("{ ");
            for (ConstraintViolation<Object> constraintViolation : c) {
                String itemMessage = constraintViolation.getMessage();
                String itemName = constraintViolation.getPropertyPath().toString();
                s.append("字段<" + itemName + "> :" + itemMessage + "。");
            }
            s.append(" }");
        }
    }

    /**
     * 拼装多个对象校验信息
     *
     * @Param [s, collect]
     * @return void
     * @Author Gangbb
     * @Date 2021/10/25
     **/
    public static void getListInfo(StringBuffer s, List<Set<ConstraintViolation<Object>>> collect){
        for (int i = 0; i < collect.size(); i++) {
            Set<ConstraintViolation<Object>> constraintViolations = collect.get(i);
            if (CollectionUtil.isNotEmpty(constraintViolations)){
                s.append("[ " + "列表第["+ i + "]项校验不通过:");
                getOneInfo(s, constraintViolations);
                s.append(" ]");
            }
        }
    }

    /**
     * 注解校验,获取处理校验结果
     *
     * @param errorMsg 错误信息
     * @param context 校验上下文
     * @return boolean
     * @author Gangbb
     * @date 2021/10/29
     **/
    public static boolean getResult(String errorMsg, ConstraintValidatorContext context){
        if(StrUtil.isNotBlank(errorMsg)){
            // 重置错误提示
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(errorMsg)
                    .addConstraintViolation();
            return false;
        }
        return true;
    }
}

2.2.4 校验处理逻辑

/**
 *  注解@VerifyEnum 处理逻辑方法
 *
 * @author Gangbb
 * @date 2022/1/29
 **/
public class EnumValidator implements ConstraintValidator<VerifyEnum, Object> {

    private Class<? extends Enum<?>> enumClass;

    private String[]  allowedValues;

    private String[]  notAllowedValues;

    private String keyColumn;


    @Override
    public void initialize(VerifyEnum constraintAnnotation) {
        enumClass = constraintAnnotation.enumClass();
        allowedValues = constraintAnnotation.allowedValues();
        notAllowedValues = constraintAnnotation.notAllowedValues();
        keyColumn = constraintAnnotation.keyColumn();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if(value != null){
            // 校验enumClass中是否存在keyColumn字段
            if(!ReflectUtil.hasField(enumClass, keyColumn)){
                throw new RuntimeException(StrUtil.format("EnumValidator:<{}>中不存在填入的<{}>字段", enumClass.getSimpleName(), keyColumn));
            }
            // 获取所有该枚举的指定字段的值列表
            List<Object> codeValues = EnumUtil.getFieldValues(enumClass, keyColumn);

            String validateValueMsg = ValidatorUtils.validateValues(allowedValues, notAllowedValues, value, codeValues);
            if(StrUtil.isNotBlank(validateValueMsg)){
                return ValidatorUtils.getResult(validateValueMsg, context);
            }else{
                return true;
            }
        }
        return true;
    }
}

3. 使用示例

枚举类

/**
 * 组织架构类别枚举
 *
 * @author Gangbb
 * @date 2022/1/4
 **/
public enum SysDeptTypeEnum {
    SCHOOL(1, "学校"),
    INVALID(2, "单位/学院"),
    DEPARTMENT(3, "部门"),
    MAJOR(4, "专业"),

    ;
    private final int code;
    private final String name;

    SysDeptTypeEnum(int code, String info) {
        this.code = code;
        this.name = info;
    }

    public static String getValue(int code) {
        SysDeptTypeEnum[] enums = values();
        for (SysDeptTypeEnum item : enums) {
            if (item.code == code) {
                return item.getName();
            }
        }
        return null;
    }

    public int getCode() {
        return code;
    }

    public String getName() {
        return name;
    }
}

DTO请求参数实体类

@Data
public class SysDeptDto {


    /**
     * 组织架构d
     */
    @NotNull(message = "组织架构Id不能为空", groups = { EditGroup.class })
    private Long deptId;
    /**
     * 组织架构类别(字典类别:sys_dept_type)
     */
    @NotNull(message = "组织架构类别不能为空", groups = { AddGroup.class, EditGroup.class })
    @VerifyEnum(enumClass = SysDeptTypeEnum.class, keyColumn = "code", groups = { AddGroup.class, EditGroup.class })
    private Integer type;
	
	// 其他属性省略...
}    

Controller接口

/**
     * 新增组织架构
     *
     * @param dto 组织架构新增/修改 请求参数Dto
     * @return ApiResult<Void>
     * @date 2022-01-05
     **/
    @PostMapping()
    public ApiResult<Void> add(@Validated(AddGroup.class) @RequestBody SysDeptDto dto){
        return toApiRes(sysDeptService.insertByDto(dto));
    }

请求测试
在这里插入图片描述
稍微修改下注解代码

@VerifyEnum(enumClass = SysDeptTypeEnum.class, keyColumn = "code", notAllowedValues = {"5"}, groups = { AddGroup.class, EditGroup.class })

测试:
在这里插入图片描述

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot项目中,我们可以使用Java Bean Validation(JSR 303)规范中的注解校验枚举值。比如,我们可以使用@EnumValidator注解校验枚举值。 首先,我们需要在pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.2.0.Final</version> </dependency> ``` 接下来,我们可以在实体类中使用@EnumValidator注解校验枚举值。比如,以下是一个使用@EnumValidator注解校验枚举值的示例代码: ```java public enum Gender { MALE, FEMALE } public class User { @NotNull private String name; @EnumValidator(enumClass = Gender.class, message = "gender must be MALE or FEMALE") private Gender gender; // getters and setters } ``` 在上述代码中,@EnumValidator注解用于校验gender属性是否为Gender枚举中的值。如果不是,会抛出校验异常并提示"gender must be MALE or FEMALE"。 最后,我们需要在Controller中使用@Valid注解来触发参数校验。比如,以下是一个使用@Valid注解触发参数校验的示例代码: ```java @RestController @RequestMapping("/users") public class UserController { @PostMapping public ResponseEntity<String> createUser(@Valid @RequestBody User user) { // do something return ResponseEntity.ok("User created successfully"); } } ``` 在上述代码中,@Valid注解用于触发参数校验。如果校验失败,会抛出异常并返回相应的错误信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值