基于LiteFlow的风控系统指标版本控制

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

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview


更新日志

最近关于https://github.com/wnhyang/coolGuard此项目更新了如下内容:https://github.com/wnhyang/coolGuard/commits/main/
在这里插入图片描述

继前文GeoHash后项目又有了一些更新主要有:

1、【一般】增加了新的指标类型,历史取值。

2、【重要】更改表的关联关系,使用唯一索引而不是自增id。

这里很有必要展开一下,之前的所有表关系都是通过数据库自增id关联的,这有很大的弊端,尤其影响之后的计划(如:策略/规则/指标等导入导出,chain版本控制等),所以这个变化很有必要。

3、【一般】增加缓存删除,增加了一些场景的缓存删除,保障缓存-数据库一致性。

4、【一般】增加基础参数查询,主要包括一些常量枚举的查询,如指标类型、字段类型、逻辑操作等等。

5、【一般】完善注解校验。

6、【重要】增加指标版本控制。

如题,本篇文章将围绕指标版本控制详细展开,匆匆做完,就来分享,如有问题请指正!

版本控制

为什么要版本控制?

简单来说就几个重点:追踪变更、恢复与回滚、CICD、数据完整性。。。

常见的版本控制有哪些方法呢?

最常用的方法“主表+历史表”的设计,已经在《风控系统之事件溯源,决策流程记录与版本控制》中提到,在这次将其详细展开,通过实践检验真理。

主表+历史表的设计,就算没有了解过,听名字也大概知道怎么做了。

无非就是主表存储的是最新的、有效的数据记录。通常情况下,主表中会包含业务关键字段以及一些基本的元数据信息(如创建时间、最后修改时间等)。

历史表则用来保存所有历史版本的数据记录。每当主表中的数据发生变化时,旧的数据会被复制到历史表中,以便长期保存。

但是如果细心思考的话,其实还可以将其分为两种模式。

1、主表即运行

主表即当下运行,即running,历史表即historyversion

下图画的也不是很好(其中chain的变化没有体现出来),大概意思是:规则引擎运行的永远都是主表的数据,历史纯粹是历史,是用来回溯恢复的,历史表数据会和主表差一个版本。

这样设计完全没问题,可以满足基本的版本控制需要。

如果非要缺点可能就是主表一定要是确认的运行流程,只能存在终态。可以加上开关表示运行与停止,状态切换不记入历史表,其他任意的修改都需要生成新的版本(可以使用hash算法对比本次修改是否真的有变化)。
在这里插入图片描述

2、历史既是历史也是运行

主表表示编辑区,即edit,历史表才是真正的运行区,当然同时也是历史,即historyrunningversion

这样的设计会相对复杂一点,除了基本的新增、修改、删除,还会有发版、下线。本来也想尝试画图的,但发现好像达不到我要的效果,放弃了😂

但还是要补充说明一下,主表和历史表都需要一个状态标识,而且意义不同。主表的状态标识有两种p0p1p0标识编辑中,与运行状态不一致,发版后历史表创建新记录并运行;p1表示与运行状态一致不可发版,只能修改后再提交。历史表状态标识也有两种,0是历史,即真正历史表的作用,保留过去版本数据;1是当下运行状态。

新增、编辑、删除只是操作主表,下面直接引用项目代码了。

这里与上面最大的区别就是不用操作历史表。

@Override
@Transactional(rollbackFor = Exception.class)
@CacheEvict(value = RedisKey.INDICATOR, allEntries = true)
public Long createIndicator(IndicatorCreateVO createVO) {// 1、指标名重复抛异常if (indicatorMapper.selectByName(createVO.getName()) != null) {throw exception(INDICATOR_NAME_EXIST);}// 2、变换并插入主表Indicator indicator = IndicatorConvert.INSTANCE.convert(createVO);indicator.setCode(IdUtil.fastSimpleUUID());indicator.setReturnType(IndicatorUtil.getReturnType(indicator.getType(), indicator.getCalcField()));indicator.setTimeSlice(WinSize.getWinSizeValue(indicator.getWinSize()));indicator.setCondStr(JsonUtils.toJsonString(createVO.getCond()));indicatorMapper.insert(indicator);return indicator.getId();
}@Override
@Transactional(rollbackFor = Exception.class)
@CacheEvict(value = RedisKey.INDICATOR, allEntries = true)
public void updateIndicator(IndicatorUpdateVO updateVO) {// 1、校验存在和name重复// TODO 校验存在和name// 2、hash确认是否真的有更改// TODO hash确认是否真的有更改Indicator indicator = IndicatorConvert.INSTANCE.convert(updateVO);indicator.setReturnType(IndicatorUtil.getReturnType(indicator.getType(), indicator.getCalcField()));indicator.setCondStr(JsonUtils.toJsonString(updateVO.getCond()));// 注意:状态只能变为fase即与运行区不一致,可以提交,不然没有做修改就是和运行区一致,不可提交indicator.setStatus(Boolean.FALSE);indicatorMapper.updateById(indicator);
}@Override
@Transactional(rollbackFor = Exception.class)
@CacheEvict(value = RedisKey.INDICATOR, allEntries = true)
public void deleteIndicator(Long id) {// 1、检查存在Indicator indicator = indicatorMapper.selectById(id);// 2、确认此指标是否在运行,运行不可删除,// 感觉有点没必要了IndicatorVersion indicatorVersion = indicatorVersionMapper.selectRunningByCode(indicator.getCode());if (indicatorVersion != null) {throw exception(INDICATOR_IS_RUNNING);}indicatorMapper.deleteById(id);
}

提交版本

提交版本会复杂一些,同时操作了指标、指标历史、chain表

@Override
@Transactional(rollbackFor = Exception.class)
public BatchVersionSubmitResultVO submit(VersionSubmitVO submitVO) {BatchVersionSubmitResultVO result = new BatchVersionSubmitResultVO().setId(submitVO.getId());// 提交后就同时在version表中增加一条记录,表示该指标在运行状态// 检查指标存在Indicator indicator = indicatorMapper.selectById(submitVO.getId());if (ObjectUtil.isNull(indicator)) {throw exception(INDICATOR_NOT_EXIST);}// 校验当前提交是不是和运行区一致,一致无需提交if (indicator.getStatus()) {throw exception(INDICATOR_VERSION_EXIST);}// 1、更新当前指标为已提交indicatorMapper.updateById(new Indicator().setId(submitVO.getId()).setStatus(Boolean.TRUE));// 2、查询是否有已运行的,有版本+1,没有版本1IndicatorVersion indicatorVersion = indicatorVersionMapper.selectRunningByCode(indicator.getCode());int version = 1;if (indicatorVersion != null) {version = indicatorVersion.getVersion() + 1;// 关闭已运行的indicatorVersionMapper.updateById(new IndicatorVersion().setId(indicatorVersion.getId()).setStatus(Boolean.FALSE));}// 3、插入新纪录并加入chainIndicatorVersion convert = IndicatorVersionConvert.INSTANCE.convert(indicator);convert.setVersion(version);convert.setVersionDesc(submitVO.getVersionDesc());convert.setStatus(Boolean.TRUE);indicatorVersionMapper.insert(convert);// 4、更新chainString iChain = StrUtil.format(LFUtil.INDICATOR_CHAIN, indicator.getCode());// 构造指标elString condEl = LFUtil.buildCondEl(convert.getCondStr());if (chainMapper.selectByChainName(iChain)) {Chain chain = chainMapper.getByChainName(iChain);chain.setElData(StrUtil.format(LFUtil.IF_EL, condEl,LFUtil.INDICATOR_TRUE_COMMON_NODE,LFUtil.INDICATOR_FALSE_COMMON_NODE));chainMapper.updateById(chain);} else {chainMapper.insert(new Chain().setChainName(iChain).setElData(StrUtil.format(LFUtil.IF_EL, condEl,LFUtil.INDICATOR_TRUE_COMMON_NODE,LFUtil.INDICATOR_FALSE_COMMON_NODE)));}result.setSuccess(Boolean.TRUE);return result;
}

下线

下线相对简单

@Override
@Transactional(rollbackFor = Exception.class)
public void offline(Long id) {IndicatorVersion indicatorVersion = indicatorVersionMapper.selectById(id);indicatorVersionMapper.updateById(new IndicatorVersion().setId(id).setStatus(Boolean.FALSE));chainMapper.deleteByChainName(StrUtil.format(LFUtil.INDICATOR_CHAIN, indicatorVersion.getCode()));
}

查编辑区和运行区就不用展示了。

LiteFlow流程变化

我前面提到的最近更新标注了【重要】的真的很关键,没错说的就是“更改表的关联关系,使用唯一索引而不是自增id”。没有这次改造也不会有这篇指标版本控制。

因为我的LiteFlow流程设计中,所有指标运行的核心EL是这样的FOR(i_fn).parallel(true).DO(i_cn);使用的是异步次数循环,对于我只需要更改i_fn组建循环的部分就可以完成从主表表示运行到历史表表示运行的切换。

如图,只是修改了查询当前运行指标的代码。
在这里插入图片描述

关于项目进度

我是如何测试的?

如果你看过项目代码,发现极少的单元测试,确实,测试这部分很不完善,仅有的就是通过apifox同步的接口,运行几个接口保存为测试集合,改完代码后通常要做这些事情:1、Reformat Code,必须要格式化一下,强迫症受不了;2、mvn clean install,因为使用mapstruct等其他插件,为了避免依赖的一些问题;3、如果有接口更新,同步一下apifox;4、运行apifox测试集合;5、针对修改的点mock数据发一下接口;6、如果有更改表结构,或是必要数据,导出sql到项目替换;7、确认没问题后,commit-push

git代码分支,为什么都在main上?

项目还未发版,而且没有协作者,自己随意了一些。

下一步?

乘胜追击,指标版本控制做了,下一步策略、规则版本控制。

至于发版,我感觉遥遥无期🤔

写在最后

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


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

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview

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

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

相关文章

Mysql中的 TEXT 和 BLOB 解析

🚀 博主介绍:大家好,我是无休居士!一枚任职于一线Top3互联网大厂的Java开发工程师! 🚀 🌟 在这里,你将找到通往Java技术大门的钥匙。作为一个爱敲代码技术人,我不仅热衷…

241124_文本解码原理

241124_文本解码原理 一个文本序列的概率分布可以分解为每个词基于其上文的条件概率的乘积。 Greedy search 就是每步都选择概率最大的,不会去考虑全局 按照贪心搜索输出the nice woman 的概率就是0.5*0.40.2 这种方法简单,但也存在问题,比…

介绍一下strlwr(arr);(c基础)

hi , I am 36 适合对象c语言初学者 strlwr(arr)&#xff1b;函数是把arr数组变为小写字母 格式 #include<string.h> strlwr(arr); 返回值为arr 链接分享一下arr的意义(c基础)(必看)(牢记)-CSDN博客 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #incl…

16:(标准库)ADC三:使用外部触发启动ADC/模拟看门狗

使用外部触发启动ADC 1、外部中断线EXTI11触发ADC2、外部定时器TIM2_CH2触发ADC3、ADC中模拟看门狗的使用 1、外部中断线EXTI11触发ADC ADC的触发方式有很多&#xff0c;一般情况都是使用软件触发反式启动ADC转换。除了软件触发方式还能使用外部事件触发启动ADC转换。如下图所…

Linux之管道,system V的共享内存,消息队列和信号量

Linux之管道&#xff0c;systemV共享内存和信号量 一.进程间通信1.1进程间通信的目的1.2进程间通信的方式 二.管道2.1管道的概念2.2匿名管道2.3命名管道 三.system V3.1共享内存3.2消息队列3.3信号量 一.进程间通信 在我们之前有关Linux指令的学习时我们使用过“|”这个命令&a…

使用ChatGPT生成和优化电子商务用户需求规格说明书

在电子商务项目开发中&#xff0c;用户需求规格说明书&#xff08;User Requirement Specification, URS&#xff09;是团队沟通与项目成功的基石。然而&#xff0c;面对复杂多变的需求&#xff0c;如何快速生成清晰、完整且具备说服力的文档&#xff1f;这正是AI工具的用武之地…

1+X应急响应(网络)常见网络攻击-SQL注入:

常见网络攻击-SQL注入&#xff1a; SQL注入概述&#xff1a; 动态网站的工作流程&#xff1a; SQL注入的起源&#xff1a; SQL典型的攻击手段&#xff1a; SQL注入的危害&#xff1a; SQL注入的函数&#xff1a; SQL注入类型&#xff1a; 提交方式分类&#xff1a; Get注入&am…

Spire.PDF for .NET【页面设置】演示:打开 PDF 时自动显示书签或缩略图

用户打开 PDF 文档时&#xff0c;他们会看到 PDF 的初始视图。默认情况下&#xff0c;打开 PDF 时不会显示书签面板或缩略图面板。在本文中&#xff0c;我们将演示如何设置文档属性&#xff0c;以便每次启动文件时都会打开书签面板或缩略图面板。 Spire.PDF for .NET 是一款独…

[Docker-显示所有容器IP] 显示docker-compose.yml中所有容器IP的方法

本文由Markdown语法编辑器编辑完成。 1. 需求背景: 最近在启动一个服务时&#xff0c;突然发现它的一个接口&#xff0c;被另一个服务ip频繁的请求。 按理说&#xff0c;之前设置的是&#xff0c;每隔1分钟请求一次接口。但从日志来看&#xff0c;则是1秒钟请求一次&#xff…

单片机GPIO的8种工作模式

1、输入 GPIO_MODE_AIN:模拟输入 GPIO_MODE_IN_FLOATING:浮空输入 GPIO_MODE_IPD:下拉输入 GPIO_MODE_IPU:上拉输入 2、输出 GPIO_MODE_OUT_OD:开漏输出&#xff08;特殊情况使用&#xff09; GPIO_MODE_OUT_PP&#xff1a;推挽输出-----点灯&#xff08;通用&#…

Azkaban部署

首先我们需要现在相关的组件&#xff0c;在这里已经给大家准备好了相关的安装包&#xff0c;有需要的可以自行下载。 只需要启动hadoop集群就可以&#xff0c;如果现在你的hive是打开的&#xff0c;那么请你关闭&#xff01;&#xff01;&#xff01; 如果不关会造成证书冲突…

时钟使能、

时钟使能 如果正确使用&#xff0c;时钟使能能够显著地降低系统功耗&#xff0c;同时对面积或性能的影响极小。但是如果不正确地使用时钟使能&#xff0c; 可能会造成下列后果&#xff1a; • 面积增大 • 密度减小 • 功耗上升 • 性能下降 在许多使用大量控制集的…

视觉经典神经网络与复现:深入解析与实践指南

目录 引言 经典视觉神经网络模型详解 1. LeNet-5&#xff1a;卷积神经网络的先驱 LeNet-5的关键特点&#xff1a; 2. AlexNet&#xff1a;深度学习的突破 AlexNet的关键特点&#xff1a; 3. VGGNet&#xff1a;深度与简洁的平衡 VGGNet的关键特点&#xff1a; 4. ResNe…

【CSS in Depth 2 精译_060】9.3 详解 CSS 作用域的相关概念、最新 @scope 规则的应用及注意事项

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 【第九章 CSS 的模块化与作用域】 ✔️ 9.1 模块的定义 9.1.1 模块和全局样式9.1.2 一个简单的 CSS 模块9.1.3 模块的变体9.1.4 多元素模块 9.2 将模块组合为更大的结构 9.2.1 模块中多个职责的拆分…

uniapp实现开发遇到过的问题(持续更新中....)

1. 在ios模拟器上会出现底部留白的情况 解决方案&#xff1a; 在manifest.json文件&#xff0c;找到开源码视图配置&#xff0c;添加如下&#xff1a; "app-plus" : {"safearea":{"bottom":{"offset" : "none" // 底部安…

React(六)——Redux

文章目录 项目地址基本理解一、配置Redux store二、创建slice配置到store里并使用三、给Slice配置reducers&#xff0c;用来修改初始值 项目地址 教程作者&#xff1a;教程地址&#xff1a; 代码仓库地址&#xff1a; 所用到的框架和插件&#xff1a; dbt airflow基本理解 s…

国家级资质!同驭汽车获得CNAS实验室认证

近日&#xff0c;同驭汽车科技顺利通过中国合格评定国家认可委员会&#xff08;简称CNAS&#xff09;评审&#xff0c;获得《中国合格评定国家认可委员会实验室认可证书》。这标志着同驭已建立国际标准的实验室管理体系&#xff0c;产品的试验与检测技术能力达到了国际认可的准…

HTML5好看的音乐播放器多种风格(附源码)

文章目录 1.设计来源1.1 音乐播放器风格1效果1.2 音乐播放器风格2效果1.3 音乐播放器风格3效果1.4 音乐播放器风格4效果1.5 音乐播放器风格5效果 2.效果和源码2.1 动态效果2.2 源代码 源码下载万套模板&#xff0c;程序开发&#xff0c;在线开发&#xff0c;在线沟通 作者&…

快速简单的视频下载器——lux

文章目录 前言1.环境检查1.1 检查 lux 安装1.2 检查FFmpeg安装1.3 备注 2. lux指令2.1 无OPTIONS2.2 -i 指令2.3 - f 指令2.4 -c 指令2.5 -o 指令2.6 备注 3.结语 前言 在学习之余&#xff0c;发现了一个简单并且高效的视频下载器lux,能够帮你快速且高效的下载文件&#xff08…

linux ubuntu的脚本知

目录 一、变量的引用 二、判断指定的文件是否存在 三、判断目录是否存在 四、判断最近一次命令执行是否成功 五、一些比较符号 六、"文件"的读取和写入 七、echo打印输出 八、ubuntu切换到root用户 N、其它可以参考的网址 脚本功能强大&#xff0c;用起来也…