# chungkui **Repository Path**: csdn-zeng8750_admin/chungkui ## Basic Information - **Project Name**: chungkui - **Description**: 钟馗是一款java数据校验框架,支持注解式数据校验,可以对系统中的各种接口,请求进行,入参校验,流控,防重等校验。 同时支持运行时动态改变校验规则;和spring注解式数据校验的区别是,校验规则纯文本 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 6 - **Created**: 2023-04-23 - **Last Updated**: 2023-04-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 钟馗 #### 介绍 钟馗是一款java数据校验框架,支持注解式数据校验,可以对系统中的各种接口,请求进行,入参校验,流控,防重等校验。 同时支持运行时动态改变校验规则;和spring注解式数据校验的区别是,校验规则纯文本化,方便校验逻辑集中在一处进行配置管理, 更像是一种语法糖 。 目前支持的校验模式有spring mvc模式,方法单个map入参模式,方法单个json入参模式,方法列表模式等等 ; 在当下前后端分离,微服务,api接口越来越常见的背景下,希望你会喜欢这样一款校验插件; 首先我不建议你直接读文档,建议你下载下来项目,运行demo中的applicat#main 访问:127.0.0.1:8080 体验下使用钟馗的方式和效果,看看是否喜欢,然后决定是否继续读文档 # 一、如何使用: 可以参考demo项目 ## 第一步:初始化 引入jar包 ```xml com.chungkui check 0.0.7.2-RELEASE ``` 1.实现AbstractCheckConfigCacheService,2.继承ChungkuiCheckConfig类并把bean注入spring容器;例如注解式配置如下(xml模式同理): ```java public class CheckConfigCacheServiceImpl extends AbstractCheckConfigCacheService { public void change() { clear(); } @Override public String remoteGet(String s, String s1) { return null; } } @Configuration public class CheckConfig extends ChungkuiCheckConfig { @Bean ExpressionsCacheService expressionsCacheService() { return new CheckConfigCacheServiceImpl(); } } ``` ## 第二步:配置校验规则 在方法上面加上@Check注解,配置格式为json;如下: ```java public class controller { @Check({"{rule:'long(a)+long(b)>0',msg:{fail:'不通过',error:'数据格式不正确'},code:'500'}" ,"{param:'a',type:'int',min:1,max:2,msg:'int不通过'}"}) @RequireMaping public Object methodName(){ return "jason"; } } ``` # 二、校验规则具体配置方法 有两种配置方式: #### a.通过注解配置项value 该配置项为一个数组。每个参数是数组的一个元素。方便复用校验配置 #### b.通过注解配置项rules 整个配置项为一个json字符串,复用性差。但是方便进行远程管理所有配置项。有问题时方便拷贝整个配置进行修改 ### 1.表达式校验:aviator ```java @Check({"{condition:'str.isNotEmpty(a)',rule:'long(a)>long(b)',msg:{fail:'校验不通过',error:'数字格式不正确'},code:500}"}) ``` #### 配置项含义: | key | 描述 | | ------------ | ------------ | | type | 校验类型不配置表示aviator | | rule | 表达式具体配置方式参考官网 | | min | 最小长度 | | max | 最大长度 | | msg | 校验失败提醒 | | code | 失败编码 | | condition | 触发条件,当表达式为true时才会校验 | #### 提示项含义 配置方法为把msg配置为json格式,如下。 ```java @Check({"{rule:'long(a)>long(b)',msg:{fail:'校验不通过',error:'数字格式不正确'},code:500}"}) ``` | key | 描述 | default| | ------------ | ------------ |------------ | | fail | 校验不通过 | 无| | error | 数据格式不正确报错 |无| ### 2.非空校验:require ```java @Check({"{type:'require',min:1,max:2,param:'c',msg:'require不通过'}"}) ``` #### 配置项含义: | key | 描述 | | ------------ | ------------ | | type | 校验类型 | | rule | 校验规则 | | param | 参数名 | | min | 最小长度 | | max | 最大长度 | | msg | 校验失败提醒 | | code | 失败编码 | | condition | 触发条件,当表达式为true时才会校验 | #### 智能提示 配置方法为把msg配置为json格式,如下。 ```java @Check({"{type:'require',min:1,max:2,param:'c',msg:{min:'长度不可小于1',max:'长度不可大于20',null:'不可以为空值'}}"}) ``` | key | 描述 | default| | ------------ | ------------ |------------ | | min | 最小值不满足提示 | 最小值超限| | max | 最大值不满足提示 |最大值超限| | null | 格式不满足提示 |参数必填| ### 3.数字校验:int/double/long #### 规则格式 ```java @Check({ "{type:'int/double/long',min:1,max:2,param:'a',msg:'int不通过'}"}) ``` #### 配置项含义: | key | 描述 | | ------------ | ------------ | | min | 最小值 | | rule | 校验规则 | | max | 最大值 | | msg | 校验失败提醒 | | code | 失败编码 | | require | 是否必填,true时候校验必填,fasle如果为空则不会校验值是否合法 | | condition | 触发条件,当表达式为true时才会校验 | #### 智能提示 配置方法为把msg配置为json格式,如下。 ```java @Check({ "{type:'int/double/long',min:1,max:2,param:'paramName',msg:{min:'不可小于1',max:'不可大于20',fai:'数字不合法'}}"}) ``` | key | 描述 | default| | ------------ | ------------ | ------------ | | min | 最小值不满足提示 | 最小值超限| | max | 最大值不满足提示 |最大值超限| | fail | 格式不满足提示 |参数不合法| ### 4.json数组校验:jsonArray ```java @Check({"{type:'jsonArray',min:1,max:2,param:'c',msg:'require不通过'}"}) ``` #### 配置项含义: | key | 描述 | | ------------ | ------------ | | type | 校验类型 | | rule | 校验规则 | | param | 参数名 | | min | 最小个数 | | max | 最大个数 | | msg | 校验失败提醒 | | code | 失败编码 | | condition | 触发条件,当表达式为true时才会校验 | #### 智能提示 配置方法为把msg配置为json格式,如下。 ```java @Check({"{type:'jsonArray',min:1,max:2,param:'c',msg:{min:'长度不可小于1',max:'长度不可大于20',null:'不可以为空值'}}"}) ``` | key | 描述 | default| | ------------ | ------------ |------------ | | min | 最小值不满足提示 | 元素个数不足| | max | 最大值不满足提示 |元素个数超限| | null | 格式不满足提示 |参数必填| ### 5.日期校验:date ```java @Check({"{type:'date',fmt:'yyyyMMdd HHmmss',before:2,after:3,param:'startTime',msg:'日期非法'}"}) ``` #### 配置项含义: | key | 描述 | | ------------ | ------------ | | type | 校验类型 | | rule | 校验规则 | | param | 参数名 | | fmt | 日期格式 | | before | 早于当前时间毫秒数 | | after | 晚于当前时间毫秒数 | | code | 失败编码 | | condition | 触发条件,当表达式为true时才会校验 | ### 6.日期跨度校验:dateInterval ```java @Check({"{type:'dateInterval',fmt:'yyyyMMdd HHmmss',maxInterval:2,minInterval:3,start:'startTime',end:'endTime',msg:{minInterval:'时间间隔太小',maxInterval:'时间间隔太长'}}"}) ``` #### 配置项含义: | key | 描述 | | ------------ | ------------ | | type | 校验类型 | | start | 开始时间参数名 | | end | 结束时间参数名 | | require | 是否必填 | | param | 参数名 | | fmt | 日期格式 | | maxInterval | 最大跨度毫秒数 | | minInterval | 最小跨度毫秒数 | | code | 失败编码 | | condition | 触发条件,当表达式为true时才会校验 | ### 6.防重校验 #### 开启方式为 实现ReSumitWallCacheService接口实现。并注入spring中。推荐实现时使用redis. 例如: ``` @Service public class ReSumitWallCacheServiceImpl implements ReSumitWallCacheService { @Override public boolean set(String s, String s1, int expire) { return CacheUtils.set(s, "01", expire, CacheUtils.ExPx.EX, CacheUtils.NxXx.NX); } } ``` 使用时配置项为reSubmitWall,配置格式如下。 调用方需要在入参中传入reSubmitWall(一个防重的token值,业务系统自发生成保证适当场景的唯一性。) ``` @Check(reSubmitWall = "{expire:2,prefix:'缓存前缀',msg:{null:'reSubmitWall不可传空',fail:'请勿重复提交'}}") ``` #### 配置项含义: | key | 描述 | | ------------ | ------------ | | expire | 防重token过期时间 | | msg | 校验失败提醒 | | prefix | token前缀,prefix=off表示关闭重复提交校验(开启配置中心时可用) | #### msg配置项含义: | key | 描述 | | ------------ | ------------ | | null | token为空提醒 | | fail | 重复提交提醒 | ### 6.单机版流控校验 #### 配置方式为注解中的rateLimters项 ```java @Check(rateLimters = {"{rate:1,msg:'流控',code:500}"}) ``` #### 配置项含义: | key | 描述 | | ------------ | ------------ | | rate | 流控tps值 | | model | 模式,动态:dynamic,静态:不配置 | | param | 动态流控参数 | | sync | 是否直接阻塞方式执行;true,false,默认为false;为true时直接阻塞直到获得令牌,慎用 | | warmupPeriod | 从冷却状态到达最大速率所需时间(s) | | waitLimit | 取令牌等待(阻塞)时间(s)默认0;sync为false时候才有效;为 0的时候取不到令牌直接返回; | | createRate | 单位时间内动态流控对象生成速率 | | code | 失败编码 | | msg | 校验失败提醒 | | code | 失败编码 | ### 7.集群版流控校验 #### 配置方式为注解中的redisRateLimters项 ```java @Check(redisRateLimters={"{capacity:1,rate:1,perUser:1,msg:'您手速太快了,请稍后再试',code:500}"} ``` #### 配置项含义: | key | 描述 | | ------------ | ------------ | | rate | 流控tps值 | | param | 动态流控参数 | | capacity | 令牌桶容量 | | perUser | 每次消耗令牌个数 | | code | 失败编码 | | msg | 校验失败提醒 | | code | 失败编码 | # 三、自定义msg格式 自定义格式注解配置项为msgTemp,格式必须为json,通过占位符的方式进行自定义 默认模板为{paramName:'#param',value:'#val',code:'#code',message:'#msg'} 例子: ```java @Check(msgTemp="{paramName:'#param',val:'#val',code:'#code',msg:'#msg'}") ``` | 值 | 描述 | | ------------ | ------------ | | #param | 入参名 | | #val | 入参值 | | #code | 失败编码 | | #msg | 描述值 | # 四、校验模式 目前支持三种校验模式mvc,mvc_html,args 默认的会自动探测校验模式,建议手动指定校验模式,效率更快。方法如下: 在注解中添加model项,可从枚举中获取支持的模式 ```java @Check(model = Model.MVC); ``` ##### 1.mvc模式: 校验request中的入参,校验失败无侵入代码直接响应json给客户端 ##### 2.mvc_html模式(不推荐): 校验request中的入参,校验失败,会缓存失败详情,可以通过如下方式获取校验结果,该种方式不会直接返回方法, 需要人工接收校验结果。使用场景是校验非ajax请求的场景 ##### 1.args模式: 从参数列表中取参数 ```java boolean result=CheckResultContainer.check();//来在代码中获取是否校验通过; Object msg=CheckResultContainer.getFailMsg();//来在代码中获取校验失败消息; ``` # 五、使用远程动态配置 使用远程配置,可以运行时修改校验规则,并且进行服务降级和流控等操作!(一般系统可能用不到的,可以不看) ## 如何配置: 自己继承AbstractExpressionsCacheService抽象类注入sping容器即可,实现remoteGet方法;获取远程配置需要你自己实现,这里返回远程配置的值即可;可以是从数据库查询的方式,或者配置中心获取的方式; 然后在你的配置改变之后请自行调用该bean类中的clear()方法; ```java public class DefaultExpressionsCacheServiceImpl extends AbstractExpressionsCacheService { @Override public String remoteGet(String s, String s1) { return "返回数据库查询的值,或者配置中心的值即可"; } //当配置变化的时候自行调用clear()或者remoteSetCallBack(String remote, String val); public void remoteChange(String remote, String val) { //当配置变化的时候调用clear方法或者remoteSetCallBack清空或者刷新缓存 clear(); } } ``` ## 使用 a.注解上添加上remote配置项,指定远程的key,格式如下 ``` @Check(remote="scm_validatekey"...) ``` b.然后你需要在你自己的服务中心(根据自己系统的实现做具体配置,可能是数据库配置,可能是配置中心配置),配置格式规则如下:和代码中配置类似,也是json格式 ``` scm_validatekey={value:"[{type:'int/double/long',rule:{min:1,max:2,param:'a'},msg:{min:'不可小于1',max:'不可大于20',fail:'数字不合法'}}]"} ``` ## 进行服务降级 同动态校验配置类似,在远端配置demoteConfig项即可 ### 配置中心 ``` scm_validatekey={demoteConfig:"{open:true,msg:'服务暂时不可使用请稍后重试',code:1000}"} ``` ### 配置项含义 | 值 | 描述 | | ------------ | ------------ | | open | 是否开启降级,true表示开启 | | msg | 降级触发拦截的时候的提示消息 | | code | 降级触发拦截的时候的异常码 | ## 远程配置流控rateLimters ### 单机版 @Check(rateLimters = {"{rate:1,msg:'流控',code:500}"}) 远程配置trafficControlConfig项即可 ``` scm_validatekey={rateLimters : "{rate:100,param:'userName',msg:'流控命中服务暂时不可使用请稍后重试',code:10}"} ``` 配置项含义 | 值 | 描述 | | ------------ | ------------ | | rate | 流控tps值 | | param | 不为空表示根据参数动态流控,动态流控的时候会根据该参数进行动态流控 | | sync | 是否直接阻塞方式执行;true,false,默认为false;为true时直接阻塞直到获得令牌,慎用 | | warmupPeriod | 从冷却状态到达最大速率所需时间(s) | | waitLimit | 取令牌等待(阻塞)时间(s)默认0;sync为false时候才有效;为 0的时候取不到令牌直接返回; | | code | 失败编码 | | msg | 校验失败提醒 | | code | 失败编码 | # 六、自定义流控 ### 开启自定义流控 开启流控需要在spring 容器中实现TrafficControlService,并注入到spring容器中。 例如: ```java @Service public class TrafficControlServiceImpl implements TrafficControlService { @Autowired private FlowControllerService flowControllerService; @Override public boolean check(String s) { return flowControllerService.check(s); } @Override public boolean dynamicCheck(String s, String s1) { return flowControllerService.dynamicCheck(s, s1); } } ``` ### 使用自定义流控 同动态校验配置类似,在配置中心配置项中添加trafficControlConfig; ### 在远程服务中配置 ``` scm_validatekey={trafficControlConfig : "{key:'flow_control_key',param:'userName',msg:'流控命中服务暂时不可使用请稍后重试',code:1000}"} ``` ### 配置项含义 | 值 | 描述 | | ------------ | ------------ | | key | 表示流控的配置中心key | | param | 不为空表示根据参数动态流控,动态流控的时候会根据该参数进行动态流控 | | msg | 流控触发拦截的时候的提示消息 | | code | 流控触发拦截的时候的异常码 | # 七、参数为对象(json对象或者json数组等等)时的校验 可以对对象类型的数据中的字段进行校验;需要指定rules来进行校验,目前支持"(json),[json],javaBean,集合类型的对象等等"。 这里最初设计的时候我误入了一个误区,想要实现无限级嵌套,校验实现之后发现完全不好用,配置起来非常复杂。所以后来讨论后决定不做无限极嵌套, 如果你入参中对象深度较大。首先你应当考虑的是接口设计是否合理,如果必须这样做,那么建议你解析完成第一层后在另一个方法上做第二层的校验; 这样才是合理的。这是我折腾了很久之后想到的一点感悟;简单才是合理的。 (json)表示该对象是json对象[json]表示该对象是一个json数组 配置方式为: ```java //json对象 @Check(rules = "[{param:'userinfo',type:'(json)',msg:{error:'json格式不正确',null:'手机不可为空'},code:'500',rules:[{param:'username',type:'require',code:500,min:2,max:5,msg:{min:'手机名称太短',max:'手机名称太长',null:'不可为空'}}]}]") //json数组 @Check(rules = "[{param:'param',type:'[json]',min:1,max:5,code:'500',msg:{min:'数组长度不足',max:'超长了',null:'数组不可为空'}, rules:[{param:'a',type:'require',min:2,max:5,msg:{min:'长度不足',max:'超长了',null:'不可为空'},code:500}]}]") ``` # 八、注意事项 ajax请求mvc的时候mvc返回值请使用Object,或者Map否则会出现类型转换异常 # 九、开发计划 开发辅助配置页面 # 十、架构: 默认规则引擎为aviator表达式引擎,另外提供必填和数字类型的定制化表达式引擎,定制化引擎支持更加完善的消息体系; 支持单个入参校验和多个参数之间进行表达式校验。 性能上通过把表达式预编译,放入LRU热点缓存的方式提高校验性能。 #### 软件校验采用校验链的方式 校验链目前有四种。顺序分别为: 降级校验->流控校验->防重校验->参数校验;每个链路是否执行是可选的 #### 两大引擎 规则解析引擎, 参数解析引擎 #### 一个构造器 消息构造器