ITPub博客

首页 > 数据库 > 数据库开发技术 > EasyRules规则引擎工具类

EasyRules规则引擎工具类

原创 数据库开发技术 作者:毛毛小妖的笔记 时间:2023-04-06 09:00:00 0 删除 编辑

EasyRules是一款基于Java的开源的轻量级的规则引擎框架。它可以帮助开发人员快速开发并管理规则,实现应用程序的自动化决策。EasyRules框架非常易于使用,且可以与任何Java应用程序无缝集成。在本文中,我们将对其进行一个简单的封装,以实现复杂的规则表达式匹配。

一、easyRules的基本概念

EasyRules是一个基于规则的引擎,它基于规则引擎的常见原则和概念。以下是一些EasyRules框架中的重要概念:

  • 规则(Rule):规则是EasyRules框架中的核心概念,它用于描述应用程序中需要遵循的规则。每个规则通常包含两个部分:规则名称和规则条件。

  • 规则条件(Condition):规则条件定义了规则的前提条件。如果规则条件为true,则规则将被触发执行。否则,规则将被忽略。

  • 规则动作(Action):规则动作是在规则被触发时执行的一段代码。它可以用于实现各种应用程序逻辑,例如更新数据、发送消息等。

  • 规则执行(Rule Engine):规则执行是EasyRules框架的核心功能之一,它负责解析规则条件,并根据条件执行相应的规则动作。

二、简单demo

了解了easyRules的基本概念后,我们来写一个简单的demo:

假设我们有一个需求,根据用户的年龄来决定是否可以购买酒类产品,如果用户年龄小于18岁,则不能购买酒类产品。

首先,我们需要定义一个规则类,继承自org.jeasy.rules.annotation.Rule,如下所示:

import org.jeasy.rules.annotation.Condition;import org.jeasy.rules.annotation.Fact;import org.jeasy.rules.annotation.Rule;
@Rule(name = "age rule", description = "Check if user is of legal age to buy alcohol")public class AgeRule {
@Condition public boolean checkAge(@Fact("age") int age) { return age >= 18; }}

在这个规则类中,我们定义了一个名为“checkAge”的条件方法,它接受一个名为“age”的事实参数,并返回一个布尔值表示用户是否满足购买酒类产品的年龄要求。

接下来,我们需要创建一个规则引擎实例,并将规则类添加到规则引擎中。代码如下所示:

import org.jeasy.rules.api.Facts;import org.jeasy.rules.api.Rules;import org.jeasy.rules.core.RulesImpl;import org.jeasy.rules.core.DefaultRulesEngine;
public class RuleEngineDemo { public static void main(String[] args) { AgeRule ageRule = new AgeRule(); Rules rules = new RulesImpl(); rules.register(ageRule); DefaultRulesEngine rulesEngine = new DefaultRulesEngine(); Facts facts = new Facts(); facts.put("age", 20); rulesEngine.fire(rules, facts); }}

在这个示例代码中,我们创建了一个名为“ageRule”的规则对象,并将其注册到名为“rules”的规则集合中。接着,我们创建了一个默认的规则引擎实例,并创建了一个名为“facts”的事实对象,并将“age”和“20”作为键值对添加到事实对象中。最后,我们通过调用规则引擎的fire方法来启动规则引擎并触发规则执行。

三、easyRules工具类

以上示例展示了如何快速的使用easyRule实现规则创建及调用的流程。但是如果我们有更复杂的场景,比如在数据质量规则校验中,我们有如下的规则判断:

对于这种比较复杂且校验规则经常会发生变化的规则,通过上述代码就无法实现了,为此,我将对easyRule做进一步的封装,以达到此目的。

1. 定义常量类

首先定义一个常量类

package com.shsc.bigdata.indicator.monitor.common.constant;
public class EasyRulesConstants {
// 事实别名 public static final String FACT_ALIAS = "fact"; // 结果别名 public static final String RESULT_ALIAS = "result"; // and关系 public static final String RELATION_AND = "and"; // or关系 public static final String RELATION_OR = "or"; // 匹配成功信息 public static final String MATCH_SUCCESS_MESSAGE = "匹配成功"; public static final String FIELD_TYPE = "type"; public static final String FIELD_OPERATOR = "operator"; public static final String FIELD_NAME = "metricName"; public static final String FIELD_VALUE = "value"; public static final String FIELD_CHILDREN = "children"; public static final String EXPRESSION_TYPE = "EXPRESSION"; public static final String RELATION_TYPE = "RELATION"; public static final String LEFT_BRACKETS = "("; public static final String RIGHT_BRACKETS = ")"; public static final String SYMBOL_SPACE = " "; public static final String SYMBOL_EMPTY = ""; public static final String LOGICAL_AND = "&&"; public static final String LOGICAL_OR = "||";}

2. 定义枚举类

定义一个枚举类,罗列出常用的运算符

package com.shsc.bigdata.indicator.monitor.common.enums;
public enum EasyRulesOperation { GREATER_THAN("GREATER_THAN", "%s > %s", "大于"), GREATER_THAN_EQUAL("GREATER_THAN_EQUAL", "%s >= %s", "大于等于"), LESS_THAN("LESS_THAN", "%s < %s", "小于"), LESS_THAN_EQUAL("LESS_THAN_EQUAL", "%s <= %s", "小于等于"), EQUAL("EQUAL", "%s == %s", "等于"), UNEQUAL("UNEQUAL", "%s != %s", "不等于"), BETWEEN("BETWEEN", "%s >= %s && %s <= %s", "介于之间"), OUT_OF_RANGE("OUT_OF_RANGE", "%s >= %s || %s >= %s", "超出范围"), CONTAINS("CONTAINS", "%s.contains(\"%s\")", "包含"), STARTSWITH("STARTSWITH", "%s.startsWith(\"%s\")", "前缀"), ENDSWITH("ENDSWITH", "%s.endsWith(\"%s\")", "后缀"), ;
public static EasyRulesOperation getOperationByOperator(String operator) { EasyRulesOperation[] list = EasyRulesOperation.values(); for (EasyRulesOperation item : list) { String compareOperator = item.getOperator(); if (compareOperator.equals(operator)) { return item; } } return null; }
private final String operator; private final String expression; private final String remark;
EasyRulesOperation(String operator, String expression, String remark) { this.operator = operator; this.expression = expression; this.remark = remark; }
public String getOperator() { return operator; }
public String getExpression() { return expression; }
public String getRemark() { return remark; }}
3. 工具类
package com.shsc.bigdata.indicator.monitor.common.utils.easyRules;
import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;import com.shsc.bigdata.indicator.monitor.common.enums.EasyRulesOperation;import lombok.Data;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.ObjectUtils;import org.apache.commons.lang3.StringUtils;import org.jeasy.rules.api.Facts;import org.jeasy.rules.api.Rules;import org.jeasy.rules.api.RulesEngine;import org.jeasy.rules.core.DefaultRulesEngine;import org.jeasy.rules.mvel.MVELRule;import static com.shsc.bigdata.indicator.monitor.common.constant.EasyRulesConstants.*;
@Slf4jpublic class EasyRulesUtil {
/** * 执行规则匹配 * @param fact 事实json * @param ruleModel 规则模型 */ public static RuleResult match(JSONObject fact, RuleModel ruleModel){ // 结果 RuleResult result = new RuleResult(); result.setRuleId(ruleModel.getRuleId()); // 规则实例 Facts facts = new Facts(); facts.put(FACT_ALIAS, fact); facts.put(RESULT_ALIAS, result); // 规则内容 org.jeasy.rules.api.Rule mvelrule = new MVELRule() .name(ruleModel.getRuleName()) .description(ruleModel.getDescription()) .when(ruleModel.getWhenExpression()) .then(ruleModel.getThenExpression()); // 规则集合 Rules rules = new Rules(); // 将规则添加到集合 rules.register(mvelrule); // 创建规则执行引擎,并执行规则 RulesEngine rulesEngine = new DefaultRulesEngine(); rulesEngine.fire(rules, facts); return result; }
/** * 构建mvel条件表达式 * @param json 节点json,例如: * { * "type": "EXPRESSION", * "operator": "LESS_THAN", * "metricName": "NORMAL_NUMBER", * "value": "11", * "children": [] * } */ public static String buildWhenExpression(JSONObject json) { StringBuilder mvelExpression = new StringBuilder(); String type = json.getString(FIELD_TYPE); String operator = json.getString(FIELD_OPERATOR);
switch (type) { case EXPRESSION_TYPE: String fieldName = json.getString(FIELD_NAME); String fieldValue = json.getString(FIELD_VALUE); mvelExpression.append(buildOperatorExpress(operator, fieldName, fieldValue)); break; case RELATION_TYPE: JSONArray children = json.getJSONArray(FIELD_CHILDREN); if (children.size() == 0) { return SYMBOL_EMPTY; } operator = convertRelationExpress(operator); StringBuilder childrenExpression = new StringBuilder(); for (int i = 0; i < children.size(); i++) { JSONObject child = children.getJSONObject(i); // 递归构建单个规则条件 String childExpression = buildWhenExpression(child); if (!childExpression.isEmpty()) { if (childrenExpression.length() > 0) { childrenExpression.append(SYMBOL_SPACE).append(operator).append(SYMBOL_SPACE); } childrenExpression.append(LEFT_BRACKETS).append(childExpression).append(RIGHT_BRACKETS); } } mvelExpression.append(childrenExpression); break; default: break; } return mvelExpression.toString(); }
/** * 构建mvel结果表达式 */ public static String buildThenExpression() { StringBuilder expression = new StringBuilder(); expression.append(RESULT_ALIAS).append(".setValue(\"").append(MATCH_SUCCESS_MESSAGE).append("\");"); log.info("thenExpression: {}", expression); return expression.toString(); }
/** * 转换条件连接符 * @param relation 条件连接符 */ private static String convertRelationExpress(String relation) { if (StringUtils.isEmpty(relation)){ return SYMBOL_EMPTY; } else if(relation.equalsIgnoreCase(RELATION_AND)){ return LOGICAL_AND; } else if(relation.equalsIgnoreCase(RELATION_OR)){ return LOGICAL_OR; } return relation; }
/** * 构建mvel表达式 * @param operator 操作符 * @param fieldName 字段名称 * @param value 字段值 */ private static String buildOperatorExpress(String operator, String fieldName, Object value) { EasyRulesOperation operation = EasyRulesOperation.getOperationByOperator(operator); if (ObjectUtils.isNotEmpty(operation)) { String expression = operation.getExpression(); return String.format(expression, buildValueExpress(fieldName), value); } return SYMBOL_EMPTY; }
/** * 构建mvel取值表达式 * @param fieldName 字段名称 */ private static String buildValueExpress(String fieldName) { return String.format("%s.get(\"%s\")", FACT_ALIAS, fieldName); }
@Data public static class RuleModel { private String ruleId; String ruleName; String description; String whenExpression; String thenExpression; }
@Data public static class RuleResult { // 规则主键 private String ruleId;
// 是否匹配, 默认false boolean isMatch = false;
// 匹配信息,默认为匹配失败 String message = "匹配失败";
/** * 匹配成功后设置成功信息 */ public void setValue(String message){ this.message = message; this.isMatch = true; } }}
4. 测试
package com.shsc.bigdata.indicator.monitor.common;
import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import com.shsc.bigdata.indicator.monitor.common.utils.easyRules.EasyRulesUtil;import lombok.extern.slf4j.Slf4j;
@Slf4jpublic class TestEasyRules { public static void main(String[] args) { // 1. 新增规则 EasyRulesUtil.RuleModel ruleModel = new EasyRulesUtil.RuleModel(); ruleModel.setRuleId("1"); ruleModel.setRuleName("rule1"); ruleModel.setDescription("测试规则"); // 2. 设置规则条件 String ruleJson = "{\n" + " \"validateCondition\": {\n" + " \"type\": \"RELATION\",\n" + " \"operator\": \"OR\",\n" + " \"children\": [\n" + " {\n" + " \"type\": \"EXPRESSION\",\n" + " \"operator\": \"LESS_THAN\",\n" + " \"metricName\": \"NORMAL_NUMBER\",\n" + " \"value\": \"11\",\n" + " \"children\": []\n" + " },\n" + " {\n" + " \"type\": \"EXPRESSION\",\n" + " \"operator\": \"LESS_THAN_EQUAL\",\n" + " \"metricName\": \"ERROR_NUMBER\",\n" + " \"value\": \"11\",\n" + " \"children\": []\n" + " },\n" + " {\n" + " \"type\": \"RELATION\",\n" + " \"children\": [\n" + " {\n" + " \"type\": \"EXPRESSION\",\n" + " \"metricName\": \"NORMAL_NUMBER\",\n" + " \"operator\": \"GREATER_THAN\",\n" + " \"value\": 10,\n" + " \"children\": []\n" + " },\n" + " {\n" + " \"type\": \"EXPRESSION\",\n" + " \"metricName\": \"ERROR_NUMBER\",\n" + " \"operator\": \"GREATER_THAN\",\n" + " \"value\": 100,\n" + " \"children\": []\n" + " },\n" + " {\n" + " \"type\": \"RELATION\",\n" + " \"children\": [\n" + " {\n" + " \"type\": \"EXPRESSION\",\n" + " \"metricName\": \"NORMAL_NUMBER\",\n" + " \"operator\": \"EQUAL\",\n" + " \"value\": 1,\n" + " \"children\": []\n" + " },\n" + " {\n" + " \"type\": \"EXPRESSION\",\n" + " \"metricName\": \"ERROR_NUMBER\",\n" + " \"operator\": \"EQUAL\",\n" + " \"value\": 1,\n" + " \"children \": []\n" + " }\n" + " ],\n" + " \"operator\": \"OR\"\n" + " }\n" + " ],\n" + " \"operator\": \"OR\"\n" + " }\n" + " ]\n" + " }\n" + "}"; JSONObject conditionJson = JSON.parseObject(ruleJson); // 3. 设置fact String whenExpression = EasyRulesUtil.buildWhenExpression(conditionJson.getJSONObject("validateCondition")); log.info("whenExpression:{}", whenExpression); ruleModel.setWhenExpression(whenExpression); // 4. 设置结果表达式 ruleModel.setThenExpression(EasyRulesUtil.buildThenExpression());
// 5. 设置匹配条件 JSONObject json = new JSONObject(); json.put("NORMAL_NUMBER", "10"); json.put("ERROR_NUMBER", 10); json.put("省=陕西;市=西安;", 100); // 6. 调用规则匹配 EasyRulesUtil.RuleResult result = EasyRulesUtil.match(json, ruleModel); System.out.println(result); }}

上面的例子中,我使用了MVEL表达式语言实现了动态规则,关于MVEL的知识请翻阅历史文章查看。然后传入了一个比较复杂的多层级的规则表达式,测试结果如下:

15:30:29.915 [main] INFO com.shsc.bigdata.indicator.monitor.common.EasyRules - whenExpression:(fact.get("NORMAL_NUMBER") < 11) || (fact.get("ERROR_NUMBER") <= 11) || ((fact.get("NORMAL_NUMBER") > 10) || (fact.get("ERROR_NUMBER") > 100) || ((fact.get("NORMAL_NUMBER") == 1) || (fact.get("ERROR_NUMBER") == 1)))15:30:29.919 [main] INFO com.shsc.bigdata.indicator.monitor.common.utils.easyRules.EasyRulesUtil - thenExpression: result.setValue("匹配成功");15:30:30.085 [main] DEBUG org.jeasy.rules.core.DefaultRulesEngine - Engine parameters { skipOnFirstAppliedRule = false, skipOnFirstNonTriggeredRule = false, skipOnFirstFailedRule = false, priorityThreshold = 2147483647 }15:30:30.085 [main] DEBUG org.jeasy.rules.core.DefaultRulesEngine - Registered rules:15:30:30.086 [main] DEBUG org.jeasy.rules.core.DefaultRulesEngine - Rule { name = 'rule1', description = '测试规则', priority = '2147483646'}15:30:30.086 [main] DEBUG org.jeasy.rules.core.DefaultRulesEngine - Known facts:15:30:30.086 [main] DEBUG org.jeasy.rules.core.DefaultRulesEngine - Fact{name='result', value=EasyRulesUtil.RuleResult(ruleId=1, isMatch=false, message=匹配失败)}15:30:30.086 [main] DEBUG org.jeasy.rules.core.DefaultRulesEngine - Fact{name='fact', value={"ERROR_NUMBER":10,"NORMAL_NUMBER":"10","省=陕西;市=西安;":100}}15:30:30.096 [main] DEBUG org.jeasy.rules.core.DefaultRulesEngine - Rules evaluation started15:30:30.178 [main] DEBUG org.jeasy.rules.core.DefaultRulesEngine - Rule 'rule1' triggered15:30:30.187 [main] DEBUG org.jeasy.rules.core.DefaultRulesEngine - Rule 'rule1' performed successfullyEasyRulesUtil.RuleResult(ruleId=1, isMatch=true, message=匹配成功)

从上述日志可以看到,isMatch表示匹配成功:

EasyRulesUtil.RuleResult(ruleId=1, isMatch=true, message=匹配成功)

四、总结

通过以上对easyRules的封装,我们可以实现复杂的规则表达式校验,只需要定义好表达式的json结构以及匹配的条件即可。

原文作者:毛毛小妖的笔记;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2019-04-04

  • 博文量
    70
  • 访问量
    46581