【参数校验框架】自定义校验注解

一、场景介绍
  • Hibernate-validator 依赖 jar 包中,虽然提供了很多很方便的校验注解,但是也有不满足某些实际需要的场景

  • 假如我们想针对参数中的某个属性,约定其值的枚举范围,如:OrderType 订单类型只允许传 PAYEDFAIL 两种值,那么现有的约束注解就不能适用这种场景了

  • 如果对这样的枚举值,我们还想在约束定义中直接匹配代码中的枚举定义,以更好地统一接口参数与业务逻辑的枚举定义,那么这种情况下,我们还可以自己扩展定义相应地约束注解逻辑

二、校验场景
  • 用户订单查询场景

  • 只允许用户查询支付失败的订单列表

三、定义注解
  • 自定义注解

    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Constraint(validatedBy = {EnumValidated.EnumValidator.class})
    public @interface EnumValidated {
    
        /** 默认错误信息*/
        String message() default "默认错误提示:请参数接口文档传入必要参数";
    
        /** 支持 String 数组校验*/
        String[] strValues() default {};
    
        /** 支持 int 数组校验*/
        int[] intValues() default {};
    
        /** 支持枚举列表校验*/
        Class<?>[] enumValues() default {};
    
        /** 分组*/
        Class<?>[] groups() default {};
    
        /** 负载*/
        Class<? extends Payload>[] payload() default {};
    
        /** 指定多个时使用*/
        @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        @interface List {
            EnumValidated[] value();
        }
    
        /**
         * Copyright (C), 1998-2021, Shenzhen Rambo Technology Co., Ltd
         * 校验类逻辑定义
         *
         * @author  Rambo
         * @date    2021/3/11 18:21
         * @since   1.0.0.1
         */
        class EnumValidator implements ConstraintValidator<EnumValidated, Object> {
            /** 字符串类型数组*/
            private String[] strValues;
    
            /** int类型数组*/
            private int[] intValues;
    
            /** 枚举类*/
            private Class<?>[] enumValues;
    
            /**
             * 初始化方法
             *
             * @author  Rambo
             * @date    2021/3/11 18:34
             * @param	constraintAnnotation    自定义枚举类型注解对象
             */
            @Override
            public void initialize(EnumValidated constraintAnnotation) {
                strValues = constraintAnnotation.strValues();
                intValues = constraintAnnotation.intValues();
                enumValues = constraintAnnotation.enumValues();
            }
    
            /**
             * 校验方法
             *
             * @author  Rambo
             * @date    2021/3/11 18:34
             * @param	value   待校验的参数值
             * @param	context 校验对象
             * @return  boolean 校验结果
             */
            @SneakyThrows
            @Override
            public boolean isValid(Object value, ConstraintValidatorContext context) {
                // 针对字符串数组的校验匹配
                if (strValues != null && strValues.length > 0) {
                    if (value instanceof String) {
                        // 判断值类型是否为 String 类型
                        for (String s : strValues) {
                            if (s.equals(value)) {
                                return true;
                            }
                        }
                    }
                }
    
                // 针对整型数组的校验匹配
                if (intValues != null && intValues.length > 0) {
                    // 判断值类型是否为 Integer 类型
                    if (value instanceof Integer) {
                        for (Integer s : intValues) {
                            if (s == value) {
                                return true;
                            }
                        }
                    }
                }
    
                // 针对枚举类型的校验匹配
                if (enumValues != null && enumValues.length > 0) {
                    for (Class<?> cl : enumValues) {
                        if (cl.isEnum()) {
                            // 枚举类验证
                            Object[] objs = cl.getEnumConstants();
                            // 这里需要注意,定义枚举时,枚举 Key 的字段名称统一用 private String code; 表示
                            Method method = cl.getMethod("getCode");
                            for (Object obj : objs) {
                                Object code = method.invoke(obj, (Object[]) null);
                                if (value.toString().equals(code.toString())) {
                                    return true;
                                }
                            }
                        }
                    }
                }
                return false;
            }
        }
    }
    

    P.S

    • 如上所示的 @EnumValidated 约束注解,是一个非常实用的扩展,通过该注解我们可以实现对参数取值范围(不是大小范围)的约束,它支持对 intString 以及 enum 三种数据类型的约束
四、使用注解
  1. 新建 Spring Boot 项目

  2. 创建订单状态枚举类型

    @Getter
    public enum OrderStateEnum {
    
        /** 支付中*/
        PAY("PAYING", "支付中"),
    
        /** 已支付*/
        PAYED("PAYED", "已支付"),
    
        /** 支付失败*/
        FAIL("FAIL", "支付失败"),
        ;
    
        /** 枚举编码*/
        private final String code;
    
        /** 枚举描述*/
        private final String name;
    
        OrderStateEnum(String code, String name) {
            this.code = code;
            this.name = name;
        }
    
        /** 根据代码获取枚举名称*/
        public static String getNameByCode(String code) {
            for (OrderStateEnum orderState : OrderStateEnum.values()) {
                if (orderState.getCode().equals(code)) {
                    return orderState.getName();
                }
            }
            return null;
        }
    
        /** 根据名称获取枚举代码*/
        public static String getCodeByName(String name) {
            for (OrderStateEnum orderType : OrderStateEnum.values()) {
                if (orderType.getName().equals(name)) {
                    return orderType.getCode();
                }
            }
            return null;
        }
    
        /** 根据代码获取枚举对象*/
        public static OrderStateEnum getOrderStateEnumByCode(String code) {
            for (OrderStateEnum orderState : OrderStateEnum.values()) {
                if (orderState.getCode().equals(code)) {
                    return orderState;
                }
            }
            return null;
        }
    
        /** 根据名称获取枚举对象*/
        public static OrderStateEnum getOrderStateEnumByName(String name) {
            for (OrderStateEnum orderState : OrderStateEnum.values()) {
                if (orderState.getName().equals(name)) {
                    return orderState;
                }
            }
            return null;
        }
    }
    
  3. 创建 Order 实体

    @Data
    @ToString
    public class Order implements Serializable {
        private static final long serialVersionUID = 784930215432L;
    
        /** 订单号*/
        private int id;
    
        /** 订单名称*/
        @NotNull(message = "订单名称不能为空")
        private String orderName;
    
        /** 订单状态
            定制化注解,支持参数值与指定类型数组列表值进行匹配(缺点是需要将枚举值写死在字段定义的注解中)*/
        @EnumValidated(strValues = {"FAIL", "PAYED"}, message = "只能查询指定状态的订单信息-1")
        private String orderState;
    
        /** 订单状态枚举
            定制化注解,实现参数值与枚举列表的自动匹配校验(能更好地与实际业务开发匹配)*/
        @EnumValidated(enumValues = OrderStateEnum.class, message = "只能查询指定状态的订单信息-2")
        private String orderStateEnum;
    }
    

    P.S

    • 如上所示代码,该扩展注解既可以使用 strValuesintValues 属性来编程列举取值范围,也可以直接通过 enumValues 来绑定枚举定义。但是需要注意,处于通用考虑,具体枚举定义的属性的名称要统一匹配为codename,具体请参数上述枚举类型到定义
  4. 创建接口控制器

    @PostMapping("/order")
    @ApiOperation(value = "订单查询", notes = "订单查询,匹配自定义验证注解,抛出异常由统一异常处理")
    public DataResult orderList(@RequestBody @Validated Order order) {
        log.info("The oder request is {}", order.toString());
        return DataResult.success();
    }
    
  5. 模拟请求参数

    {
      "id": 0,
      "orderState": "FAIL1",
      "orderStateEnum": "FAIL2"
    }
    
  6. 验证响应结果

    {
      "code": 10009,
      "msg": "订单名称不能为空;只能查询指定状态的订单信息-2;只能查询指定状态的订单信息-1;",
      "detail": null,
      "data": null
    }
    

    P.S
    以上 Controller 通过项目中定义 @RestControllerAdvice 来进行异常统一处理,所以看到的响应结果是封装过的

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值