Validate核查框架——Mikilin
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
}
注意:
- 如果设置的类型不是属性的类型或者父类则会报错
- 如果为具体的类型,则再设置与其相同的类型,则没有必要,就像上面的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
}