规则引擎--规则逻辑形如“1 (2 | 3)“的抽象

news2024/11/14 13:49:05

目录

    • 规则下逻辑表达和条件的抽象
      • 表达逻辑的编码和抽象
    • 规则&规则集合
      • 条件
      • 操作符
      • 规则
      • 规则执行
        • 表达式遍历进行操作符计算添加
        • 具体条件的执行
      • 规则执行完成后得到最后的结果

规则下逻辑表达和条件的抽象

对于任何一个规则,包括多个条件,都可以抽象成如下的json形式

1,2,3分别代表3个条件,这个规则如何执行,则是"1 & (2 | 3)"

{
    "logic": "1 & (2 | 3)",
    "conditions":[
        {
            "isNot":true,
            "itemId":1,
            "left":{
                "value":"1",
                "isVar":true
            },
            "operator":"<",
            "right":{
                "value":"3",
                "isVar":true
            }
        },
        {
            "isNot":false,
            "itemId":2,
            "left":{
                "value":"1",
                "isVar":true
            },
            "operator":"==",
            "right":{
                "value":"3",
                "isVar":true
            }
        },
        {
            "isNot":false,
            "itemId":3,
            "left":{
                "value":"number",
                "isVar":true
            },
            "operator":"startsWith",
            "right":{
                "value":"666",
                "isVar":true
            }
        }
    ]
}

而每个条件抽象成如下,包括对应ID,用来关联逻辑表达式。以及条件的三要素:左变量右变量操作符

{
   "isNot":false,
   "itemId":1,
     "left":{
         "value":"1",
         "isVar":true
     },
     "operator":"<",
     "right":{
         "value":"3",
         "isVar":true
     }
}

表达逻辑的编码和抽象

1 & (2 | 3)仍然类似后缀表达式, 2 | 3 执行的结果在和 1 进行 &。1 & (2 | 3)解析后如下:

在这里插入图片描述

直接利用操作符表达式:包含操作数据操作符

public class OperatorExpr implements Expr {

	private List<Expr> operands;

	private String operator;

	/**
	 * !标志,not在表达式和操作符中都可能出现,用这个key来区分这两种情况,主要用于统计分析
	 *
	 * 其余取值无意义
	 */
	private String notToken;

	/**
	 * 表达式的标识,不是所有的Operator都有
	 */
	private int id = -1;

	public OperatorExpr(String operator, List<Expr> operands) {
		this(operator, operands, "");
    }

	public OperatorExpr(String operator, List<Expr> operands, String notToken) {
		this.operator = operator;
		this.operands = operands;
		this.notToken = notToken;
	}

	public List<Expr> getOperands() {
		return operands;
	}

	public void setOperands(List<Expr> operands) {
		this.operands = operands;
	}

	public String getOperator() {
		return operator;
	}

	public void setOperator(String operator) {
		this.operator = operator;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	@Override
	public void accept(ExprVisitor visitor) {
		visitor.visit(this);
	}

}

规则&规则集合

条件

public class Condition {
    /**
     * 序号。
     *
     * 一般从 1 开始。
     */
    private int itemId;

    /**
     * 操作符。
     *
     * 比如 : 大于,不等于 等
     */
    private String operator;

    /**
     *
     */
    private boolean isNot;

    /**
     * 左变量
     */
    private Operand left;

    /**
     * 右变量
     */
    private Operand right;


操作符

在这里插入图片描述

public abstract class AbstractBaseOperator extends AbstractIdentifiableOperator{

    /**
     * @param context 引擎执行上下文
     * @return 识别结果
     */
    @Override
    public EvalResult doEval(EngineExecutionContext context) {
        Variable op1 = operands.get(0);
        Variable op2 = operands.get(1);
        if (op1 == null || op2 == null) {
            throw new IllegalArgumentException("argument in  operator can't  be null.");
        }


        Object a, b = null;
        a = op1.eval(context);

        //op2.eval()是个耗时操作。如果op1是Unknown,op2.eval就没必要做了。
        if (a == EvalResult.Unknown) {
            return EvalResult.Unknown;
        }

        if (op2 != null) {
            b = op2.eval(context);
        }
        if (b == EvalResult.Unknown) {
            return EvalResult.Unknown;
        }

        return invokeOperator(a, b, context);
    }


    private EvalResult invokeOperator(Object a, Object b, EngineExecutionContext context) {
        try {
            // 第一遍求值时,忽略高耗时操作符
            return EvalResult.valueOf(apply(a, b, context));
        } catch (Throwable e) {
            throw e;
        } finally {

        }
    }


    /**
     * 操作符具体逻辑
     *
     * @param a       左操作实参
     * @param b       右操作实参
     * @param context 上下文
     * @return true/false
     */
    protected abstract Boolean apply(Object a, Object b, EngineExecutionContext context);

    @Override
    public void accept(EvaluableVisitor visitor) {
        visitor.visit(this);
    }

}
  1. 需要进行左右操作数取数求值的逻辑,eval方法
  2. 执行操作符运算,accept方法

之所以使用接口,主要是实现延迟运行或者说用时操作,即需要执行的时候才执行,可参考复习:https://doctording.blog.csdn.net/article/details/121593411

规则


public class Rule implements Serializable, Evaluable<RuleResult>{
    protected String id;
    protected String title;
    private RuleEntry ruleEntry;
    protected volatile Evaluable<EvalResult> expression;

    public Rule() {
    }

    public Rule(RuleEntry entry) {
        this.id = entry.getId();
        this.expression = parse(entry);
        this.ruleEntry = entry;
    }

    @Override
    public RuleResult eval(EngineExecutionContext context) {

        long startTime = System.nanoTime();
        RuleResult result = new RuleResult(EvalResult.False, this);
        try {
            EvalResult evalResult = getExpression().eval(context);
            result.setEvalResult(evalResult);
        } catch (Exception ab) {
            result = new RuleResult(ab);
        } finally {
            result.setCost(System.nanoTime() - startTime);
        }

        return result;
    }

    @Override
    public void accept(EvaluableVisitor visitor) {
        visitor.visit(this);
    }

    //
    public Evaluable<EvalResult> getExpression() {
        return expression;
    }

    // parse表达式并执行
    public Evaluable<EvalResult> parse(RuleEntry rawRule) {
        try {
            Expr expr = parseExpr(rawRule.getExpression());
            return toEvaluable(rawRule, expr);
        } catch (Exception e) {
            throw new IllegalArgumentException("parse rule error, ruleId: " + rawRule.getId(), e);
        }
    }

    public Expr parseExpr(Expression rawExpr) {
        if (rawExpr == null) {
            throw new IllegalArgumentException("absence of raw expression");
        }

        return parseExpr(rawExpr.getLogic(), rawExpr.getConditions());
    }

    public Expr parseExpr(String logic, List<Condition> conditions) {
        ExprParser parser = new ExprParser(logic, conditions);
        return parser.expr();
    }

    public static Evaluable<EvalResult> toEvaluable(RuleEntry rawRule, Expr expr) {
        if (expr == null) {
            return null;
        }
        String ruleId = rawRule.getId();
        EvalExprVisitor visitor = new EvalExprVisitor();
        expr.accept(visitor);

        return visitor.getEvalExpr();
    }
}
  • 规则属性包含条件表达式及其运算逻辑
  • 规则初始化就把条件表达式初始化好了,并做校验
  • 规则的执行则是执行表达式的过程

规则执行

public class RuleTest {
    public static void main(String[] args) {
        String logic = "1 &(2|3)";
        List<Condition > conditions = new ArrayList<>();
        Condition condition1 = new Condition();
        condition1.setItemId(1);
        Operand operandLeft = new Operand();
        operandLeft.setIsVar(true);
        operandLeft.setValue("age");
        operandLeft.setModifier("age");
        condition1.setLeft(operandLeft);
        condition1.setOperator(Operator.GT);
        Operand operandRight = new Operand();
        operandRight.setIsVar(false);
        operandRight.setValue("18");
        operandRight.setType("int");
        condition1.setRight(operandRight);
        conditions.add(condition1);

        Condition condition2 = new Condition();
        condition2.setItemId(2);
        Operand operandLeft2 = new Operand();
        operandLeft2.setIsVar(false);
        operandLeft2.setValue("2");
        condition2.setLeft(operandLeft2);
        condition2.setOperator(Operator.LT);
        Operand operandRight2 = new Operand();
        operandRight2.setIsVar(false);
        operandRight2.setValue("1");
        condition2.setRight(operandRight2);
        conditions.add(condition2);

        Condition condition3 = new Condition();
        condition3.setItemId(3);
        Operand operandLeft3 = new Operand();
        operandLeft3.setIsVar(true);
        operandLeft3.setValue("number");
        operandLeft3.setModifier("number");
        condition3.setLeft(operandLeft3);
        condition3.setOperator(Operator.CONTAINS_STRING);
        Operand operandRight3 = new Operand();
        operandRight3.setIsVar(false);
        operandRight3.setValue("666");
        condition3.setRight(operandRight3);
        conditions.add(condition3);

        Expression expression = new Expression(logic, conditions);

        RuleEntry ruleEntry = new RuleEntry();
        ruleEntry.setId("1");
        ruleEntry.setExpression(expression);

        Rule rule = new Rule(ruleEntry);

        EngineExecutionContext engineExecutionContext = new EngineExecutionContext();
        Map<String, Object> ctx = new HashMap<>();
        ctx.put("age", 19);
        ctx.put("number", "666abc");
        engineExecutionContext.setData(ctx);

        RuleResult ruleResult = rule.eval(engineExecutionContext);
        System.out.println(ruleResult);
    }
}

表达式遍历进行操作符计算添加

public class EvalExprVisitor implements ExprVisitor {

	private Operator<?, EvalResult> expr = null;
	private Operator<Evaluable<?>, ?> currentParent = null;

	@Override
	public boolean visit(IdentifierExpr x) {
		if (null != currentParent && null != x) {
			currentParent.addOperand(new Identifier(x.getConditionId(),x.getName()));
		}
		return false;
	}

	@Override
	public boolean visit(LiteralExpr x) {
		if (null != currentParent && null != x) {
			currentParent.addOperand(new Literal(x.getValue()));
		}
		return false;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public boolean visit(OperatorExpr x) {
		String operator = x.getOperator();

		// 	获取操作符实际例子
		Operator opexpr = OperatorLoader.getOperator(operator);

		if (expr == null) {
			expr = opexpr;
		}else {
			currentParent.addOperand(opexpr);
		}

		Operator<Evaluable<?>, ?> oldParent = currentParent;

		currentParent = opexpr;
		List<Expr> operands = x.getOperands();
		if (operands != null && !operands.isEmpty()) {
			for (Expr expr : operands) {
				expr.accept(this);
			}
		}
		currentParent = oldParent;
		return false;
	}

	public Evaluable<EvalResult> getEvalExpr() {
		return expr;
	}
}

不同的表达式类型

public interface ExprVisitor {

    /**
     * 访问变量表达式
     *
     * @param identifierExpr
     * @return
     */
    boolean visit(IdentifierExpr identifierExpr);

    /**
     * 访问常量表达式
     *
     * @param literalExpr
     * @return
     */
    boolean visit(LiteralExpr literalExpr);

    /**
     * 访问操作符表达式
     *
     * @param operatorExpr
     * @return
     */
    boolean visit(OperatorExpr operatorExpr);

}
  • 表达式可以通过在程序启动前就缓存起来
  • 可以开放SPI,支持用户自定义的表达式

具体条件的执行

规则下某个条件的执行,实际上是某个操作符的执行

  1. 第一步: 获取左右操作数, 需要根据context动态执行获取最终的值
  2. 第二步:执行操作符的具体判断比较
  3. 第三步:得到结果,或者作为其它操作符的执行的操作数
public abstract class AbstractBaseOperator extends AbstractIdentifiableOperator{

    /**
     * @param context 引擎执行上下文
     * @return 识别结果
     */
    @Override
    public EvalResult doEval(EngineExecutionContext context) {
        Variable op1 = operands.get(0);
        Variable op2 = operands.get(1);
        if (op1 == null || op2 == null) {
            throw new IllegalArgumentException("argument in  operator can't  be null.");
        }


        Object a, b = null;
        a = op1.eval(context);

        //op2.eval()是个耗时操作。如果op1是Unknown,op2.eval就没必要做了。
        if (a == EvalResult.Unknown) {
            return EvalResult.Unknown;
        }

        if (op2 != null) {
            b = op2.eval(context);
        }
        if (b == EvalResult.Unknown) {
            return EvalResult.Unknown;
        }

        return invokeOperator(a, b, context);
    }


    private EvalResult invokeOperator(Object a, Object b, EngineExecutionContext context) {
        try {
            // 第一遍求值时,忽略高耗时操作符
            return EvalResult.valueOf(apply(a, b, context));
        } catch (Throwable e) {
            throw e;
        } finally {

        }
    }


    /**
     * 操作符具体逻辑
     *
     * @param a       左操作实参
     * @param b       右操作实参
     * @param context 上下文
     * @return true/false
     */
    protected abstract Boolean apply(Object a, Object b, EngineExecutionContext context);

    @Override
    public void accept(EvaluableVisitor visitor) {
        visitor.visit(this);
    }

规则执行完成后得到最后的结果

在这里插入图片描述

在规则执行过程中可以记录每个条件的执行情况:耗时,异常,取数问题等等。而在返回的结果中,也可以记录,以方便后续的问题排查。

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

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

相关文章

抽象确实JavaScript

看完上篇的添加事件&#xff0c;我想肯定有一万个黑马在奔腾 明明是照着添加宾语的公式来的&#xff0c;为什么会有报错&#xff1f; 事件不是说可以随便设置吗&#xff1f;但是会出问题 this又是什么关键词&#xff1f; value是啥&#xff1f; 围绕这三大疑问&#xff0c…

LED显示产业如何突破芯片短板

LED显示产业在突破芯片短板方面可以采取以下措施&#xff1a; 研发先进的芯片技术&#xff1a;LED显示芯片的研发是关键。通过投入更多资源和资金&#xff0c;研究机构和企业可以致力于开发更先进、更高效的LED显示芯片技术。这包括改进光电转换效率、提高亮度和色彩表现力等方…

Python基础合集 练习25 (正则表达式)

[0123456789] 普通字符 [0-9]简洁写法 在正则表达式中所有字符类型都有对应的编码 在匹配大写英文字母时,应该是 “”" [a-zA-Z]或[A-Za-z] “”" 元字符… … (.[0-9]{1,3}){3}进行重复三次操作 ^\d{9}$ 使用^和$匹配开始和结束位置,\d表示匹配数字,{9}表示…

【C++篇】C++的动态分配内存

友情链接&#xff1a;C/C系列系统学习目录 知识点内容正确性以C Primer&#xff08;中文版第五版&#xff09;、C Primer Plus&#xff08;中文版第六版&#xff09;为标准&#xff0c;同时参考其它各类书籍、优质文章等&#xff0c;总结归纳出个人认为较有逻辑的整体框架&…

处理 Python 3.11 弃用的 PySys_SetPath 和 Py_SetProgramName

在C调用matplotlibcpp.h画图时报错&#xff0c;使用的python版本是3.11.3版本。 解决方案&#xff1a;不重要的话&#xff0c;注释该行代码即可。 Python 3.11 弃用 PySys_SetPath 和 Py_SetProgramName。这 PyConfig API 取代了这些功能和其他功能。此提交使用 PyConfig API …

深入理解深度学习——GPT(Generative Pre-Trained Transformer):基础知识

分类目录&#xff1a;《深入理解深度学习》总目录 《深入理解深度学习——Transformer》系列文章介绍了Transformer&#xff0c;该模型最初被用于机器翻译任务&#xff0c;其出色表现引起了学术界的极大兴趣&#xff0c;其优异的特征提取与语义抽象能力得到了学者的广泛认可&am…

企业做seo有什么好处?SEO 为您的企业带来的 10 大主要优势?

如果您希望建立长期的品牌知名度、扩大目标受众并赚取更多收入&#xff0c;那么搜索引擎优化至关重要。让我们看看 SEO 为您的企业带来的 10 大好处&#xff0c;以及如何实现它们。 1. 它提高了你的可信度 在搜索引擎结果页面上排名靠前的网站通常被搜索引擎认为是高质量和值得…

【unity细节】—怎么将unity编译时和运行时的功能隔开

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 收录于专栏&#xff1a;unity细节和bug ⭐怎么将unity编译时和运行时的功能隔开的问题⭐ 文章目录 ⭐怎么将unity编译时和…

TCP的十个核心机制

目录 前言一 到 三四. 滑动窗口五. 流量控制六. 拥塞控制七. 延时应答八. 捎带应答九. 面向字节流十. 异常处理总结 前言 TCP协议是传输层的重点协议, 负责将数据从发送端传输到接收端. TCP协议是传输控制协议, 顾名思义也就是对数据的传输进行控制的协议. TCP 协议有很多, 我…

android存储3--初始化.unlock事件的处理

android版本&#xff1a;android-11.0.0_r21http://aospxref.com/android-11.0.0_r21 概述&#xff1a;收到unlock事件后&#xff0c;StorageSessionController、vold、storaged模块进行各自初始化操作。 一、StorageManagerService::onUnlockUser处理unlock事件 设备解锁后…

openlayers瓦片的使用

OpenLayers是一个用于WebGIS客户端的地图开发库&#xff0c;支持多种地图。在使用瓦片时&#xff0c;先将自己需要的瓦片下载&#xff0c;下载好的瓦片会分层&#xff0c;越高的层级瓦片的数量余额多。 使用时可以引入 ol.js 文件&#xff0c;和 ol.css 文件&#xff0c;或者使…

机器学习 | 实验五:LDA

LDA的思想&#xff1a;“投影后类内方差最小&#xff0c;类间方差最大”。即数据在低维度上进行投影&#xff0c;投影后希望每一种类别数据的投影点尽可能的接近&#xff0c;而不同类别的数据的类别中心之间的距离尽可能的大。 假设我们有两类数据分别为红色和蓝色&#xff0c;…

关于socket编程中FD_XXX以及select函数的理解

文章目录 01 | 宏接口定义02 | 使用方法03 | 服务端代码示例 学习socket编程的时候看到很多FD开头的宏定义和函数&#xff0c;这里记录一下这些宏定义和函数的含义及处理流程 01 | 宏接口定义 fd_set fd_set 是一种表示文件描述符的集合类型&#xff0c;在socket编程中&#xf…

计算机网络——自顶向下方法(第三章学习记录)

本章学习运输层 运输层位于应用层和网络层之间&#xff0c;是分层的网络体系的重要部分&#xff0c;该层为运行在不同主机上的应用进程提供直接的通信服务起着至关重要的作用。 运输层协议为运行在不同主机上的应用进程之间提供了逻辑通信(logic communication)功能。从应用程…

CSS3-补充-伪元素

伪元素 作用&#xff1a;在网页中创建非主体内容&#xff0c;开发中常用CSS创建标签&#xff0c;比如装饰性的不重要的小图 区别&#xff1a; 1 元素&#xff1a;HTML 设置的标签 2 伪元素&#xff1a;由 CSS 模拟出的标签效果 …

EMC学习笔记(七)阻抗控制(一)

阻抗控制&#xff08;一&#xff09; 1.特征阻抗的物理意义1.1 输入阻抗1.2 特征阻抗1.3 偶模阻抗、奇模阻抗、差分阻抗 2.生产工艺对阻抗控制的影响 1.特征阻抗的物理意义 1.1 输入阻抗 在集总电路中&#xff0c;输入阻抗是经常使用的一个术语 &#xff0c;它的物理意义是: …

FreeRTOS实时操作系统(六)列表与列表项

系列文章目录 文章目录 系列文章目录简要概念列表列表项迷你列表项 相关API函数初始化列表列表项初始化列表项插入&#xff08;升序&#xff09;末尾列表项插入列表项删除 实战实验 简要概念 列表是 FreeRTOS 中的一个数据结构&#xff0c;概念上和链表有点类似&#xff0c;列…

ubuntu环境下测试硬盘读写速度

在Ubuntu下&#xff0c;可以使用hdparm、dd和fio等工具来测试硬盘的读写速度。 开始之前&#xff0c;先使用sudo fdisk -l命令来列出系统中所有的硬盘和分区&#xff1a; 1.使用hdparm测试硬盘读取速度&#xff1a; 安装hdparm&#xff1a; sudo apt-get install hdparm 通…

C++17中utf-8 character literal的使用

一个形如42的值被称作字面值常量(literal),这样的值一望而知。每个字面值常量都对应一种数据类型&#xff0c;字面值常量的形式和值决定了它的数据类型。 由单引号括起来的一个字符称为char型字面值&#xff0c;双引号括起来的零个或多个字符则构成字符串型字面值。 字符串字面…

9.QT 三目运算符

上面引出两个新的概念&#xff1a; 左值&#xff1a;能被赋值的就是左值。 右值&#xff1a;不能被赋值的就是右值。