美文网首页框架介绍
Validate核查框架——Mikilin

Validate核查框架——Mikilin

作者: 老柿子 | 来源:发表于2020-05-28 19:52 被阅读0次

    Mikilin 简介

    Mikilin框架是自主设计的对象的属性核查框架,功能直接对应JSR-303协议,但是着眼点和用法不一样,暂时没有采用该协议(后续版本考虑进去),JSR-303协议对应的业内实现为hibernate.validate,但是该框架比hibernate.validate中的功能更多,使用和扩展也更简单。

    JSR-303协议中的校验基本层面为属性本身的校验,而属性关联的校验就没有关注。而我们这里的框架,着眼点为数据领域,每个待核查的数据都归为一类进行匹配,其中数据之间的关联在概念上也作为一个类别。所有这些类别最后具象化到代码中为一个注解中的多个属性。
    目前已经发布到maven中央仓库,直接使用即可

    <dependency>
        <groupId>com.github.simonalong</groupId>
        <artifactId>mikilin</artifactId>
        <!--请替换为最新版本-->
        <version>${latest.release.version}</version>
    </dependency>
    

    框架文档:https://www.yuque.com/simonalong/mikilin/mduu3z
    源码:https://github.com/SimonAlong/Mikilin

    一、快速入门

    该框架使用极其简单,添加注解和使用静态类核查即可
    如下:给需要拦截的属性添加注解即可

    @Data
    @Accessors(chain = true)
    public class WhiteAEntity {
        
        // 修饰属性name,只允许对应的值为a,b,c和null
        @Matcher(value = {"a","b","c","null"}, errMsg = "输入的值不符合需求")
        private String name;
        private String address;
    }
    

    在拦截的位置添加核查,这里是做一层核查,在业务代码中建议封装到aop中对业务使用方不可见即可实现拦截

    import lombok.SneakyThrows;
    
    @Test
    @SneakyThrows
    public void test1(){
        WhiteAEntity whiteAEntity = new WhiteAEntity();
        whiteAEntity.setName("d");
        // 可以使用带有返回值的核查
        if (!MkValidators.check(whiteAEntity)) {
            // 输出:数据校验失败-->属性 name 的值 d 不在只可用列表 [null, a, b, c] 中-->类型 WhiteAEntity 核查失败
            System.out.println(MkValidators.getErrMsgChain());
            // 输出:输入的值不符合需求
            System.out.println(MkValidators.getErrMsg());
        }
        // 或者 可以采用抛异常的核查,该api为 MkValidators.check 的带有异常的检测方式
        MkValidators.validate(whiteAEntity);
    }
    

    以上基本上就是该框架的所有功能。非常简单,不过功能却很多,多在注解@Matcher中的属性,后面一一介绍。

    二、注解

    这里只有有三个注解

    /**
    * 匹配器:修饰属性
    */
    @Matcher
    /**
    * 多个匹配器,不同的分组:修饰属性,为@Matcher的复数类型
    */
    @Matchers
    /**
    * 复杂对象解析器:修饰属性,只有添加该注解,则复杂的属性,才会进行解析
    */
    @Check
    

    三、匹配器

    匹配器就是指注解@Matcher,而注解中的各种各样的如下的属性,表示匹配项,而丰富的匹配项是该框架最强大和功能最丰富的的地方,每个属性(匹配项)定位是能够匹配该领域的所有类型

    @Repeatable(Matchers.class)
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Matcher {
    
        /**
         * 针对不同场景下所需的匹配模式的不同,默认"_default_",详见{@link com.simonalong.mikilin.MkConstant#DEFAULT_GROUP}
         * <p>
         * 该参数使用一般结合{@link Matchers}这个注解使用
         * 
         * @return 分组
         */
        String[] group() default {MkConstant.DEFAULT_GROUP};
    
        /**
         * 匹配属性为对应的类型,比如Integer.class,Long.class等等
         */
        Class<?>[] type() default {};
    
        /**
         * 如果要匹配值为null,那么添加一个排除的值为"null",因为不允许直接设置为null,修饰的对象可以为Number类型也可以为String类型,也可以为Boolean类型
         * @return 只允许的值的列表
         */
        String[] value() default {};
    
        /**
         * 可用的值对应的类型
         * @return 对应的枚举类型
         */
        FieldModel model() default FieldModel.DEFAULT;
    
        /**
         * 枚举类型的判断
         *
         * 注意:该类型只用于修饰属性的值为String类型或者Integer类型的属性,String为枚举的Names,Integer是枚举的下标
         *
         * @return 该属性为枚举对应的类,否则不生效
         */
        Class<? extends Enum>[] enumType() default {};
    
        /**
         * 数据范围的判断
         * <p> 该字段修饰的类型可以为数值类型,也可以为时间类型,也可以为集合类型(集合类型用来测试集合的size个数的范围)
         *
         * @return
         * 如果是数值类型,则比较的是数值的范围,使用比如:[a,b],[a,b),(a,b],(a,b),(null,b],(null,b),[a, null), (a, null)
         * 如果是集合类型,则比较的是集合的size大小,使用和上面一样,比如:[a,b]等等
         * 如果是时间类型,可以使用这种,比如["2019-08-03 12:00:32.222", "2019-08-03 15:00:32.222"),也可以用单独的一个函数关键字
         * past: 表示过去
         * now: 表示现在
         * future: 表示未来
         * 同时也支持范围中包含函数(其中范围内部暂时不支持past和future,因为这两个函数没有具体的时间),比如:
         * past 跟(nul, now)表示的相同
         * future 跟(now, null)表示的相同
         * 支持具体的范围,比如:("2019-08-03 12:00:32", now),其中对应的时间类型,目前支持这么几种格式
         * yyyy
         * yyyy-MM
         * yyyy-MM-dd
         * yyyy-MM-dd HH
         * yyyy-MM-dd HH:mm
         * yyyy-MM-dd HH:mm:ss
         * yyyy-MM-dd HH:mm:ss.SSS
         */
        String range() default "";
    
        /**
         * 数据条件的判断
         *
         * 根据Java的运算符构造出来对应的条件表达式来进行判断,而其中的数据不仅可以和相关的数据做条件判断,还可和当前修饰的类的其他数据进行判断,
         * 其中当前类用#root表示,比如举例如下,对象中的一个属性小于另外一个属性,比如:{@code #current + #root.ratioB + #root.ratioC == 100}
         * 其中#current表示当前的属性的值,#root表示当前的属性所在的对象,ratioB为该对象的另外一个属性,如上只有在属性ratioA是大于ratioB的时候核查才会拦截
         *
         * @return 用于数据字段之间的条件表达式(即条件结果为true还是false),当前条件支持Java的所有运算符,以及java的所有运算结果为boolean的表达式
         * 算术运算符:{@code  "+"、"-"、"*"、"/"、"%"、"++"、"--"}
         * 关系运算符:{@code "=="、"!="、">"、"<"、">="、"<="}
         * 位运算符:{@code "&"、"|"、"^"、"~"、"<<"、">>"、">>>"}
         * 逻辑运算符:{@code "&&"、"||"、"!"}
         * 赋值运算符:{@code "="、"+="、"-="、"*="、"/="、"(%)="、"<<="、">>="、"&="、"^="、"|="}
         * 其他运算符:{@code 条件运算符(?:)、instanceof运算符}
         * {@code java.lang.math}中的所有函数,比如:{@code min,max,asb,cell}等等
         */
        String condition() default "";
    
        /**
         * 可用的值对应的正则表达式
         * @return 对应的正则表达式
         */
        String regex() default "";
    
        /**
         * 系统自己编码判断
         *
         * @return 调用的核查的类和函数对应的表达式,比如:"com.xxx.AEntity#isValid",其中#后面是方法,方法返回boolean或者包装类,其中参数根据个数支持的类型也是不同,参考测试类{@link com.simonalong.mikilin.judge.JudgeCheck}
         */
        String judge() default "";
        
        /**
         * 核查失败后的返回语句
         *
         * @return 核查失败后返回的语句
         */
        String errMsg() default "";
    
        /**
         * 过滤器模式
         * <p>
         *     其他的属性都是匹配,而该属性表示匹配之后对应的数据的处理,是接受放进来,还是只拒绝这样的数据
         * @return true:accept(放进来),false:deny(拒绝)
         */
        boolean accept() default true;
        
        /**
         * 是否启用核查
         * @return true:禁用核查,false:启用核查
         */
        boolean disable() default false;
    }
    

    group:分组匹配

    上面看到,每个属性只有一种核查规则,但是如果我们要在不同的场景中使用不同的规则,那么这个时候应该怎么办呢,分组就来了,新增一个注解Matchers

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Matchers {
    
        Matcher[] value();
    }
    

    使用时候,通过属性中的group进行定义不同的规则,核查的时候,采用函数MkValidators.check(String group, Object obj)进行核查,如果采用MkValidators.check(Object obj)则采用默认的组,即下面的没有设置组的匹配规则

    @Data
    @Accessors(chain = true)
    public class GroupEntity {
    
        @Matchers({
            @Matcher(range = "[50, 100]", accept = false),
            @Matcher(group = "test1", range = "[12, 23]", accept = false),
            @Matcher(group = "test2", range = "[1, 10]", accept = false)
        })
        private Integer age;
    
        @Matchers({
            @Matcher(value = {"beijing", "shanghai", "guangzhou"}),
            @Matcher(group = "test1", value = {"beijing", "shanghai"}),
            @Matcher(group = "test2", value = {"shanghai", "hangzhou"})
        })
        private String name;
    }
    

    用例

    def "测试指定分组"() {
        given:
        GroupEntity entity = new GroupEntity().setAge(age).setName(name)
    
        expect:
        def act = MkValidators.check("test1", entity);
        Assert.assertEquals(result, act)
        if (!act) {
            println MkValidators.errMsg
        }
    
        where:
        age | name        | result
        10  | "shanghai"  | true
        12  | "beijing"   | false
        23  | "beijing"   | false
        50  | "beijing"   | true
        100 | "guangzhou" | false
    }
    

    value:指定某些值匹配

    @Data
    @Accessors(chain = true)
    public class WhiteAEntity {
        @Matcher({"a","b","c","null"})
        private String name;
        private String address;
    }
    

    注意:测试代码这里全部采用Spock测试框架进行工具方面的测试,主要是为了方便

    def "只有指定的值才能通过"() {
        given:
        WhiteAEntity entity = new WhiteAEntity()
        entity.setName(name as String)
    
        expect:
        boolean actResult = MkValidators.check(entity)
        if (!actResult) {
            println MkValidators.getErrMsgChain()
        }
        Assert.assertEquals(result, actResult)
    
        where:
        name | result
        "a"  | true
        "b"  | true
        "c"  | true
        null | true
        "d"  | false
    }
    

    type:属性类型匹配

    @Data
    @Accessors(chain = true)
    public class TypeEntity {
    
        /**
         * 没有必要设置type
         */
        @Matcher(type = Integer.class)
        private Integer data;
    
        @Matcher(type = CharSequence.class)
        private String name;
    
        @Matcher(type = {Integer.class, Float.class})
        private Object obj;
    
        @Matcher(type = Number.class)
        private Object num;
    }
    

    测试代码

    def "测试不明写类继承关系1"() {
        given:
        TypeEntity entity = new TypeEntity().setObj(obj)
    
        expect:
        boolean actResult = MkValidators.check(entity, "obj")
        if (!result) {
            println MkValidators.getErrMsgChain()
        }
        Assert.assertEquals(result, actResult)
    
        where:
        obj    | result
        'c'    | false
        "abad" | false
        1232   | true
        1232l  | false
        1232f  | true
        12.0f  | true
        -12    | true
    }
    

    注意:

    1. 如果设置的类型不是属性的类型或者父类则会报错
    2. 如果为具体的类型,则再设置与其相同的类型,则没有必要,就像上面的data属性

    model:内置类型匹配

    目前内置了常见的几种类型:身份证号、手机号、固定电话、邮箱、IP地址

    ID_CARD :身份证号
    PHONE_NUM :手机号
    FIXED_PHONE :固定电话
    MAIL :邮箱
    IP_ADDRESS: IP地址

    @Data
    @Accessors(chain = true)
    public class IpEntity {
    
        @Matcher(model = FieldModel.IP_ADDRESS)
        private String ipValid;
        @Matcher(model = FieldModel.IP_ADDRESS, accept =false)
        private String ipInvalid;
    }
    
    def "IP测试"() {
        given:
        IpEntity entity = new IpEntity().setIpValid(valid).setIpInvalid(invalid)
    
        expect:
        boolean actResult = MkValidators.check(entity)
        if (!result) {
            println MkValidators.getErrMsgChain()
        }
        Assert.assertEquals(result, actResult)
    
        where:
        valid             | invalid           | result
        "192.231asdf"     | "192.123.231.222" | false
        "192.231asdf"     | "192.231asdf"     | false
        "192.123.231.222" | "192.231asdf"     | true
        "192.123.231.222" | "192.123.231.222" | false
    }
    

    range:范围匹配

    目前该属性不只是数值类型(Integer, Long, Float, Short, Double等一切数值类型),也支持时间类型,也支持集合类型(集合比较的是集合的大小),范围是用的是数学的开闭写法

    数值范围

    @Data
    @Accessors(chain = true)
    public class RangeEntity4 {
    
        /**
         * 属性为大于100
         */
        @Matcher(range = "(100, null)")
        private Integer num1;
    
        /**
         * 属性为大于等于100
         */
        @Matcher(range = "[100, null)")
        private Integer num2;
    
        /**
         * 属性为大于20且小于50
         */
        @Matcher(range = "(20, 50)")
        private Integer num3;
    
        /**
         * 属性为小于等于50
         */
        @Matcher(range = "(null, 50]")
        private Integer num4;
        
        /**
         * 属性为大于等于20且小于等于50
         */
        @Matcher(range = "[20, 50]")
        private Integer num5;
    
        /**
         * 属性为大于等于100,同属性num2一样
         */
        @Matcher(range = "[100, )")
        private Integer num6;
        
        /**
         * 属性为大于等于100,同属性num2一样
         */
        @Matcher(range = "[100,)")
        private Integer num7;
        
        /**
         * 属性为小于等于5,同属性num4一样
         */
        @Matcher(range = "(, 50]")
        private Integer num8;
    }
    

    时间范围

    修饰的类型可以为Date类型,也可以为Long类型

    @Data
    @Accessors(chain = true)
    public class RangeTimeEntity {
    
        /**
         * 属性为:2019-07-13 12:00:23.321 到 2019-07-23 12:00:23.321的时间
         */
        @Matcher(range = "['2019-07-13 12:00:23.321', '2019-07-23 12:00:23.321']")
        private Date date1;
    
        /**
         * 属性为:2019-07-13 12:00:23.000 到 2019-07-23 12:00:00.000 的时间
         */
        @Matcher(range = "['2019-07-13 12:00:23', '2019-07-23 12:00']")
        private Date date2;
        
        /**
         * 属性为:2019-07-13 00:00:00.000 到 2019-07-01 00:00:00.000 的时间
         */
        @Matcher(range = "['2019-07-13', '2019-07']")
        private Long dateLong3;
            
        /**
         * 属性为:现在时间 到 2019-07-23 12:00:23.321 的时间
         */
        @Matcher(range = "(now, '2019-07-23 12:00:23.321']")
        private Date date4;
        
        /**
         * 属性为:2019-07-13 00:00:00.000 到现在的时间
         */
        @Matcher(range = "['2019-07-13', now)")
        private Date date5;
        
        /**
         * 属性为:过去的时间,同下面的past
         */
        @Matcher(range = "(null, now)")
        private Date date6;
        
        /**
         * 属性为:过去的时间,同下面的past
         */
        @Matcher(range = "('null', 'now')")
        private Date date7;
        
        /**
         * 属性为:过去的时间,同上
         */
        @Matcher(range = "past")
        private Date date8;
        
        /**
         * 属性为:未来的时间,同下面的future
         */
        @Matcher(range = "(now, null)")
        private Date date9;
        
        /**
         * 属性为:未来的时间,同下面的future
         */
        @Matcher(range = "future")
        private Date date10;
    }
    

    集合大小范围

    集合这里只核查集合的数据大小

    @Data
    @Accessors(chain = true)
    public class CollectionSizeEntityA {
    
        private String name;
    
        /**
        * 对应集合的个数不为空,且个数小于等于2 
        */
        @Matcher(range = "(0, 2]")
        private List<CollectionSizeEntityB> bList;
    }
    

    condition:表达式匹配

    这里的表达式只要是任何返回Boolean的表达式即可,框架提供两个占位符,#current和#root,其中#current表示当前属性的值,#root表示的是当前属性所在的对象的值,可以通过#root.xxx访问其他的属性。该表达式支持java中的任何符号操作,此外还支持java.lang.math中的所有静态函数,比如:min、max和abs等等

    @Data
    @Accessors(chain = true)
    public class ConditionEntity {
    
        /**
        * 当前属性和属性num3的值大于100 
        */
        @Matcher(condition = "#current + #root.num2 > 100")
        private Integer num1;
    
        /**
        * 当前属性的值小于 20 
        */
        @Matcher(condition = "#current < 20")
        private Integer num2;
    
        /**
        * 当前属性的值大于31并自增 
        */
        @Matcher(condition = "(++#current) >31")
        private Integer num3;
        
        /**
        * 当前属性的值大于31并自增 
        */
        @Matcher(condition = "(++#current) >31")
        private Integer num4;
        
        /**
        * 其中某个属性为true 
        */
        @Matcher(condition = "#root.judge")
        private Integer age;
    
        private Boolean judge;
        
        /**
        * 当前值和另外值的最小值大于第三个值 
        */
        @Matcher(condition = "min(#current, #root.num6) > #root.num7")
        private Integer num5;
        private Integer num6;
        private Integer num7;
    }
    

    regex:正则表达式匹配

    @Data
    @Accessors(chain = true)
    public class RegexEntity {
    
        @Matcher(regex = "^\\d+$")
        private String regexValid;
    
        @Matcher(regex = "^\\d+$", accept = false)
        private String regexInValid;
    }
    

    customize:自定义扩展匹配

    上面都是系统内置的一些匹配,如果用户想自定义匹配,可以自行扩展,需要通过该函数指定一个全限定名的类和函数指定即可,目前支持的参数类型有如下几种,比如

    自定义函数路径匹配

    @Data
    @Accessors(chain = true)
    public class JudgeEntity {
    
        /**
        * 外部定义的匹配器,只传入属性的参数
        */
        @Matcher(customize = "com.simonalong.mikilin.judge.JudgeCheck#ageValid")
        private Integer age;
    
        /**
        *  外部定义的匹配器,传入属性所在对象本身,也可传入属性的参数类型
        */
        @Matcher(customize = "com.simonalong.mikilin.judge.JudgeCheck#ratioJudge")
        private Float mRatio;
    
        private Float nRatio;
    
        /**
        * 这里自定义的第一个参数是属性本身,第二个参数是框架的上下文(用户填充匹配成功或者失败的信息) 
        */
        @Matcher(customize = "com.simonalong.mikilin.judge.JudgeCheck#twoParam")
        private String twoPa;
    
        /**
        * 这里自定义的第一个参数是属性所在对象,第二个是属性本身,第三个参数是框架的上下文(用户填充匹配成功或者失败的信息) 
        */
        @Matcher(customize = "com.simonalong.mikilin.judge.JudgeCheck#threeParam")
        private String threePa;
    }
    

    对应的匹配逻辑,其中匹配函数的入参是上面注解修饰的属性的类型(或者子类)

    public class CustomizeCheck {
    
        /**
         * 年龄是否合法
         */
        public boolean ageValid(Integer age) {
            if(null == age){
                return false;
            }
            if (age >= 0 && age < 200) {
                return true;
            }
    
            return false;
        }
        
        /**
         * 能够传递核查的对象,对于对象中的一些属性可以进行系统内部的配置
         *
         * mRatio + nRatio < 1.0
         */
        private boolean ratioJudge(JudgeEntity judgeEntity, Float nRatio) {
            if(null == nRatio || null == judgeEntity){
                return false;
            }
            return nRatio + judgeEntity.getMRatio() < 10.0f;
        }
        
        /**
         * 两个函数
         */
        private boolean twoParam(String funName, MkContext context) {
            if (funName.equals("hello")){
                context.append("匹配上字段'hello'");
               return true;
            }
            context.append("没有匹配上字段'hello'");
            return false;
        }
        
        /**
         * 三个函数
         */
        private boolean threeParam(JudgeEntity judgeEntity, String temK, MkContext context) {
            if (temK.equals("hello") || temK.equals("word")){
                context.append("匹配上字段'hello'和'word'");
                return true;
            }
            context.append("没有匹配上字段'hello'和'word'");
            return false;
        }
    }
    

    spring的Bean自定义匹配器

    上面看到了,我们指定一个全限定路径即可设置过滤器,其实是反射了一个代理类,在真实的业务场景中,我们的bean是用spring进行管理的,因此这里增加了一个通过spring管理的匹配器,如下 使用时候需要在指定为扫描一下如下路径即可

    @ComponentScan(value = "com.simonalong.mikilin.util")

    下面的函数对应的参数跟上面非spring时候一样,可以有三种格式

    @Service
    public class JudgeCls {
    
        // 该引用只是举例
        @Autowire
        private UserSevice userSevice;
    
        /**
         * 年龄是否合法
         */
        public boolean ageValid(Integer age) {
            if(null == age){
                return false;
            }
            if (age >= 0 && age < 200) {
                return true;
            }
    
            return false;
        }
    }
    

    enumType:枚举值匹配

    @Data
    @AllArgsConstructor
    @Accessors(chain = true)
    public class JudgeEntity {
    
        @Matcher(enumType = AEnum.class)
        private String name;
    
        @Matcher(enumType = {AEnum.class, BEnum.class})
        private String tag;
    
        @Matcher(enumType = {CEnum.class}, accept = false)
        private String invalidTag;
    }
    
    @Getter
    public enum AEnum {
        A1("a1"),
        A2("a2"),
        A3("a3");
    
        private String name;
    
        AEnum(String name) {
            this.name = name;
        }
    }
    
    def "枚举类型测试"() {
        given:
        JudgeEntity judgeEntity = new JudgeEntity(name, tag, invalidTag)
    
        expect:
        def act = MkValidators.check(judgeEntity)
        Assert.assertEquals(result, act)
        if (!act) {
            println MkValidators.errMsg
        }
    
        where:
        name | tag  | invalidTag | result
        "A1" | "A1" | "c"        | true
        "A1" | "B1" | "c"        | true
        "A1" | "B2" | "c"        | true
        "A1" | "B3" | "c"        | true
        "A1" | "A1" | "C1"       | false
        "A1" | "A1" | "C2"       | false
    }
    

    accept:拦截还是拒绝

    该属性表示匹配后的数据是接收,还是拒绝,如果为true表示接收,则表示只接收按照匹配器匹配的数据,为白名单概念。如果为false,则表示值拒绝对于匹配到的数据,为黑名单概念。白名单就不再介绍,这里介绍下为false情况

    @Data
    @Accessors(chain = true)
    public class DenyEntity {
    
        @Matcher(value = {"a", "b", "null"}, accept = false)
        private String name;
        @Matcher(range = "[0, 100]", accept = false)
        private Integer age;
    }
    

    拦截用例

    def "测试指定的属性age"() {
        given:
        DenyEntity entity = new DenyEntity().setName(name).setAge(age)
    
        expect:
        def act = MkValidators.check(entity);
        Assert.assertEquals(result, act)
        if (!act) {
            println MkValidators.errMsgChain
        }
    
        where:
        name | age | result
        "a"  | 0   | false
        "b"  | 89  | false
        "c"  | 100 | false
        null | 200 | false
        "d"  | 0   | false
        "d"  | 200 | true
    }
    

    errMsg:自定义拦截文案

    自定义的文案

    version:>=1.5.0errMsg是用于在当前的数据被拦截之后的输出,比如刚开始的介绍案例,如果

    @Data
    @Accessors(chain = true)
    public class WhiteAEntity {
        
        // 修饰属性name,只允许对应的值为a,b,c和null
        @Matcher(value = {"a","b","c","null"}, errMsg = "输入的值不符合需求")
        private String name;
        private String address;
    }
    

    在拦截的位置添加核查,这里是做一层核查,在业务代码中建议封装到aop中对业务使用方不可见即可实现拦截

    import lombok.SneakyThrows;
    
    @Test
    @SneakyThrows
    public void test1(){
        WhiteAEntity whiteAEntity = new WhiteAEntity();
        whiteAEntity.setName("d");
    
        // 可以使用带有返回值的核查
        if (!MkValidators.check(whiteAEntity)) {
            // 输出:数据校验失败-->属性 name 的值 d 不在只可用列表 [null, a, b, c] 中-->类型 WhiteAEntity 核查失败
            System.out.println(MkValidators.getErrMsgChain());
            // 输出:输入的值不符合需求
            System.out.println(MkValidators.getErrMsg());
        }
    
        // 或者 可以采用抛异常的核查,该api为 MkValidators.check 的带有异常的检测方式
        MkValidators.validate(whiteAEntity);
    }
    

    采用系统生成的文案

    version:>=1.0.0如果我没不写errMsg,如下这种,那么返回值为系统默认的错误信息,比如

    @Data
    @Accessors(chain = true)
    public class WhiteAEntity {
        
        // 修饰属性name,只允许对应的值为a,b,c和null
        @Matcher(value = {"a","b","c","null"})
        private String name;
        private String address;
    }
    

    执行结果

    import lombok.SneakyThrows;
    
    @Test
    @SneakyThrows
    public void test1(){
        WhiteAEntity whiteAEntity = new WhiteAEntity();
        whiteAEntity.setName("d");
    
        // 可以使用带有返回值的核查
        if (!MkValidators.check(whiteAEntity)) {
            // 输出:数据校验失败-->属性 name 的值 d 不在只可用列表 [null, a, b, c] 中-->类型 WhiteAEntity 核查失败
            System.out.println(MkValidators.getErrMsgChain());
            // 输出:属性 name 的值 d 不在只可用列表 [null, a, b, c] 中
            System.out.println(MkValidators.getErrMsg());
        }
    
        // 或者 可以采用抛异常的核查,该api为 MkValidators.check 的带有异常的检测方式
        MkValidators.validate(whiteAEntity);
    }
    

    errMsg中添加属性的值

    version:>=1.5.1自定义文案中如果要显示我们修饰的属性的值,那么可以采用变量#current即可

    @Data
    @Accessors(chain = true)
    public class ErrMsgEntity3 {
    
        @Matcher(value = {"a", "b", "c"}, errMsg = "值#current不符合要求")
        private String name;
    }
    
    def "提供占位符的要求"() {
        given:
        ErrMsgEntity3 entity = new ErrMsgEntity3().setName(name)
    
        expect:
        def act = MkValidators.check(entity)
        if (!act) {
            println MkValidators.getErrMsgChain()
            println MkValidators.getErrMsg()
        }
        Assert.assertEquals(result, act)
    
        where:
        name | result
        "a"  | true
        "b"  | true
        "c"  | true
        "d"  | false
    }
    

    四、匹配方式

    前面介绍了匹配器有哪些,那么怎么进行匹配,这里简单介绍下

    1.匹配器内部有多个匹配项

    匹配器就是指@Matcher 这样的一个注解,这个注解中有很多属性,这些属性称之为匹配项对于修饰属性的某个匹配器而言,如果能够命中任何一个匹配项,则认为匹配上了该匹配器

    @Data
    @Accessors(chain = true)
    public class MultiMatcherEntity {
    
        /**
         * 市编码
         */
        @Matcher(value = "12")
        private String cityCode;
        /**
         * num:数字为11或者0~10(包含边界值)
         */
        @Matcher(value = "11", range = "[0, 10]")
        private Integer num;
    
        /**
         * code:数字为33或者10~20(左开右闭)
         */
        @Matcher(value = "33", range = "(10, 20]", accept = false)
        private Integer code;
    }
    
    def "一个匹配器多配器项(或)测试"() {
        given:
        MultiMatcherEntity entity = new MultiMatcherEntity().setCityCode(cityCode).setNum(num).setCode(code)
    
        expect:
        boolean actResult = MkValidators.check(entity)
        if (!actResult) {
            println MkValidators.getErrMsg()
            println MkValidators.getErrMsgChain()
            println()
        }
        Assert.assertEquals(result, actResult)
    
        where:
        cityCode | num | code | result
        "12"     | 5   | 5    | true
        "12"     | 11  | 5    | true
        "13"     | 5   | 5    | false
        "12"     | 120 | 5    | false
        "12"     | 5   | 33   | false
        "12"     | 5   | 15   | false
    }
    

    2.多个匹配器进行匹配

    如果要求有些值要进行多种条件限制,那么这个时候就要与的操作了,那么这种多种严格条件的限制,可以采用多匹配器方式,即有多个注解修饰同一个属性(注解@Matcher是支持多注解模式的)

    @Data
    @Accessors(chain = true)
    public class MultiMatcherEntity2 {
    
        /**
         * 数据:为偶数,而且是在0~100这个范围
         */
        @Matcher(condition = "#current %2 ==0", errMsg = "值#current不是偶数")
        @Matcher(range = "[0, 100]", errMsg = "值#current没有在0~100范围中")
        private Integer code;
    }
    
    def "多个匹配器测试"() {
        given:
        MultiMatcherEntity2 entity = new MultiMatcherEntity2().setCode(code)
    
        expect:
        boolean actResult = MkValidators.check(entity)
        if (!actResult) {
            println MkValidators.getErrMsg()
        }
        Assert.assertEquals(result, actResult)
    
        where:
        code | result
        0    | true
        1    | false
        2    | true
        3    | false
        102  | false
    }
    

    3.多个匹配器多个不同的组

    对上面2的补充,在多个不同组的情况下,也可以在不同组情况下的多个匹配

    @Data
    @Accessors(chain = true)
    public class MultiMatcherEntity3 {
    
    
        /**
         * 数据:为偶数,而且是在0~100这个范围
         */
        @Matcher(group = "偶数", condition = "#current %2 ==0", errMsg = "值#current不是偶数")
        @Matcher(group = "偶数", range = "[0, 100]", errMsg = "值#current没有在0~100范围中")
        @Matcher(group = "奇数", condition = "#current %2 ==1", errMsg = "值#current不是奇数")
        @Matcher(group = "奇数", range = "[100, 200]", errMsg = "值#current没有在100~200范围中")
        private Integer code;
    }
    
        def "多group的奇数配置"() {
            given:
            MultiMatcherEntity3 entity = new MultiMatcherEntity3().setCode(code)
    
            expect:
            boolean actResult = MkValidators.check(group, entity)
            if (!actResult) {
                println MkValidators.getErrMsg()
            }
            Assert.assertEquals(result, actResult)
    
            where:
            group | code | result
            "偶数"  | 0    | true
            "偶数"  | 1    | false
            "偶数"  | 2    | true
            "偶数"  | 3    | false
            "偶数"  | 102  | false
    
            "奇数"  | 101  | true
            "奇数"  | 102  | false
            "奇数"  | 103  | true
            "奇数"  | 201  | false
            "奇数"  | 202  | false
        }
    

    五、核查方式

    核查的时候,其实就是怎么校验数据的正确还是错误,核查函数为静态类 MkValidators 内的所有静态函数
    注意:类MkValidators是版本 v1.4.5及之后改名的,之前为Checks

    /**
    * 核查对象
    */
    public boolean check(Object object){}
    /**
    * 核查对象的某些属性
    */
    public boolean check(Object object, String... fieldSet){}
    /**
    * 根据分组核查属性
    */
    public boolean check(String group, Object object) {}
    /**
    * 核查分组中的对象的某些属性
    */
    public boolean check(String group, Object object, String... fieldSet){}
    /**
    * 返回错误信息链
    */
    public String getErrMsgChain() {}
    /**
    * 获取最后设置错误信息
    */
    public String getErrMsg() {}
    
    /**
     * 核查对象失败抛异常
     */
    public void validate(Object object) throws MkException
    
    /**
     * 核查对象指定属性失败抛异常
     */
    public void validate(Object object, String ...fieldSet) throws MkException
    
    /**
     * 根据组核查对象失败抛异常
     */
    public void validate(String group, Object object) throws MkException
    
    /**
     * 根据组核查对象指定属性失败抛异常
     */
    public void validate(String group, Object object, String ...fieldSet) throws MkException
    

    核查整个对象

    那么会核查对象中的所有添加@Matcher注解(而且disable=false)的属性

    核查某些属性

    有些情况下,我们可能不是核查整个对象,而是可能核查某些属性,那么就可以使用核查某些属性的功能

    @Data
    @Accessors(chain = true)
    public class TestEntity {
    
        @Matcher(value = {"nihao", "ok"}, accept = false)
        private String name;
        @Matcher(range = "[12, 32]")
        private Integer age;
        @Matcher({"beijing", "shanghai"})
        private String address;
    }
    
    def "测试指定的属性age"() {
        given:
        TestEntity entity = new TestEntity().setName(name).setAge(age)
    
        expect:
        def act = MkValidators.check(entity, "age");
        Assert.assertEquals(result, act)
        if (!act) {
            println MkValidators.errMsg
        }
    
        where:
        name     | age | result
        "nihao"  | 12  | true
        "ok"     | 32  | true
        "hehe"   | 20  | true
        "haohao" | 40  | false
    }
    

    核查某个组

    对于一个属性,在不同的情况下,对这个属性值的要求可能不同,那么这个时候该怎么办呢,那么group属性就排上用场了

    @Data
    @Accessors(chain = true)
    public class GroupEntity {
    
        @Matchers({
            @Matcher(range = "[50, 100]", accept = false),
            @Matcher(group = "test1", range = "[12, 23]", accept = false),
            @Matcher(group = "test2", range = "[1, 10]", accept = false)
        })
        private Integer age;
    
        @Matchers({
            @Matcher(value = {"beijing", "shanghai", "guangzhou"}),
            @Matcher(group = "test1", value = {"beijing", "shanghai"}),
            @Matcher(group = "test2", value = {"shanghai", "hangzhou"})
        })
        private String name;
    }
    
    def "测试指定分组指定属性"() {
        given:
        GroupEntity entity = new GroupEntity().setAge(age).setName(name)
    
        expect:
        def act = MkValidators.check("test1", entity, "age");
        Assert.assertEquals(result, act)
        if (!act) {
            println MkValidators.errMsgChain
        }
    
        where:
        age | name        | result
        10  | "shanghai"  | true
        12  | "beijing"   | false
        23  | "beijing"   | false
        50  | "beijing"   | true
        100 | "guangzhou" | true
    }
    

    六、泛型支持

    前面提到的修饰的属性,其实都是各种各样的属性,但是都不是泛型类型,该框架也是支持泛型累心搞的。泛型类型分为四种: ParameterizedType:代表参数中是显而易见的类型,如:Map<String, TestEntity> TypeVariable:代表泛型类型中的字符类型,如:T t、Map<R, U>,或者Set,这种包含字符型的泛型 WildcardType:代表的是泛型类型中含有通配符?,如:Set<? extends MyEntity>或者, 或只有一个?。注意为WildcardType的前提是这个对象一定是ParameterizedType GenericArrayType:代表的范型数组。 它的组成元素是 ParameterizedType、TypeVariable、WildcardType 或者GenericArrayType

    ParameterizedType

    // <>标签 这种泛型
    @Data
    @Accessors(chain = true)
    public class ParameterizedTypeEntity {
    
        private String word;
    
        @Check
        private Map<String, DataEntity> dataEntityMap;
    }
    
    def "<>符号测试"() {
        given:
        Map<String, DataEntity> dataEntityMap = new HashMap<>();
        dataEntityMap.put("a", new DataEntity().setName(name));
        ParameterizedTypeEntity entity = new ParameterizedTypeEntity().setDataEntityMap(dataEntityMap);
    
        expect:
        boolean actResult = MkValidators.check(entity)
        if (!actResult) {
            println MkValidators.getErrMsgChain()
            println MkValidators.getErrMsg()
        }
        Assert.assertEquals(result, actResult)
    
        where:
        name | result
        "a"  | true
        "b"  | true
        "c"  | false
        "d"  | false
    }
    

    TypeVariable

    // TypeVariable:类型匹配符
    @Data
    @Accessors(chain = true)
    public class TypeVariableEntity<T> {
    
        private Integer pageNo;
        @Matcher(range = "[0, 100]", value = "null")
        private Integer pageSize;
    
        @Check
        private T data;
    
        @Check
        private List<T> dataList;
    }
    
    def "泛型类型(字符类型)测试"() {
        given:
        DataEntity dataEntity = new DataEntity().setName(name);
        TypeVariableEntity<DataEntity> entity = new TypeVariableEntity().setPageSize(pageSize).setData(dataEntity);
    
        expect:
        boolean actResult = MkValidators.check(entity)
        if (!result) {
            println MkValidators.getErrMsg()
            println MkValidators.getErrMsgChain()
        }
        Assert.assertEquals(result, actResult)
    
        where:
        pageSize | name | result
        0        | "a"  | true
        100      | "b"  | true
        200      | "a"  | false
        100      | "c"  | false
    }
    

    WildcardType

    // 通配符测试
    @Data
    @Accessors(chain = true)
    public class WildcardTypeEntity {
    
       private String wildName;
    
       @Check
       private Map<String, ? extends DataEntity> dataMap;
    }
    
    def "通配符测试"() {
        given:
        Map<String, ChildDataEntity> dataEntityMap = new HashMap<>()
        dataEntityMap.put("a", new ChildDataEntity().setNameChild(name))
        WildcardTypeEntity entity = new WildcardTypeEntity().setDataMap(dataEntityMap)
    
        expect:
        boolean actResult = MkValidators.check(entity)
        if (!actResult) {
            println MkValidators.getErrMsgChain()
            println MkValidators.getErrMsg()
        }
        Assert.assertEquals(result, actResult)
    
        where:
        name | result
        "a"  | true
        "b"  | true
        "c"  | false
        "d"  | false
    }
    

    GenericArrayType

    // 泛型数组:GenericArray
    @Data
    @Accessors(chain = true)
    public class GenericArrayTypeEntity<T> {
    
        @Check
        private T[] dataArray;
    
        @Check
        private T[][] dataArrays;
    }
    
    def "泛型数组测试"() {
        given:
        DataEntity[] dataEntities = new DataEntity[4];
        dataEntities[0] = new DataEntity().setName(name1)
        dataEntities[1] = new DataEntity().setName(name2)
        GenericArrayTypeEntity<DataEntity> entity = new GenericArrayTypeEntity().setDataArray(dataEntities)
    
        expect:
        boolean actResult = MkValidators.check(entity)
        if (!actResult) {
            println MkValidators.getErrMsgChain()
            println MkValidators.getErrMsg()
        }
        Assert.assertEquals(result, actResult)
    
        where:
        name1 | name2 | result
        "a"   | "a"   | true
        "a"   | "b"   | true
        "b"   | "a"   | true
        "b"   | "b"   | true
        "c"   | "b"   | false
        "c"   | "c"   | false
    }
    

    相关文章

      网友评论

        本文标题:Validate核查框架——Mikilin

        本文链接:https://www.haomeiwen.com/subject/kzqzahtx.html