风控系统之普通规则条件,使用LiteFlow实现

个人博客:无奈何杨(wnhyang)

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview


提要

参考:智能风控筑基手册:全面了解风控决策引擎

前面有可配置输入参数的接口如何设计和风控系统指标计算/特征提取分析与实现01,Redis、Zset、模版方法两篇文章,分别提出:

1、风控系统服务动态选择,根据配置处理输入参数,转换为系统参数

2、使用Rediszset结构完成简单的指标计算(特征提取)

他们都是一次风控决策流程的一部分,当然完成的风控系统,比较复杂,涉及的功能模块更多,以下仅仅是我的简单梳理。

如上,服务选择和入参处理可配置输入参数的接口如何设计是这篇文章讨论的内容,风控系统指标计算/特征提取分析与实现01,Redis、Zset、模版方法讨论的是规则集内普通指标计算。

本篇文章讨论通过LiteFlow这款规则引擎框架实现风控系统的普通规则条件。

规则条件

规则条件是什么?

我将规则划分如下(未来会逐渐完善),规则条件是规则的一部分。

需要注意的是规则条件应该都是灵活可配置,并不是上面这样并列,可以任意复杂的组合。

为什么只是规则条件灵活可配置呢?操作难道不是吗?

可以,但没必要。

如下,是规则(最近24小时转账次数>=10次)示例。

观察可知,其实右半边可以作为一个新的规则独立出去的,所以说,规则操没必要和规则条件混在一起。

规则引擎LiteFlow

规则引擎是为了解耦,编排而生。

LiteFlow官网:🍤LiteFlow简介 | LiteFlow

LiteFlow官方文档写的已经非常清晰,花费不到一上午的时间就可以了完全了解了,所以我也不多说些什么了。

为什么需要规则引擎

因为独立组件+灵活编排的需求和规则引擎不谋而合。

设计与实现

组件

组件是规则引擎中最重要的一部分,他是所有规则表达式最终业务的实现。

不使用规则引擎时

在不使用规则引擎时,针对前面普通规则条件,可以设计如下的结构。

一条规则关联一组规则条件,规则条件又最多分为两级,一级指明了二级规则条件“与或非”关系,二级是具体的规则条件。具体的规则条件关键字段是:系统字段(property)、字段类型(property_data_type)、操作(operator)、希望的值(value)。

有了如下的结构该怎么使用也很清晰了

1、查规则

2、查规则条件组

3、根据父条件,确定子条件关系

4、代码解析操作类型,返回条件结果

iduuidrule_uuidparent_uuidlogic_operatorpropertyproperty_data_typeoperatorvalue
1270a8dc859a940008539f270ae596ad686cbd8adff914f67b576f0046b5b337d
2bfbe53d6b5ae4895aef1c4c453e3e16e86cbd8adff914f67b576f0046b5b337d270a8dc859a940008539f270ae596ad6&&
3cf46348d533a48db8f027e4db4f6bb7a86cbd8adff914f67b576f0046b5b337dbfbe53d6b5ae4895aef1c4c453e3e16eS_N_EVENTHOURINT>=22
4f759541121664847bbc7d944ad1a553f86cbd8adff914f67b576f0046b5b337dbfbe53d6b5ae4895aef1c4c453e3e16eS_N_EVENTHOURINT<=24
5ec1e79cc18734fa4ab3daa51fe8597c886cbd8adff914f67b576f0046b5b337dbfbe53d6b5ae4895aef1c4c453e3e16eC_S_FINANCIALCLIENTSSTRING==
65a3b35c7d1d04466b16ae2da64383e2186cbd8adff914f67b576f0046b5b337d270a8dc859a940008539f270ae596ad6&&
71bed61e83d7e4ad0a813f2fa3bd7b8a986cbd8adff914f67b576f0046b5b337d5a3b35c7d1d04466b16ae2da64383e21S_N_EVENTHOURINT>=0
89446d7a9ec284c1ab52c600ac1cfad2686cbd8adff914f67b576f0046b5b337d5a3b35c7d1d04466b16ae2da64383e21S_N_EVENTHOURINT<6
9690e668a2e7c445c80b04ef5e30d3fa486cbd8adff914f67b576f0046b5b337d5a3b35c7d1d04466b16ae2da64383e21C_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,更像是下面这样,整个流程存在这样的数据上下文,每个组件只需要去数据上下文中取自己关心的数据,结果也是一样,放进数据上下文即可。

此模式下,要非常注重数据上下文的管理,数据隔离和共享要非常注意。

相同组件数据问题

对于规则条件组件的问题在于:每个规则里的条件非常多,组件该怎么获取当前组件参数(如:appName==Phone)。如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 b'0'              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 b'0'              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 b'0'              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);

依赖

<dependency><groupId>com.yomahub</groupId><artifactId>liteflow-spring-boot-starter</artifactId><version>${liteflow.version}</version>
</dependency>
<dependency><groupId>com.yomahub</groupId><artifactId>liteflow-rule-sql</artifactId><version>${liteflow.version}</version>
</dependency>
<dependency><groupId>com.yomahub</groupId><artifactId>liteflow-script-groovy</artifactId><version>${liteflow.version}</version>
</dependency>

配置

liteflow:rule-source-ext-data-map:url: jdbc:mysql://localhost:3306/coolGuard?allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=truedriverClassName: 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 Map<String, String> stringFields = new ConcurrentHashMap<>();private final Map<String, Integer> numberFields = new ConcurrentHashMap<>();private final Map<String, Boolean> booleanFields = new ConcurrentHashMap<>();private final Map<String, String> enumFields = new ConcurrentHashMap<>();private final Map<String, LocalDateTime> dateFields = new ConcurrentHashMap<>();private final Map<String, 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 CommonResult<String> 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");}
}

结果

mainChainEL表达式为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

此时执行流程为:ruleConditionIf<17>==>ruleConditionIf<2>==>ruleConditionIf<2>==>orderMode<0>

参数为:appName:Phone,customerId:235246,money:3

此时执行流程为:ruleConditionIf<42>==>ruleConditionIf<2>==>ruleConditionIf<1>==>worstMode<0>

总结

LiteFlow可玩性还是很强的,未来我还会继续完善打造自己设计并实现的风控系统。冲冲冲!!!

写在最后

拙作艰辛,字句心血,望诸君垂青,多予支持,不胜感激。


个人博客:无奈何杨(wnhyang)

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/593705.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

短视频素材在哪里找?八大平台解决你的创作需求

大家好&#xff01;在短视频创作的过程中&#xff0c;寻找优质的素材网站是非常重要的。那么&#xff0c;短视频素材在哪里找呢&#xff1f;推荐八个主流的视频素材分享网站&#xff0c;话不多说直接上干货。 蛙学网&#xff08;waxue.com&#xff09;——中国 首先要推荐的是…

上位机图像处理和嵌入式模块部署(qmacvisual查找圆缺角)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们讲过识别&#xff0c;讲过标定&#xff0c;讲过测量&#xff0c;讲过匹配&#xff0c;但就是没有讨论过基于图像的产品检测。但事实上&…

干货分享!AI 免费线上去除图片水印,一键自动去图片水印

发现了一个好的在线除水印的网站&#xff0c;这里由「易极赞」的小编来分享给大家。它就是我们今天的主角Dewatermark。 近期在协助客户处置媒体图片的素材时&#xff0c;察觉到部分素材里面附有旧版的水印内容。鉴于原始图片已无从查考&#xff0c;只能够运用带有水印的图片予…

如何实现仿微信界面[我的+首页聊天列表+长按菜单功能+添加菜单功能]

如何实现仿微信界面[我的首页聊天列表长按菜单功能添加菜单功能] 一、简介 如何实现仿微信界面[我的首页聊天列表长按菜单功能添加菜单功能] 采用 uni-app 实现&#xff0c;可以适用微信小程序、其他各种小程序以及 APP、Web等多个平台 具体实现步骤如下&#xff1a; 下载…

二、GitLab相关操作

GitLab相关操作 一、组、用户、项目管理1.创建组2.创建项目3.创建用户并分配组3.1 创建用户3.2 设置密码3.3 给用户分配组 二、拉取/推送代码1.配置ssh(第一次需要)1.1 创建一个空文件夹1.2 配置本地仓账号和邮箱1.3 生成ssh公钥密钥1.4 gitlab配置公钥 2.拉取代码3.推送代码3.…

面对无所不能的AI,我们能做什么?

在科技日新月异的今天,人工智能(AI)的发展速度令人瞩目。我们时常会听到这样的说法:“你会的AI都会,你不会的AI也会。”这似乎预示着AI在不久的将来将无所不能,甚至可能超越人类的智慧。然而,即便面临这样的情境,我们依然能够找到自身的价值和位置,与AI和谐共生,共同…

方格取数

题目描述 设有 NN 的方格图 (N≤9)&#xff0c;我们将其中的某些方格中填入正整数&#xff0c;而其他的方格中则放入数字 00。如下图所示&#xff08;见样例&#xff09;: 某人从图的左上角的 A 点出发&#xff0c;可以向下行走&#xff0c;也可以向右走&#xff0c;直到到达右…

基于Java的高校教学业绩信息管理系统论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本高校教学业绩信息管理系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的…

ComfyUI ClipSeg插件报错- resize_image出错应该怎么办

上一篇刚介绍了这个插件&#xff0c;结果emm..很快发现事情并不简单...结果又报错了。 后台报错信息&#xff1a; Unused or unrecognized kwargs: padding. !!! Exception during processing !!! Traceback (most recent call last): File "F:\ComfyUI-aki\execution.p…

小红书自动化仿写发文机器人了解一下

您好&#xff0c;我是码农飞哥&#xff08;wei158556&#xff09;&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。&#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精通…

轻薄本没有独立显卡如何运行stable diffusion

众所周知&#xff0c;Stable Diffusion WebUI 使用 GPU 模式运行。 一&#xff1a;检查自己显卡 打开任务管理器或者winR 输入dxdiag 查看自己显卡状态 很明显一般轻薄本只会带有集显&#xff0c;不能满足stable diffusion要求所以我们可以使用cup来运行stable diffusion 在…

带头双向循环链表,顺序表和链表的比较

双向链表 单链表结点中只有一个指向其后继的指针&#xff0c;使得单链表只能从前往后依次遍历&#xff0c;要访问某个结点的前驱&#xff08;插入、删除操作时&#xff09;&#xff0c;只能从头开始遍历&#xff0c;访问前驱的时间复杂度为O(N)。为了克服这个缺点&#xff0c;…