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

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

对于任何一个规则,当然包括多个条件,都可以抽象成如下的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);
    }

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

在这里插入图片描述

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

猜你喜欢

转载自blog.csdn.net/qq_26437925/article/details/131339489