可视化逻辑表达式编辑器

优质博文:IT-BLOG-CN

一、QueryBuilder介绍

QueryBuilder 是一个用于创建查询和过滤器的 UI 组件。

QueryBuilder的特点
1、支持的输入属性丰富,常见的 字符串,整数,浮点数,布尔类型,日期类型,数组列表等都支持
2、高度可定制。QueryBuilder是由规则以及规则组组合而成的查询以及过滤组件,规则以及规则组可以层层嵌套,所以复杂的规则也可以配置出来。
3、支持的生成脚本语句多。QueryBuilder的最终目的是,通过配置规则树,最终把规则树转化为我们想要的脚本语句。

目前支持的脚本语句有 groovy脚本,jsonLogic脚本,mpsql脚本等目前市面上的QueryBuilder很多,功能大同小异,我们选用的是react-awesome-query-builder,功能性以及可用性相比较是最好的

QueryBuilder的详细介绍
在这里插入图片描述

QueryBuilder主要分为以下几个部分:

规则以及规则组
1、规则可以理解为一条判断语句,比如今天天气很好,或者今天天气不好,它的结果是一个布尔判断,即是或者否
2、规则组,则是多个规则的组合,比如 当前时间大于8点并且当前时间小于21点并且今天不是周末,他是老师或者他是公务员,它是有多个布尔判断拼接而成,结果也是一个布尔值

连接词(conjunctions) 连接词是用来连接规则与规则或者规则组与规则组,或者规则与规则组之间关系的逻辑词,连接词有三种,不是,或者,并且,对应的逻辑符号是 !,||, &&

输入属性(widgets),或者叫做左值(leftValue) 输入属性支持的类型有 文本/数值/单选值/多选值/日期类型/布尔类型/函数

操作符(operators) 操作符是用来连接输入属性以及期望值的逻辑符号
1、不同的输入属性会有不同的操作符
2、对于文本类型,对应的操作符有[“等于”,“不等于”,“包含”,“包含数组项”,“字符开头是”,“字符结尾是”,“正则匹配”,“为空”,“不为空”]
3、对于数值类型,对应的操作符有[“等于”,“不等于”,“小于”,“小于等于”,“大于”,“大于等于”,“范围在”,“范围不在”,“为空”,“不为空”]
4、对于数组类型,对应的操作符有[“等于”,“不等于”,“等于其中一个”,“不等于其中一个”,“包含其中一个”]
5、对于布尔类型,对应的操作符有[“等于”,“不等于”]

期望值,或者叫做右值(rightValue) 期望值就是我们希望在这个规则里命中的值,如果输入值跟期望值相匹配,那么这条规则就返回true,否则这条规则就返回false

总结一下:输入属性 + 操作符 + 期望值 就组合形成一个规则

规则+ 连接词+规则 可以组成一个规则组

规则 和 规则组 相互嵌套,可以形成一个规则树

比如截图里面的规则树 就是由两个规则+两个规则组嵌套组合而成

接下来我来介绍一下怎么由规则树解析成我们想要的groovy脚本语言

举例说明规则的解析过程

先看单条规则是怎样解析成groovy脚本的

equal: {label: '等于',groovy: (leftValue, rightValue) => `${leftValue} == ${rightValue}`,labelForFormat: '==',sqlOp: '=',reversedOp: 'not_equal',formatOp: (field, op, value, valueSrcs, valueTypes, opDef, operatorOptions, isForDisplay, fieldDef) => {if (valueTypes == 'boolean' && isForDisplay)return value == 'No' ? `NOT ${field}` : `${field}`;elsereturn `${field} ${opDef.label} ${value}`;},mongoFormatOp: mongoFormatOp1.bind(null, '$eq', v => v, false),jsonLogic: '==',},
like: {label: '包含',groovy: (leftValue, rightValue) => `${leftValue}.contains(${rightValue})`,labelForFormat: 'Like',reversedOp: 'not_like',sqlOp: 'LIKE',sqlFormatOp: (field, op, values, valueSrc, valueType, opDef, operatorOptions) => {if (valueSrc == 'value') {return `${field} LIKE ${values}`;} else return undefined; // not supported},mongoFormatOp: mongoFormatOp1.bind(null, '$regex', v => (typeof v == 'string' ? escapeRegExp(v) : undefined), false),//jsonLogic: (field, op, val) => ({ "in": [val, field] }),jsonLogic: "in",_jsonLogicIsRevArgs: true,valueSources: ['value'],},
between: {label: '范围在',groovy: (leftValue, rightValue) => `(${leftValue} >= ${rightValue[0]} && ${leftValue} <= ${rightValue[1]}) `,labelForFormat: 'BETWEEN',sqlOp: 'BETWEEN',cardinality: 2,formatOp: (field, op, values, valueSrcs, valueTypes, opDef, operatorOptions, isForDisplay) => {let valFrom = values.first();let valTo = values.get(1);if (isForDisplay)return `${field} >= ${valFrom} AND ${field} <= ${valTo}`;elsereturn `${field} >= ${valFrom} && ${field} <= ${valTo}`;},mongoFormatOp: mongoFormatOp2.bind(null, ['$gte', '$lte'], false),valueLabels: ['开始值','结束值'],textSeparators: [null,'到'],reversedOp: 'not_between',jsonLogic: "<=",}, 

由于操作符有很多,他们的解析过程比较类似,我就不一一介绍了。

规则树由规则以及规则组嵌套而成,最后生成的数据结构就是一个树状结构

下图是上面的示例对应的规则树结构

{"id":"99aa8a99-4567-489a-bcde-f18de0917366","type":"group","children1":{"b8b8abba-89ab-4cde-b012-318de0917366":{"type":"rule","properties":{"field":"weather","operator":"equal","value":["晴"],"valueSrc":["value"],"valueType":["text"]}},"9b898a88-4567-489a-bcde-f18de0917366":{"type":"rule","properties":{"field":"weekday","operator":"select_any_in","value":[["6","7"]],"valueSrc":["value"],"valueType":["multiselect"]}},"aa989a8a-0123-4456-b89a-b18de0923911":{"type":"group","properties":{"conjunction":"AND"},"children1":{"89b8abab-cdef-4012-b456-718de0923912":{"type":"rule","properties":{"field":"phone","operator":"match","value":["^1[3456789]\\d{9}$"],"valueSrc":["value"],"valueType":["text"]}},"898baa88-89ab-4cde-b012-318de092c6b8":{"type":"rule","properties":{"field":"isOpen","operator":"equal","value":[true],"valueSrc":["value"],"valueType":["boolean"]}}}},"a9a8baa9-4567-489a-bcde-f18de092e65a":{"type":"group","properties":{"conjunction":"AND"},"children1":{"b8a8b99b-0123-4456-b89a-b18de092e65a":{"type":"rule","properties":{"field":"time","operator":"greater","value":[8],"valueSrc":["value"],"valueType":["number"]}},"89bbb8bb-cdef-4012-b456-718de093f996":{"type":"rule","properties":{"field":"time","operator":"less","value":[17],"valueSrc":["value"],"valueType":["number"]}}}}},"properties":{"conjunction":"AND"}
} 

知道了单条规则翻译以及规则树结构,就可以把规则树对应的groovy脚本翻译出来

const ruleGroupToGroovy = (ruleGroup, contract) => {let conjunction = ruleGroup.properties.conjunction === "OR" ? " || " : " && ";let not = ruleGroup.properties.not ? "!" : "";let segments = [];for (let id in ruleGroup.children1) {let child = ruleGroup.children1[id];let segment = null;if (child.type === "group") {segment = ruleGroupToGroovy(child, contract);}else {segment = ruleToGroovy(child, contract);}if (segment) {segments.push(segment);}}if (segments.length === 0) {return "";}else if (segments.length === 1) {return not + segments[0];}else {let script = not + "(" + segments[0];for (let i = 1; i < segments.length; i++) {script += conjunction + segments[i];}script = script + ")";return script;}
}; 

由于我们是Java项目,所以我们需要groovy脚本即可,上述流程树翻译好的groovy脚本

(weather == "晴" && weekday in ["6","7"] && (!!(phone =~ /^1[3456789]\d{9}$/) && isOpen == true) && (time > 8 && time < 17)) 

除了groovy脚本,还有一种常用的脚本是jsonLogic脚本
jsonLogic,这是一种用 json 构造的语法树,最主要优势是语言无关、前后端通用。非常简单明了,jsonLogic 官方有 js/php/python/ruby 对应的解析库。

//Rule
{"and": [{"==": [{"var": "weather"},"晴"]},{"in": [{"var": "weekday"},["6","7"]]},{"and": [{"match": [{"var": "phone"},"^1[3456789]\d{9}$"]},{"==": [{"var": "isOpen"},true]}]},{"and": [{">": [{"var": "time"},8]},{"<" [{"var": "time"},17]}]}]
}
// Data
{"weather": """weekday": "","phone": "","isOpen": null,"time": null
} 

我们知道

{     // 天气  "weather": ""     // 星期几  "weekday": "",    // 游乐场电话  "phone": "",     // 游乐场是否开门  "isOpen": null,     // 时间  "time": null
}

这几个是输入属性,对这个流程树设置不同的输入属性,就可以得到不同的布尔值
比如设置输入属性 {“weather”: “晴”,“weekday”: “6”,“phone”:“13212341234”,“isOpen”:true,“time”:12} 得到的值就是 true
比如设置输入属性 {“weather”: “晴”,“weekday”: “5”,“phone”:“13212341234”,“isOpen”:true,“time”:12} 得到的值就是 false

逻辑表达式编辑器的实现
逻辑表达式编辑器流程介绍

我们的逻辑表达式编辑器就是基于上述的QueryBuilder的规则树实现的,对于QueryBuilder过滤器后面加了输出语句
在这里插入图片描述

这样就是一个判断分支。

对于复杂逻辑肯定会有多个判断分支,我们可以添加多个判断分支组合起来
在这里插入图片描述

由于分支不一定都命中,所以我们要设置一个兜底逻辑,相当于判断语句中的default,这样整个逻辑表达式的流程就完整了
在这里插入图片描述

这样,对应的逻辑就可以这样简单的表示

二、服务端实现介绍

首先看一下流程图
在这里插入图片描述

对于每一个规则流程都有一个唯一的accesskey,比如
trip.ibu.TTS.outbound
trip.flight.Offline.outbound.call

我们在项目初始化的时候,项目里面有几个规则流程,把这些规则流程对应的accessKey放到List里面,然后逐个的遍历初始化

一个规则流程有多个规则分支,因为groovy脚本,Java程序是无法直接执行的,因此我们需要需要进行转化。

这里我们用到了GroovyClassLoader,GroovyClassLoader主要负责在运行时编译groovy脚本为Class的工作,从而使Groovy实现了将groovy源代码动态加载为Class的功能。

注意,我们要有预热的过程,不能在程序运行中将groovy脚本转化为class对象,然后让程序执行。因为一个规则流程有多个规则分支,一个规则分支就对应一个groovy脚本,运行的时候就要转化成一个class对象,一个复杂的规则流程可能有几百个规则分支。

由于groovy脚本转化为class对象是比较耗时的,所以在程序初始化阶段进行预热是有必要的。

// 脚本转化为class对象
GroovyClassLoader classLoader = new GroovyClassLoader();
Class<Script> scriptClazz = (Class<Script>) classLoader.parseClass(JARS + scriptStr);
// 执行判断
Binding binding = new Binding(parameters);
Script script = InvokerHelper.createScript( scriptClazz, binding);
return (boolean) script.run();

需要注意的一个点
JVM中的Class只有满足以下三个条件,才能被GC回收,也就是该Class被卸载(unload):

  • 该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例。
  • 加载该类的ClassLoader已经被GC。
  • 该类的java.lang.Class 对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法.

因此,class不是很容易被gc的。

所以当我们监听到规则流程有变更,需要去重新加载规则流程里面的流程分支时,这种情况下很多规则分支是没有被改动的,被改动的往往是一两个规则分支。

所以这种情况下,我们不能再次将所有的groovy初始化生成class对象,这样会造成重复生成class对象,容易造成out of metaspace的错误

这里的解决方案是 对每一个groovy脚本生成一个md5值,存放在map<md5, Class>里面,value值就是groovy脚本转化生成的Class对象

这样的话,就不会重复生成class对象,我们只重新生成了规则分支更改的class对象

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

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

相关文章

Linux下mysql环境的搭建

1.mysql的下载 去MySQL官网下载mysql的linux压缩包 MySQL :: Download MySQL Community Server 如果下载慢请到网盘中自行下载 通过网盘分享的文件&#xff1a;mysql-8.0.40-1.el7.x86_64.rpm-bundle.tar 链接: https://pan.baidu.com/s/1vUJ-VuTwer1nLPT-haQCqw?pwd6342 提…

基于Qwen2-VL模型针对LaTeX OCR任务进行微调训练 - 多图推理

基于Qwen2-VL模型针对LaTeX OCR任务进行微调训练 - 多图推理 flyfish 基于Qwen2-VL模型针对LaTeX_OCR任务进行微调训练_-_LoRA配置如何写 基于Qwen2-VL模型针对LaTeX_OCR任务进行微调训练_-_单图推理 基于Qwen2-VL模型针对LaTeX_OCR任务进行微调训练_-_原模型_单图推理 基于Q…

图像识别 | Matlab基于卷积神经网络(CNN)的宝可梦识别源程序,GUI界面。附详细的运行说明。

图像识别 | Matlab基于卷积神经网络(CNN)的宝可梦识别源程序&#xff0c;GUI界面。附详细的运行说明。 目录 图像识别 | Matlab基于卷积神经网络(CNN)的宝可梦识别源程序&#xff0c;GUI界面。附详细的运行说明。预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab基…

设置IMX6ULL开发板的网卡IP的两种方法(临时生效和永久有效两种方法)

设置开发板网卡的IP&#xff0c;有两种方法。 方法一&#xff1a;临时生效 第一种方式是临时设置&#xff0c;只有本次有效&#xff0c;重启后又要重新设&#xff0c;命令为&#xff1a; ifconfig eth0 192.168.5.9设置成功后可以使用ifconfig命令来查看已设置的 IP 地址。 …

22. Three.js案例-创建旋转的圆环面

22. Three.js案例-创建旋转的圆环面 实现效果 知识点 WebGLRenderer (WebGL渲染器) THREE.WebGLRenderer 是Three.js中最常用的渲染器&#xff0c;用于将场景渲染到WebGL画布上。 构造器 new THREE.WebGLRenderer(parameters) 参数类型描述parametersObject可选参数对象&…

【D3.js in Action 3 精译_044】5.1 饼图和环形图的创建(四):数据标签的添加

当前内容所在位置&#xff1a; 第五章 饼图布局与堆叠布局 ✔️ 5.1 饼图和环形图的创建 ✔️ 5.1.1 准备阶段&#xff08;一&#xff09;5.1.2 饼图布局生成器&#xff08;二&#xff09;5.1.3 圆弧的绘制&#xff08;三&#xff09; ✔️5.1.4 数据标签的添加&#xff08;四&…

java全栈day13-后端Web实战2

接上述查询部门实现&#xff0c;完成后续要求 一、统一响应结果 1.1步骤 资料如下 对一开始的代码修改如下 结果如下 1.2测试 指定请求方式 结果 小结 二、前后端联调测试 资料如下&#xff1a; (不行&#xff0c;一定要不带空格和不带中文&#xff0c;要不然启动不了试了半天…

vscode 排除文件夹搜索

排除的文件夹 node_modules/,dist/

优雅的@ObservedV2和@Trace装饰器

Hello&#xff0c;大家好&#xff0c;我是 V 哥。在HarmonyOS NEXT开发中&#xff0c;ObservedV2装饰器和Trace装饰器是用于状态管理的两个装饰器&#xff0c;它们在HarmonyOS应用开发中用于增强对类对象中属性的观测能力。如果你学过观察者模式的原理&#xff0c;你会更容易理…

一款免费、简单、快速的JS打印插件,web 打印组件,基于JavaScript开发,支持数据分组,快速分页批量预览,打印,转pdf,移动端,PC端

前言 在数字化办公时代&#xff0c;打印需求呈现多样化和复杂化的趋势。现有的打印软件往往存在cao作繁琐、兼容性差、功能单一等问题&#xff0c;难以满足现代企业高效、灵活的打印需求。 为了解决这些痛点&#xff0c;一款简单、高效、多功能的打印插件成为了迫切需求。 介…

TCP/IP杂记

TCP三次握手、四次挥手 从应用角度&#xff0c;不用多考虑为什么有三次&#xff0c;遵循标准即可。 ubuntu 下 wireshark安装&#xff1a; sudo add-apt-repository universe sudo apt install wireshark 三次握手实证&#xff1a; 第一次握手的情况如下&#xff1a;&#…

Vue前端开发-接收跳转参数

路由携带参数跳转到目标页面后&#xff0c;页面组件可以接收到携带传入的参数&#xff0c;接收的方式与携带的方式相关&#xff0c;如果是采用查询字符串方式携带&#xff0c;那么可以通过路由中的query对象获取到参数&#xff0c;如果是其他方式&#xff0c;通常都是通过路由中…

[ComfyUI]批量生成图片的节点:输入一个prompt列表批量生成图像

文章目录 1.参考资料2.两个节点的部署FizzNodes节点comfyui-mixlab-nodes 生成的结果展示 1.参考资料 如何使用ComfyUI一次批量生成不同内容的图片 ComfyUI工作流】随机提示词批量出图&#xff0c;懒人刷图福音&#xff0c;根据提示 2.两个节点的部署 FizzNodes节点 fizzn…

【实操GPT-SoVits】声音克隆模型图文版教程

项目github地址&#xff1a;https://github.com/RVC-Boss/GPT-SoVITS.git官方教程&#xff1a;https://www.yuque.com/baicaigongchang1145haoyuangong/ib3g1e/tkemqe8vzhadfpeu本文旨在迅速实操GPT-SoVits项目&#xff0c;不阐述技术原理&#xff08;后期如果有时间研究&#…

JAVA (Springboot) i18n国际化语言配置

JAVA i18n国际化语言配置 一、简介二、功能三、Java配置国际化步骤四、Java国际化配置工具类五、Spring Boot配置六、测试 一、简介 在Java中&#xff0c;国际化&#xff08;Internationalization&#xff0c;通常简称为i18n&#xff09;是一个过程&#xff0c;它允许应用程…

如何创建基于udp的客户端和服务端

1.先创建好udpServer.hpp、udpServer.cc、udpClient.hpp、udpClient.cc的框架。 #pragma once #include <string> #include <iostream> #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <cerrno> #include…

【上线文档】系统上线方案模板,计算机系统上线保障计划,系统运维信息系统运行保障方案,系统上线方案模板(Word原件)

一、项目背景和目标 二、项目需求分析 2.1 功能需求 2.2 非功能需求 三、系统设计 3.1 系统架构设计 3.2 数据库设计 3.3 接口设计 3.4 用户界面设计 四、系统开发 4.1 开发环境搭建 4.2 业务逻辑开发 4.3 数据库实现 4.4 接口实现 4.5 用户界面实现 五、系统测…

大模型应用的数字能源数据集

除了尚须时日的量子计算解决算力效率和能源问题&#xff0c;以及正在路上的超越transformer的全新模型架构外&#xff0c;无疑是“数据集”&#xff0c;准确讲是“高质量大规模多样性的数据集”。数据集是大模型发展的核心要素之一&#xff0c;是大计算的标的物&#xff0c;是实…

【OpenCV】图像转换

理论 傅立叶变换用于分析各种滤波器的频率特性。对于图像&#xff0c;使用 2D离散傅里叶变换&#xff08;DFT&#xff09; 查找频域。快速算法称为 快速傅立叶变换&#xff08;FFT&#xff09; 用于计算DFT。 Numpy中的傅立叶变换 首先&#xff0c;我们将看到如何使用Numpy查…

如何使用Java编写Jmeter函数

Jmeter 自带有各种功能丰富的函数&#xff0c;可以帮助我们进行测试&#xff0c;但有时候提供的这些函数并不能满足我们的要求&#xff0c;这时候就需要我们自己来编写一个自定义的函数了。例如我们在测试时&#xff0c;有时候需要填入当前的时间&#xff0c;虽然我们可以使用p…