成都网络推广建站,常州网站推,强企网做网站,网站通栏广告代码个人博客#xff1a;无奈何杨#xff08;wnhyang#xff09;
个人语雀#xff1a;wnhyang
共享语雀#xff1a;在线知识共享
Github#xff1a;wnhyang - Overview 提要
参考#xff1a;智能风控筑基手册#xff1a;全面了解风控决策引擎
前面有可配置输入参数的接…个人博客无奈何杨wnhyang
个人语雀wnhyang
共享语雀在线知识共享
Githubwnhyang - Overview 提要
参考智能风控筑基手册全面了解风控决策引擎
前面有可配置输入参数的接口如何设计和风控系统指标计算/特征提取分析与实现01Redis、Zset、模版方法两篇文章分别提出
1、风控系统服务动态选择根据配置处理输入参数转换为系统参数
2、使用Redis的zset结构完成简单的指标计算特征提取
他们都是一次风控决策流程的一部分当然完成的风控系统比较复杂涉及的功能模块更多以下仅仅是我的简单梳理。 如上服务选择和入参处理可配置输入参数的接口如何设计是这篇文章讨论的内容风控系统指标计算/特征提取分析与实现01Redis、Zset、模版方法讨论的是规则集内普通指标计算。
本篇文章讨论通过LiteFlow这款规则引擎框架实现风控系统的普通规则条件。
规则条件
规则条件是什么
我将规则划分如下未来会逐渐完善规则条件是规则的一部分。 需要注意的是规则条件应该都是灵活可配置并不是上面这样并列可以任意复杂的组合。
为什么只是规则条件灵活可配置呢操作难道不是吗
可以但没必要。
如下是规则最近24小时转账次数10次示例。 观察可知其实右半边可以作为一个新的规则独立出去的所以说规则操没必要和规则条件混在一起。 规则引擎LiteFlow
规则引擎是为了解耦编排而生。
LiteFlow官网LiteFlow简介 | LiteFlow
LiteFlow官方文档写的已经非常清晰花费不到一上午的时间就可以了完全了解了所以我也不多说些什么了。
为什么需要规则引擎
因为独立组件灵活编排的需求和规则引擎不谋而合。
设计与实现
组件
组件是规则引擎中最重要的一部分他是所有规则表达式最终业务的实现。
不使用规则引擎时
在不使用规则引擎时针对前面普通规则条件可以设计如下的结构。
一条规则关联一组规则条件规则条件又最多分为两级一级指明了二级规则条件“与或非”关系二级是具体的规则条件。具体的规则条件关键字段是系统字段property、字段类型property_data_type、操作operator、希望的值value。
有了如下的结构该怎么使用也很清晰了
1、查规则
2、查规则条件组
3、根据父条件确定子条件关系
4、代码解析操作类型返回条件结果
iduuidrule_uuidparent_uuidlogic_operatorpropertyproperty_data_typeoperatorvalue1270a8dc859a940008539f270ae596ad686cbd8adff914f67b576f0046b5b337d2bfbe53d6b5ae4895aef1c4c453e3e16e86cbd8adff914f67b576f0046b5b337d270a8dc859a940008539f270ae596ad63cf46348d533a48db8f027e4db4f6bb7a86cbd8adff914f67b576f0046b5b337dbfbe53d6b5ae4895aef1c4c453e3e16eS_N_EVENTHOURINT224f759541121664847bbc7d944ad1a553f86cbd8adff914f67b576f0046b5b337dbfbe53d6b5ae4895aef1c4c453e3e16eS_N_EVENTHOURINT245ec1e79cc18734fa4ab3daa51fe8597c886cbd8adff914f67b576f0046b5b337dbfbe53d6b5ae4895aef1c4c453e3e16eC_S_FINANCIALCLIENTSSTRING是65a3b35c7d1d04466b16ae2da64383e2186cbd8adff914f67b576f0046b5b337d270a8dc859a940008539f270ae596ad671bed61e83d7e4ad0a813f2fa3bd7b8a986cbd8adff914f67b576f0046b5b337d5a3b35c7d1d04466b16ae2da64383e21S_N_EVENTHOURINT089446d7a9ec284c1ab52c600ac1cfad2686cbd8adff914f67b576f0046b5b337d5a3b35c7d1d04466b16ae2da64383e21S_N_EVENTHOURINT69690e668a2e7c445c80b04ef5e30d3fa486cbd8adff914f67b576f0046b5b337d5a3b35c7d1d04466b16ae2da64383e21C_S_FINANCIALCLIENTSSTRING是
使用规则引擎后
首先我们定义组件可以完成字段的比较并返回true/false。
那么上面作为组件的只有id为3、4、5、7、8、9然后将这些编排成如下表达式即可。
这里简单介绍一些IF表达式一共三个参数第一个为条件后面两个为true执行false执行跟三元表达式一样。
IF(OR(AND(3,4,5),AND(7,8,9)),x,y)是不是很简单看着确实但有一个设计必须要搞通也就是下面我要说的数据上下文。
数据上下文
说明 | LiteFlow
数据上下文这个概念在LiteFlow框架中非常重要你所有的业务数据都是放在数据上下文中。
要做到可编排一定是消除每个组件差异性的。如果每个组件出参入参都不一致那就没法编排了。
LiteFlow对此有独特的设计理念平时我们写瀑布流的程序时A调用B那A一定要把B所需要的参数传递给B而在LiteFlow框架体系中每个组件的定义中是不需要接受参数的也无任何返回的。
每个组件只需要从数据上下文中获取自己关心的数据即可而不用关心此数据是由谁提供的同样的每个组件也只要把自己执行所产生的结果数据放到数据上下文中即可也不用关心此数据到底是提供给谁用的。这样一来就从数据层面一定程度的解耦了。从而达到可编排的目的。关于这个理念也在LiteFlow简介中的设计原则有提到过给了一个形象的例子大家可以再去看看。
一旦在数据上下文中放入数据整个链路中的任一节点都是可以取到的。
我简单说明一下。
如下表示瀑布流程从开始到结束每步调用都需要将数据传递给下一步调用者完成整个流程。 而对于LiteFlow更像是下面这样整个流程存在这样的数据上下文每个组件只需要去数据上下文中取自己关心的数据结果也是一样放进数据上下文即可。 此模式下要非常注重数据上下文的管理数据隔离和共享要非常注意。
相同组件数据问题
对于规则条件组件的问题在于每个规则里的条件非常多组件该怎么获取当前组件参数如appNamePhone。如IF(OR(AND(3,4,5),AND(7,8,9)),x,y)此表达式转换为我们使用的规则表达式应该是这样的IF(OR(AND(ruleConditionIF,ruleConditionIF,ruleConditionIF),AND(ruleConditionIF,ruleConditionIF,ruleConditionIF)),x,y)ruleConditionIF为规则条件组件。一个表达式中有多个相同的组件意味着他们需要不同的处理那么数据怎么获取
LiteFlow提供了三种不同方式
1、组件参数 | LiteFlow定义EL表达式时声明数据并传入组件
2、组件标签 | LiteFlow定义组件tag区别组件
3、私有投递 | LiteFlow用于私有独有数据传递
下面使用方式二组件标签来实现普通条件组件。
表结构与数据
chain表
create table de_chain
(id bigint auto_increment comment 主键primary key,application_name varchar(32) default not null comment 应用名,chain_name varchar(64) default not null comment chain名,el_data text not null comment el数据,enable bit default b0 not null comment chain状态,description varchar(64) charset utf8mb4 default null comment 描述,creator varchar(64) charset utf8mb4 default null comment 创建者,create_time datetime default CURRENT_TIMESTAMP not null comment 创建时间,updater varchar(64) charset utf8mb4 default null comment 更新者,update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment 更新时间,deleted bit default b0 not null comment 是否删除,constraint uk_codeunique (chain_name)
)comment chain表;INSERT INTO coolGuard.de_chain (id, application_name, chain_name, el_data, enable, description, creator, create_time, updater, update_time, deleted) VALUES (1, coolGuard, mainChain, THEN(chain1);, true, , , 2024-04-04 22:35:36, , 2024-04-04 22:40:30, false);
INSERT INTO coolGuard.de_chain (id, application_name, chain_name, el_data, enable, description, creator, create_time, updater, update_time, deleted) VALUES (2, coolGuard, chain1, IF(OR(AND(ruleConditionIf.tag(1),ruleConditionIf.tag(2)),ruleConditionIf.tag(3)),orderMode,worstMode);, true, , , 2024-04-04 22:31:16, , 2024-04-05 13:25:49, false);规则条件表
create table de_rule_condition
(id bigint auto_increment comment 主键primary key,chain_name varchar(64) default not null comment chain名,field_name varchar(32) default not null comment 字段名,operate_type int default 0 not null comment 操作类型,expect_value varchar(32) default not null comment 期望值,description varchar(64) charset utf8mb4 default null comment 描述,creator varchar(64) charset utf8mb4 default null comment 创建者,create_time datetime default CURRENT_TIMESTAMP not null comment 创建时间,updater varchar(64) charset utf8mb4 default null comment 更新者,update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment 更新时间,deleted bit default b0 not null comment 是否删除
)comment 规则条件表;INSERT INTO coolGuard.de_rule_condition (id, chain_name, field_name, operate_type, expect_value, description, creator, create_time, updater, update_time, deleted) VALUES (1, chain3, appName, 2, Phone, , , 2024-04-04 22:32:25, , 2024-04-05 12:24:03, false);
INSERT INTO coolGuard.de_rule_condition (id, chain_name, field_name, operate_type, expect_value, description, creator, create_time, updater, update_time, deleted) VALUES (2, chain4, customerId, 2, 123456, , , 2024-04-05 12:24:03, , 2024-04-05 12:24:03, false);
INSERT INTO coolGuard.de_rule_condition (id, chain_name, field_name, operate_type, expect_value, description, creator, create_time, updater, update_time, deleted) VALUES (3, chain5, money, 5, 15, , , 2024-04-05 12:24:03, , 2024-04-05 12:24:03, false);依赖
dependencygroupIdcom.yomahub/groupIdartifactIdliteflow-spring-boot-starter/artifactIdversion${liteflow.version}/version
/dependency
dependencygroupIdcom.yomahub/groupIdartifactIdliteflow-rule-sql/artifactIdversion${liteflow.version}/version
/dependency
dependencygroupIdcom.yomahub/groupIdartifactIdliteflow-script-groovy/artifactIdversion${liteflow.version}/version
/dependency配置
liteflow:rule-source-ext-data-map:url: jdbc:mysql://localhost:3306/coolGuard?allowPublicKeyRetrievaltrueserverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf-8zeroDateTimeBehaviorconvertToNulluseSSLfalseallowPublicKeyRetrievaltruedriverClassName: com.mysql.cj.jdbc.Driverusername: wnhyangpassword: 123456applicationName: ${spring.application.name}#是否开启SQL日志sqlLogEnabled: true#是否开启SQL数据轮询自动刷新机制 默认不开启pollingEnabled: truepollingIntervalSeconds: 60pollingStartSeconds: 60#以下是chain表的配置这个一定得有chainTableName: de_chainchainApplicationNameField: application_namechainNameField: chain_nameelDataField: el_datachainEnableField: enable#以下是script表的配置如果你没使用到脚本下面可以不配置# scriptTableName: script
# scriptApplicationNameField: application_name
# scriptIdField: script_id
# scriptNameField: script_name
# scriptDataField: script_data
# scriptTypeField: script_type
# scriptLanguageField: script_language
# scriptEnableField: enable自定义数据上下文
可以改善先这样贴出来。
FieldContext表示经过入参处理后的所有字段集合后面的规则/指标都会用到的。
/*** author wnhyang* date 2024/4/3**/
public class FieldContext {private final MapString, String stringFields new ConcurrentHashMap();private final MapString, Integer numberFields new ConcurrentHashMap();private final MapString, Boolean booleanFields new ConcurrentHashMap();private final MapString, String enumFields new ConcurrentHashMap();private final MapString, LocalDateTime dateFields new ConcurrentHashMap();private final MapString, BigDecimal floatFields new ConcurrentHashMap();public void setStringData(String key, String value) {stringFields.put(key, value);}public String getStringData(String key) {return stringFields.get(key);}public boolean hasStringData(String key) {return stringFields.containsKey(key);}}普通规则条件组件
当前还不是很完善TODO已有说明。
对了我使用的jdk17所有switch表达式是下面这样与jdk8有点区别。
/*** author wnhyang* date 2024/4/4**/
Slf4j
LiteflowComponent
RequiredArgsConstructor
public class RuleConditionIf extends NodeIfComponent {private final RuleConditionMapper ruleConditionMapper;Overridepublic boolean processIf() throws Exception {// 获取当前chainNameString tag this.getTag();log.info(当前tag:{}, tag);// 获取当前chainName对应的条件RuleCondition ruleCondition ruleConditionMapper.selectById(tag);log.info(当前chainName对应的条件:{}, ruleCondition);// 获取上下文FieldContext fieldContext this.getContextBean(FieldContext.class);// 获取条件字段String fieldName ruleCondition.getFieldName();log.info(条件字段:{}, fieldName);// 获取字段值// TODO 支持String、Integer、BigDecimal、Boolean等String stringData fieldContext.getStringData(fieldName);log.info(字段值:{}, stringData);OperateType byType OperateType.getByType(ruleCondition.getOperateType());log.info(操作类型:{}, byType);// TODO 当前是常量之后要考虑变量String expectValue ruleCondition.getExpectValue();log.info(期望值值:{}, expectValue);return switch (Objects.requireNonNull(byType)) {case NULL:yield StrUtil.isBlank(stringData);case NOT_NULL:yield !StrUtil.isBlank(stringData);case EQ:yield stringData.equals(expectValue);case NOT_EQ:yield !stringData.equals(expectValue);case CONTAINS:yield stringData.contains(expectValue);case NOT_CONTAINS:yield !stringData.contains(expectValue);case GT:yield Integer.parseInt(stringData) Integer.parseInt(expectValue);case GTE:yield Integer.parseInt(stringData) Integer.parseInt(expectValue);case LT, LTE:yield false;case IN:String[] split1 expectValue.split(,);for (String s : split1) {if (stringData.equals(s)) {yield true;}}case NOT_IN:String[] split2 expectValue.split(,);for (String s : split2) {if (stringData.equals(s)) {yield false;}}case PREFIX:yield stringData.startsWith(expectValue);case NOT_PREFIX:yield !stringData.startsWith(expectValue);case SUFFIX:yield stringData.endsWith(expectValue);case NOT_SUFFIX:yield !stringData.endsWith(expectValue);};}
}操作类型枚举
/*** author wnhyang* date 2024/4/3**/
AllArgsConstructor
Getter
public enum OperateType {NULL(0),NOT_NULL(1),EQ(2),NOT_EQ(3),GT(4),GTE(5),LT(6),LTE(7),IN(8),NOT_IN(9),CONTAINS(10),NOT_CONTAINS(11),PREFIX(12),NOT_PREFIX(13),SUFFIX(14),NOT_SUFFIX(15);private final Integer type;public static OperateType getByType(Integer type) {for (OperateType operateType : OperateType.values()) {if (operateType.getType().equals(type)) {return operateType;}}return null;}
}测试
Slf4j
RestController
RequestMapping(/field)
RequiredArgsConstructor
public class FieldController {private final FieldService fieldService;private final FlowExecutor flowExecutor;GetMapping(/test)public CommonResultString test(RequestParam(appName) String appName, RequestParam(customerId) String customerId, RequestParam(money) String money) {FieldContext fieldContext new FieldContext();fieldContext.setStringData(appName, appName);fieldContext.setStringData(customerId, customerId);fieldContext.setStringData(money, money);LiteflowResponse main1 flowExecutor.execute2Resp(mainChain, null, fieldContext);log.info(String.valueOf(main1));return success(test);}
}结果
mainChain的EL表达式为THEN(chain1);chain1为子流程
IF(OR(AND(ruleConditionIf.tag(1),ruleConditionIf.tag(2)),ruleConditionIf.tag(3)),orderMode,worstMode);ruleConditionIf为上面的规则条件组件。
最终应该是这样的THEN(IF(OR(AND(ruleConditionIf.tag(1),ruleConditionIf.tag(2)),ruleConditionIf.tag(3)),orderMode,worstMode)); 参数为appName:Phone,customerId:235246,money:35时
此时执行流程为ruleConditionIf17ruleConditionIf2ruleConditionIf2orderMode0
参数为appName:Phone,customerId:235246,money:3时
此时执行流程为ruleConditionIf42ruleConditionIf2ruleConditionIf1worstMode0 总结
LiteFlow可玩性还是很强的未来我还会继续完善打造自己设计并实现的风控系统。冲冲冲
写在最后
拙作艰辛字句心血望诸君垂青多予支持不胜感激。 个人博客无奈何杨wnhyang
个人语雀wnhyang
共享语雀在线知识共享
Githubwnhyang - Overview