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

news2024/11/23 15:38:22

目录

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

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

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

其中1、2、3分别代表3个条件;这个规则是如何执行的,则是"1 & (2 | 3)",当然可以是类似的形式。如下json定义

{
    "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,用来关联逻辑表达式(即1、2、3),以及条件的三要素:左变量右变量操作符

{
   "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();
    }
}
  • 规则属性包含条件表达式及其运算逻辑
  • 规则初始化就把条件表达式初始化好了,并做好校验
  • 规则的执行即是执行逻辑表达式1 & (2 | 3)的过程

规则执行

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/677698.html

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

相关文章

市面上最强PDF:GcPDF 6.1.4 Grapecity -Crack

适用于 .NET 6 的功能丰富的 PDF API 库 完全控制 PDF - 快速生成文档、提高内存效率且无依赖性。 在代码中生成、加载、编辑和保存 PDF 文档 支持多种语言的全文、段落格式和字体 使用新的编辑工具编辑 PDF 中的内容 支持数百种PDF功能 Windows、macOS 和 Linux 完全支持所有…

PhotoShop Beta(爱国版)安装教程-内置AI绘画功能

PS beta版安装教程 Window和Mac版都有&#xff0c;里面内置AI绘画功能 ps Beta版真的太爽了&#xff0c;今天来和大家分享下安装教程。 很多人拿这资料卖5块 9.9 19.9&#xff0c;球友们直接用&#xff0c;建议赶紧装&#xff0c;以免PS更新后&#xff0c;很多pojie程序没法用了…

ChatGPT数据分析与可视化实战

ChatGPT从入门到精通&#xff0c;一站式掌握办公自动化/爬虫/数据分析和可视化图表制作 全面AI时代就在转角 道路已经铺好了 “局外人”or“先行者” 就在此刻 等你决定 让ChatGPT帮你高效实现职场办公&#xff01;行动起来吧1、ChatGPT从入门到精通&#xff0c;一站式掌握办…

docker安装drone

目录 Drone简介docker安装drone创建Drone-server容器创建Drone-runner-docker容器 访问drone-server面板操作 Drone简介 Drone是基于GO语言开发的持续集成&#xff08;Continuous integration&#xff0c;CI&#xff09;引擎&#xff0c;它可以借助Docker容器技术&#xff0c;…

Autosar RTE C/S接口实现及synchronous与asynchronous的区别

文章目录 前言Server接口设计server接口Simulink实现server函数mapping Function生成的代码 Client接口设计Client接口Simulink实现ClientFunction Caller Mapping生成的代码Rte_CallRte_Result 总结 前言 在之前的一篇文章中&#xff0c;介绍了RTE中的S/R接口&#xff0c;也是…

(一)WPF - WPF

一、Window 图形演化 创建用户界面&#xff1a; User32&#xff1a; 该部分为许多元素&#xff08;如窗口、按钮和文本框等&#xff09;提供了熟悉的 Windows 外观。GDI/GDI&#xff1a; 该部分为渲染简单形状、文本以及图像提供了绘图支持&#xff0c;但增加了复杂程度&…

Nginx使用

说明&#xff1a;Nginx是静态资源服务器&#xff0c;可以部署静态资源&#xff0c;并对请求进行策略分发。 下载 第一步&#xff1a;可在官网&#xff08;http://nginx.org/en/download.html&#xff09;下载&#xff0c;建议安装稳定版本&#xff08;Stable version&#xf…

【力扣刷题 | 第十三天】

前言&#xff1a; 今天随机进行练习&#xff0c;题型上不会有什么限制&#xff0c;主要还是练习STL算法。 88. 合并两个有序数组 - 力扣&#xff08;LeetCode&#xff09; 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分…

[RocketMQ] Broker与NameServer的心跳服务源码 (四)

文章目录 1.Broker发送心跳注册请求源码1.1 发送心跳包入口1.2 registerBrokerAll注册broker信息 2.NameServer处理心跳注册请求2.1 处理心跳包入口2.2 NameServer注册broker信息2.2.1 RouteInfoManager介绍2.2.2 registerBroker注册broker 3.NameServer的心跳检测服务3.1 scan…

GIS坐标系统

最新在看GIS的理论知识&#xff0c;坐标系统这块比较抽象&#xff0c;B站上搜到到一个博主的视频&#xff0c;对这块讲解的比较通俗易懂&#xff0c;这里记录一下&#xff1a; 地理坐标系统 地理坐标系统是地球表面空间要素的定位参照系统。地理坐标系统是由经度和维度定义的。…

记录Unity Endless Runner要点

1. Array.IndexOf()查找数组中指定项的索引&#xff0c;如果没找到&#xff0c;就返回-1 2. 如果粒子不是循环播放的&#xff0c;则在粒子播放完毕之后销毁它 if (!m_ParticleSpawned.main.loop)Destroy(m_ParticleSpawned.gameObject, m_ParticleSpawned.main.duration); 3. 检…

普通单目相机标定

前言 这里我们还是以普通相机为例(非鱼眼相机)来进行后续的相关标定操作,再回顾下相机的成像模型如下所示。 已知相机内参(fx,fy,u0,v0),畸变系数[k1,k2,k3,p1,p2],相机外参[R|T]。世界坐标系中点Pw(Xw,Yw,Zw),投影至像素坐标系点p(u,v)的计算过程如下。 1)由世…

操作系统———文件管理

目录 一、初识文件管理1.文件属性2.文件内部数据组织3.文件之间组织4.操作系统向上提供的功能5.文件如何存放在外存6.其他需要由操作系统实现的文件管理功能7.总结 二、文件的逻辑结构1.无结构文件与有结构文件2.有结构文件的逻辑结构2.1顺序文件2.2索引文件2.3索引顺序文件 3.…

ChatGPT 指令知识要点

ChatGPT从入门到精通&#xff0c;一站式掌握办公自动化/爬虫/数据分析和可视化图表制作 全面AI时代就在转角 道路已经铺好了 “局外人”or“先行者” 就在此刻 等你决定1、ChatGPT从入门到精通&#xff0c;一站式掌握办公自动化/爬虫/数据分析和可视( 点击观看完整版本 )https…

Linux下MySQL的安装

文章目录 下载1.选择合适的yum源2.将yum源上传到Linux服务器中 安装1.安装yum源2.使用yum源一键安装MySQL3.安装时常见的问题4.检查安装 启动MySQL登录MySQL方案一方案二 下载 1.选择合适的yum源 在Linux学习阶段我们已经得知&#xff0c;在Linux环境下要安装应用程序必须要通…

【吴恩达deeplearning.ai】基于LangChain开发大语言应用模型(上)

以下内容均整理来自deeplearning.ai的同名课程 Location 课程访问地址 DLAI - Learning Platform Beta (deeplearning.ai) 一、什么是LangChain 1、LangChain介绍 LangChain是一个框架&#xff0c;用于开发由大语言模型驱动的应用程序。开发者相信&#xff0c;最强大的、差异…

面试大数据方向必问的问题:HDFS的读写流程

HDFS读写流程 这个问题是面试大数据分析师必不可少的问题&#xff0c;有不少面试者不能完整的说出 来&#xff0c;所以请务必记住。并且很多问题都是从 HDFS 读写流程中引申出来的。 一、HDFS读流程 Client 向 NameNode 发送 RPC 请求。请求文件 block 的位置&#xff1b;Na…

深入理解深度学习——BERT派生模型:扩大掩码范围SpanBERT

分类目录&#xff1a;《深入理解深度学习》总目录 MLM训练方法是BERT拥有自然语言理解能力的核心训练方法。然而&#xff0c;BERT在预训练过程中挑选掩码词的概率是独立计算的&#xff0c;即BERT掩码词的粒度是最小的&#xff0c;可能是单个词&#xff0c;也可能是一个词的部分…

【连载】经历了十多年的电子开发,回过头来跟大家一起,看穿单片机!

「经历了十多年的单片机开发&#xff0c;站在我现在的高度来回看单片机&#xff0c;可谓望眼欲穿。」 下面振南要介绍的是“单片机的体系架构模型”&#xff0c;是超脱于任何一种具体型号的单片机芯片之上的&#xff08;我感觉我要成仙&#xff09;&#xff0c;它具有很强的普适…