首页 教程 开发工具 可视化逻辑表达式编辑器

可视化逻辑表达式编辑器

优质博文: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}`;}elsereturn 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"";}elseif(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 =newGroovyClassLoader(); Class<Script> scriptClazz =(Class<Script>) classLoader.parseClass(JARS + scriptStr);// 执行判断 Binding binding =newBinding(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对象

评论(0)条

提示:请勿发布广告垃圾评论,否则封号处理!!

    猜你喜欢
    【MySQL】用户管理

    【MySQL】用户管理

     服务器/数据库  2个月前  2.15k

    我们推荐使用普通用户对数据的访问。而root作为管理员可以对普通用户对应的权限进行设置和管理。如给张三和李四这样的普通用户权限设定后。就只能操作给你权限的库了。

    Cursor Rules 让开发效率变成10倍速

    Cursor Rules 让开发效率变成10倍速

     服务器/数据库  2个月前  1.21k

    在AI与编程的交汇点上,awesome-cursorrules项目犹如一座灯塔,指引着开发者们驶向更高效、更智能的编程未来。无论你是经验丰富的老手,还是刚入行的新人,这个项目都能为你的编程之旅增添一抹亮色。这些规则文件就像是你私人定制的AI助手,能够根据你的项目需求和个人偏好,精确地调教AI的行为。突然间,你会发现AI不仅能理解Next.js的最佳实践,还能自动应用TypeScript的类型检查,甚至主动提供Tailwind CSS的类名建议。探索新的应用场景,推动AI辅助编程的边界。

    探索Django 5: 从零开始,打造你的第一个Web应用

    探索Django 5: 从零开始,打造你的第一个Web应用

     服务器/数据库  2个月前  1.12k

    Django 是一个开放源代码的 Web 应用程序框架,由 Python 写成。它遵循 MVT(Model-View-Template)的设计模式,旨在帮助开发者高效地构建复杂且功能丰富的 Web 应用程序。随着每个版本的升级,Django 不断演变,提供更多功能和改进,让开发变得更加便捷。《Django 5 Web应用开发实战》集Django架站基础、项目实践、开发经验于一体,是一本从零基础到精通Django Web企业级开发技术的实战指南《Django 5 Web应用开发实战》内容以。

    MySQL 的mysql_secure_installation安全脚本执行过程介绍

    MySQL 的mysql_secure_installation安全脚本执行过程介绍

     服务器/数据库  2个月前  1.08k

    mysql_secure_installation 是 MySQL 提供的一个安全脚本,用于提高数据库服务器的安全性

    【MySQL基础篇】概述及SQL指令:DDL及DML

    【MySQL基础篇】概述及SQL指令:DDL及DML

     服务器/数据库  2个月前  482

    数据库是长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合。数据库不仅仅是数据的简单堆积,而是遵循一定的规则和模式进行组织和管理的。数据库中的数据可以包括文本、数字、图像、音频等各种类型的信息。

    Redis中的哨兵(Sentinel)

    Redis中的哨兵(Sentinel)

     服务器/数据库  2个月前  309

    ​ 上篇文章我们讲述了Redis中的主从复制(Redis分布式系统中的主从复制-CSDN博客),本篇文章针对主从复制中的问题引出Redis中的哨兵,希望本篇文章会对你有所帮助。