文章目录
- 简介
- pom
- 规则
- 抽象规则Rule
- 基础规则BasicRule
- 事实类Facts:map
- 条件接口
- 动作接口
- 四种规则定义方式
- 注解方式
- RuleBuilder 链式
- Mvel和Spel表达式
- Yml配置
- 常用规则类
- DefaultRule
- SpELRule(Spring的表达式注入)
- 组合规则
- UnitRuleGroup
- 规则引擎
- 引擎接口
- 引擎抽象类
- 引擎类-DefaultRulesEngine
- 规则引擎参数(决定规则之间是否互斥|中断|跳过等)
- 实战
- 场景1- 恒打印
- 规则description
- 规则
- 规则引擎:使用DefaultRulesEngine
- 执行
- fire方法执行流程
- 场景2-yml
- 场景3 简单if-else
- 场景4-动态规则MVEL表达式+Json字符串
- 场景5-QLExpress
- 场景6- 动态规则Mvel + Json文件
- 场景7-履约缺货处罚金额计算
简介
1、是什么
- 基于java轻量级的规则引擎,学习成本更低、适用性更强
- 本质一个函数,y=f(x1,x2,…,xn)
- Easy Rules 每条规则都有一个条件(condition)和一个动作(action),简单地说,可以将其看作是一组 IF THEN 或类似SQL的when-then语句
2、作用
- 解决业务代码和业务规则分离,实现了将业务决策从应用程序代码中分离。 接受数据输入,解释业务规则,并根据业务规则做出业务决策。
业务系统在应用过程中,常常包含着要处理"复杂、多变"的部分,这部分往往是"业务规则"或者是"数据的处理逻辑"。因此这部分的动态规则的问题,往往需要可配置,并对系统性能和热部署有一定的要求。从开发与业务的视角主要突出以下的一些问题:
- 从开发人员视角来看
1)逻辑复杂,要使用大量if-else来实现,或者使用设计模式。但过于复杂的规则逻辑,使用设计模式也往往是存在大量并且关系复杂的类,导致代码难于维护,对新加入的同学极不友好。
2)变更时需要从头梳理逻辑,在适当的地方进行if…else…代码逻辑调整,耗费大量时间进行梳理。
3)开发周期较长,当需求发生变更时,需要研发人员安排开发周期上线,对于当下快速变化的业务,传统的开发工作方式显得捉襟见肘。
- 从业务人员视角来看
1)业务人员期望友好的管理界面,不需要专业的开发技能就能够完成规则的管理、发布。
2)期望能够实现热部署,由业务人员配置好之后即配即用。
3)减少业务规则对开发人员的依赖。
4)降低需求变动的时间成本,快速验证发布
3、怎么做:
你可以自己构建一个简单的规则引擎。你所需要做的就是创建一组带有条件和动作的规则对象rule,将它们存储在一个集合中rules,然后遍历它们以评估(fire)条件(condition)并执行这些动作(action)。
pom
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-core</artifactId>
<version>3.3.0</version>
</dependency>
<!--规则定义文件格式,支持json,yaml等-->
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-support</artifactId>
<version>3.3.0</version>
</dependency>
<!--支持mvel规则语法库-->
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-mvel</artifactId>
<version>3.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jeasy/easy-rules-spel -->
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-spel</artifactId>
<version>4.1.0</version>
</dependency>
规则
大多数业务规则可以由以下定义表示:
-
名称name:规则命名空间中的唯一规则名称
-
说明description:规则的简要说明
-
优先级priority:相对于其他规则的规则优先级,较小的值表示较高的优先级
-
事实fact:去匹配规则时的一组已知事实
-
条件condition:为了匹配该规则,在给定某些事实的情况下应满足的一组条件
如果规则条件为true,则规则将被触发执行。否则,规则将被忽略
-
动作action:当条件满足时要执行的一组动作(可以添加/删除/修改事实)
它可以用于实现各种应用程序逻辑,例如更新数据、发送消息等。
抽象规则Rule
public interface Rule extends Comparable<Rule> {
String DEFAULT_NAME = "rule";
String DEFAULT_DESCRIPTION = "description";
int DEFAULT_PRIORITY = 2147483646;
boolean evaluate(Facts var1);
void execute(Facts var1) throws Exception;
}
基础规则BasicRule
package org.jeasy.rules.core;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rule;
public class BasicRule implements Rule {
protected String name;//名称
protected String description;//说明
protected int priority;//优先
/**
* 此方法封装规则的条件(conditions)
*
* @param facts :事实
* @return 如果提供的事实适用于该规,则条件返回true
*/
public boolean evaluate(Facts facts) {
return false;
}
/**
* 此方法封装规则的操作(actions)
* evaluate方法值为TRUE才能触发execute方法(在满足规则条件时应执行的操作)。
* @throws 如果在执行过程中发生错误将抛出Exception
*/
public void execute(Facts facts) throws Exception {
}
}
事实类Facts:map
package org.jeasy.rules.api;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class Facts implements Iterable<Map.Entry<String, Object>> {
private Map<String, Object> facts = new HashMap();
public Object put(String name, Object fact) {
Objects.requireNonNull(name);
return this.facts.put(name, fact);
}
public Object remove(String name) {
Objects.requireNonNull(name);
return this.facts.remove(name);
}
public <T> T get(String name) {
Objects.requireNonNull(name);
return this.facts.get(name);
}
}
- 扩展:可以将普通对象(包含属性|方法)facts.put()。也可以将rpc对象put进来
@Resource
private MqService mqService;
facts.put("mqService", mqService)
这样condition方法就可以使用mqService的方法
.when("mqService.doRuleConditions(conditionList,tmpParam)")
或
@Override
public boolean evaluate(@Facts("mqService") MqService mqService) {
mqService.doRuleConditions();
}
条件接口
public interface Condition {
Condition FALSE = new Condition() {
public boolean evaluate(Facts facts) {
return false;
}
};
Condition TRUE = new Condition() {
public boolean evaluate(Facts facts) {
return true;
}
};
boolean evaluate(Facts var1);
}
动作接口
public interface Action {
void execute(Facts var1) throws Exception;
}
四种规则定义方式
注解方式
1、eg1(也可以实现Rule接口)
package com.mjp.easyrules.drmo1;
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Fact;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.core.BasicRule;
public class MyRule extends BasicRule {
@Override
public boolean evaluate(Facts facts) {
return false;
}
@Override
public void execute(Facts facts) throws Exception {
}
}
2、纯注解
@Rule(name = "weather rule", description = "if it rains then take an umbrella", priority = 777)
public class WeatherRule {
@Condition
public boolean itRains(@Fact("rain") boolean rain, @Fact("other") String other) {
return rain;
}
@Action(order = 1)
public void takeAnUmbrella() {
System.out.println("It rains, take an umbrella!");
}
@Action(order = 2)
public void then2(Facts facts) throws Exception {
//my actions2
}
}
RuleBuilder 链式
1、eg1
Rule rule = new RuleBuilder()
.name("myRule")
.description("myRuleDescription")
.priority(2)
.when(facts -> true)
.then(facts -> {
// do Action1
})
.then(facts -> {
// do Action2
})
.build();
2、eg2
Rule weatherRule = new RuleBuilder()
.name("weather rule")
.description("if it rains then take an umbrella")
.when(facts -> facts.get("rain").equals(true))
.then(facts -> System.out.println("It rains, take an umbrella!"))
.build();
Mvel和Spel表达式
1、eg1:Mvel表达式
Mvel表达式
Rule weatherRule = new MVELRule()
.name("weather rule")
.description("if it rains then take an umbrella")
.when("rain == true")
.then("System.out.println(\"It rains, take an umbrella!\");");
- when:对象属性
when方法参数,只能是facts对象中key对象,内部定义的属性或方法
.when("person.age > 18")
.when("person.isHappy() == true")
2、补充:
- when、then内容,可以从自定义的json文件中读取并解析成Mvel表达式形式。解析过程可参考场景6
3、eg2:Spel表达式同理
Spel表达式
Yml配置
eg1: weather-rule.yml
name: "weather rule"
description: "if it rains then take an umbrella"
condition: "true"
actions:
- "System.out.println(\"It rains, take an umbrella!\");"
- condition:对象方法
condition: "person.isAdult() == false"
condition: "person.getResult(person.getQualifications())"
eg2:一个yml文件创建多个规则
---
name: adult rule
description: when age is greater than 18, then mark as adult
priority: 1
condition: "person.age > 18"
actions:
- "person.setAdult(true);"
---
name: weather rule
description: when it rains, then take an umbrella
priority: 2
condition: "rain == true"
actions:
- "System.out.println("It rains, take an umbrella!");"
- 读取多条规则
MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());
Rule weatherRule = ruleFactory.createRule(new FileReader("D:\\CodeBetter\\src\\main\\resources\\weather-rule.yml"));
// 多条规则
Rules rules = ruleFactory.createRules(new FileReader("rules.yml"));
常用规则类
DefaultRule
package org.jeasy.rules.core;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.jeasy.rules.api.Action;
import org.jeasy.rules.api.Condition;
import org.jeasy.rules.api.Facts;
class DefaultRule extends BasicRule {
private Condition condition;//一个规则,一个条件。
private List<Action> actions;//一个规则,当满足条件时,可以有多个动作
public boolean evaluate(Facts facts) {
return this.condition.evaluate(facts);
}
public void execute(Facts facts) throws Exception {
Iterator var2 = this.actions.iterator();
while(var2.hasNext()) {
Action action = (Action)var2.next();
action.execute(facts);
}
}
}
SpELRule(Spring的表达式注入)
表达式注入
package org.jeasy.rules.spel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.jeasy.rules.api.Action;
import org.jeasy.rules.api.Condition;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.core.BasicRule;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.ParserContext;
public class SpELRule extends BasicRule {
private Condition condition;//条件
private final List<Action> actions;//动作
private final ParserContext parserContext;//文本解析
private BeanResolver beanResolver;
public boolean evaluate(Facts facts) {
return this.condition.evaluate(facts);
}
public void execute(Facts facts) throws Exception {
Iterator var2 = this.actions.iterator();
while(var2.hasNext()) {
Action action = (Action)var2.next();
action.execute(facts);
}
}
public SpELRule when(String condition) {
this.condition = new SpELCondition(condition, this.parserContext, this.beanResolver);
return this;
}
public SpELRule then(String action) {
this.actions.add(new SpELAction(action, this.parserContext, this.beanResolver));
return this;
}
}
- when方法-SpELCondition
public class SpELCondition implements Condition {
private final ExpressionParser parser;
private final Expression compiledExpression;
private BeanResolver beanResolver;
public SpELCondition(String expression, ParserContext parserContext, BeanResolver beanResolver) {
this.parser = new SpelExpressionParser();
this.beanResolver = beanResolver;
this.compiledExpression = this.parser.parseExpression(expression, parserContext);
}
public boolean evaluate(Facts facts) {
StandardEvaluationContext context = new StandardEvaluationContext();
context.setRootObject(facts.asMap());
context.setVariables(facts.asMap());
if (this.beanResolver != null) {
context.setBeanResolver(this.beanResolver);
}
return (Boolean)this.compiledExpression.getValue(context, Boolean.class);
}
}
- then方法- SpELAction
public class SpELAction implements Action {
private static final Logger LOGGER = LoggerFactory.getLogger(SpELAction.class);
private final ExpressionParser parser;
private final String expression;
private final Expression compiledExpression;
private BeanResolver beanResolver;
public SpELAction(String expression, ParserContext parserContext, BeanResolver beanResolver) {
this.parser = new SpelExpressionParser();
this.expression = expression;
this.beanResolver = beanResolver;
this.compiledExpression = this.parser.parseExpression(expression, parserContext);
}
public void execute(Facts facts) {
try {
StandardEvaluationContext context = new StandardEvaluationContext();
context.setRootObject(facts.asMap());
context.setVariables(facts.asMap());
if (this.beanResolver != null) {
context.setBeanResolver(this.beanResolver);
}
this.compiledExpression.getValue(context);
} catch (Exception var3) {
LOGGER.error("Unable to evaluate expression: '" + this.expression + "' on facts: " + facts, var3);
throw var3;
}
}
}
组合规则
抽象CompositeRule类由一组规则组成。这是一个典型地组合设计模式的实现。可以以不同方式触发组合规则。
三种CompositeRule具体子类:
UnitRuleGroup : 要么应用所有规则,要么不应用任何规则(AND逻辑)
ActivationRuleGroup : 它触发第一个适用规则,并忽略组中的其他规则(XOR逻辑)
ConditionalRuleGroup : 如果具有最高优先级的规则计算结果为true,则触发其余规则
UnitRuleGroup
- 规则1
Rule weatherRule1 = new MVELRule()
.name("weather rule1")
.description("if it rains then move")
.when("isRain == true")
.then("System.out.println(\"rule1: It rains, move!\");");
- 规则2
@Rule(name = "weather rule2", description = "if it rains then take an umbrella",priority = 777)
public class WeatherRule {
@Condition
public boolean itRains(@Fact("isRain") boolean isRain) {
return isRain;
}
@Action(order = 1)
public void takeAnUmbrella() {
System.out.println("rule2: action1-It rains, take an umbrella!");
}
@Action(order = 2)
public void then(Facts facts) throws Exception {
System.out.println("rule2: action2-边打伞,边听歌");
}
}
- 执行
// 1.创建事实fact
Facts facts = new Facts();
facts.put("isRain", true);
// 2.创建规则1(Mvel形式)
Rule weatherRule1 = new MVELRule()
.name("weather rule1")
.description("if it rains then move")
.when("isRain == true")
.then("System.out.println(\"rule1: It rains, move!\");");
// 2.创建规则1(注解形式)
WeatherRule weatherRule2 = new WeatherRule();
// 3.创建组合规则
UnitRuleGroup myUnitRuleGroup = new UnitRuleGroup("myUnitRuleGroup", "unit of weatherRule1 and weatherRule2");
myUnitRuleGroup.addRule(weatherRule1);
myUnitRuleGroup.addRule(weatherRule2);
// 4.创建规则引擎
DefaultRulesEngine defaultRulesEngine = new DefaultRulesEngine();
// 5.定义Rules
Rules rules = new Rules();
rules.register(myUnitRuleGroup);
// 7.执行
defaultRulesEngine.fire(rules, facts);
- 输出
rule2: action1-It rains, take an umbrella!
rule2: action2-边打伞,边听歌
rule1: It rains, move!
先输出规则2(优先级为777),规则1(优先级为默认值:2147483646),值越小,优先级越高
规则引擎
引擎接口
public interface RulesEngine {
RulesEngineParameters getParameters();
List<RuleListener> getRuleListeners();
List<RulesEngineListener> getRulesEngineListeners();
void fire(Rules var1, Facts var2);
Map<Rule, Boolean> check(Rules var1, Facts var2);
}
引擎抽象类
AbstractRuleEngine作用就是抽出多个引擎类共有的,不需要再各自额外重复去实现
引擎类-DefaultRulesEngine
public final class DefaultRulesEngine extends AbstractRuleEngine {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultRulesEngine.class);
public void fire(Rules rules, Facts facts) {
this.triggerListenersBeforeRules(rules, facts);
this.doFire(rules, facts);
this.triggerListenersAfterRules(rules, facts);
}
void doFire(Rules rules, Facts facts) {
Iterator var3 = rules.iterator();
while(var3.hasNext()) {
Rule rule = (Rule)var3.next();
String name = rule.getName();
int priority = rule.getPriority();
if (rule.evaluate(facts)) {
this.triggerListenersAfterEvaluate(rule, facts, true);
try {
this.triggerListenersBeforeExecute(rule, facts);
rule.execute(facts);
this.triggerListenersOnSuccess(rule, facts);
if (this.parameters.isSkipOnFirstAppliedRule()) {
LOGGER.debug("Next rules will be skipped since parameter skipOnFirstAppliedRule is set");
break;
}
} catch (Exception var8) {
this.triggerListenersOnFailure(rule, var8, facts);
if (this.parameters.isSkipOnFirstFailedRule()) {
LOGGER.debug("Next rules will be skipped since parameter skipOnFirstFailedRule is set");
break;
}
}
} else {
this.triggerListenersAfterEvaluate(rule, facts, false);
if (this.parameters.isSkipOnFirstNonTriggeredRule()) {
LOGGER.debug("Next rules will be skipped since parameter skipOnFirstNonTriggeredRule is set");
break;
}
}
}
}
public Map<Rule, Boolean> check(Rules rules, Facts facts) {
this.triggerListenersBeforeRules(rules, facts);
Map<Rule, Boolean> result = this.doCheck(rules, facts);
this.triggerListenersAfterRules(rules, facts);
return result;
}
private Map<Rule, Boolean> doCheck(Rules rules, Facts facts) {
LOGGER.debug("Checking rules");
Map<Rule, Boolean> result = new HashMap();
Iterator var4 = rules.iterator();
while(var4.hasNext()) {
Rule rule = (Rule)var4.next();
if (this.shouldBeEvaluated(rule, facts)) {
result.put(rule, rule.evaluate(facts));
}
}
return result;
}
}
规则引擎参数(决定规则之间是否互斥|中断|跳过等)
RulesEngineParameters
skipOnFirstAppliedRule:为true告诉引擎规则,被触发时跳过后面的规则。使用场景是:各个规则之间互斥
a || b || c || d,如果a为true,则b、c、d不执行
当发现一个满足条件的规则并执行了相关操作后,便不再继续判断其他规则
skipOnFirstFailedRule:失败时跳过后面的规则。
a && b && c && d,如果a为false,则b、c、d不执行
skipOnFirstNonTriggeredRule:一个规则不会被触发跳过后面的规则。
如果满足当前的规则,则执行相应的操作,直到遇到不满足条件的规则为止,并且也不会对其他规则进行判断了
rulePriorityThreshold:如果优先级超过定义的阈值,则跳过下一个规则。版本3.3已经不支持更改,默认MaxInt。
实战
场景1- 恒打印
规则description
默认打印Hello World
规则
public class MyRule extends BasicRule {
@Override
public String getName() {
return "my rule";
}
@Override
public String getDescription() {
return "my rule description";
}
@Override
public boolean evaluate(Facts facts) {
return true;
}
@Override
public void execute(Facts facts) throws Exception {
System.out.println("我是一个打印Hello World的规则");
}
}
规则引擎:使用DefaultRulesEngine
执行
public static void main(String[] args) {
// 1.创建事实fact
Facts facts = new Facts();
// 2.创建条件condition(这里MyRule中默认为true,就不自定义Condition实现类了)
// 3.创建动作action(这里MyRule中默认为:打印HelloWorld,就不自定义Action实现类了)
// 4.创建规则
MyRule myRule = new MyRule();
// 5.定义Rules
Rules rules = new Rules();
rules.register(myRule);
// 6.创建规则引擎
DefaultRulesEngine defaultRulesEngine = new DefaultRulesEngine();
// 7.执行
defaultRulesEngine.fire(rules, facts);
}
输出
DefaultRulesEngineListener - Rules evaluation started
DefaultRuleListener - Rule 'my rule' triggered
我是一个打印Hello World的规则
DefaultRuleListener - Rule 'my rule' performed successfully
fire方法执行流程
第一步:
List<RulesEngineListener> rulesEngineListeners;
执行 void beforeEvaluate(Rule var1, Facts var2)
第二步:
MyRule的boolean evaluate(Facts facts)
第三步:
List<RuleListener> ruleListeners;
执行void afterEvaluate(Rule var1, Facts var2, boolean evaluateResult)
第四步:
List<RuleListener> ruleListeners;
执行void beforeExecute(Rule var1, Facts var2);
第五步:
MyRule的 void execute(Facts facts)
第六步:
List<RuleListener> ruleListeners;
执行void onSuccess(Rule var1, Facts var2);
场景2-yml
1、规则description
if it rains then take an umbrella
2、定义规则
weather-rule.yml
name: "weather rule"
description: "if it rains then take an umbrella"
condition: "isRain == true"
actions:
- "System.out.println(\"It rains, take an umbrella!\");"
3、自定义规则引擎:使用默认的
4、执行
// 1.创建事实fact
Facts facts = new Facts();
facts.put("isRain", true);
// 2.创建条件condition(weatherRule中:isRain == true,就不自定义Condition实现类了)
// 3.创建动作action(weatherRule中默认为:It rains, take an umbrella!,就不自定义Action实现类了)
// 4.创建规则(Yml形式)
MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());
Rule weatherRule = ruleFactory.createRule(new FileReader("D:\\CodeBetter\\src\\main\\resources\\weather-rule.yml"));
// 5.创建规则引擎
DefaultRulesEngine defaultRulesEngine = new DefaultRulesEngine();
// 6.定义Rules
Rules rules = new Rules();
rules.register(weatherRule);
// 7.执行
defaultRulesEngine.fire(rules, facts);
场景3 简单if-else
1、功能描述
从1数到100,并且:
- 需求1:如果数字是5的倍数,则打印"i :是5的倍数"
- 需求2:如果数字是7的倍数,请打印"i :是7的倍数"
- 需求3:如果数字是5和7的倍数,请打印"i :是5的倍数、7的倍数"
- 需求4:否则打印数字本身
2、常规实现方法
public class FizzBuzz {
public static void main(String[] args) {
for(int i = 1; i <= 100; i++) {
if (((i % 5) == 0) && ((i % 7) == 0))
System.out.print("fizzbuzz");
else if ((i % 5) == 0) System.out.print("fizz");
else if ((i % 7) == 0) System.out.print("buzz");
else System.out.print(i);
}
}
}
3、使用Easy Rules规则引擎实现
将每个需求编写一条规则:
- rule1
@Rule(name = "Mod5Rule", description = "mod5", priority = 1)
public class Mod5Rule {
@Condition
public boolean canMod5(@Fact("number") Integer number) {
return number % 5 == 0;
}
@Action
public void action(Facts facts) {
Integer i = facts.get("number");
System.out.print(i + " :是5的倍数");
}
}
- rule2
@Rule(name = "Mod7Rule", description = "mod7", priority = 2)
public class Mod7Rule {
@Condition
public boolean canMod7(@Fact("number") Integer number) {
return number % 7 == 0;
}
@Action
public void action(Facts facts) {
Integer i = facts.get("number");
System.out.print(i + " :是7的倍数");
}
}
- rule3
public class Mod5And7Rule extends UnitRuleGroup {
public Mod5And7Rule(Object... rules) {
for (Object rule : rules) {
addRule(rule);
}
}
@Override
public int getPriority() {
return 0;
}
}
- rule4
@Rule(name = "OtherRule", description = "不是5和7的倍数", priority = 3)
public class OtherRule {
@Condition
public boolean canNotMod5Or7(@Fact("number") Integer number) {
return number % 5 != 0 || number % 7 != 0;
}
@Action
public void action(@Fact("number") Integer number) {
System.out.print(number);
}
}
- 执行
// 创建规则引擎
RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);
RulesEngine fizzBuzzEngine = new DefaultRulesEngine(parameters);
// 创建规则
Rules rules = new Rules();
rules.register(new Mod5Rule());
rules.register(new Mod7Rule());
rules.register(new Mod5And7Rule(new Mod5Rule(), new Mod7Rule()));
rules.register(new OtherRule());
// 触发规则
Facts facts = new Facts();
for (int i = 1; i <= 100; i++) {
facts.put("number", i);
fizzBuzzEngine.fire(rules, facts);
}
-
这里规则引擎参数:skipOnFirstAppliedRule(true):告诉引擎,被触发时跳过后面的规则。
即当i = 5时,满足规则1(mod5),执行完action1,就不会再去匹配其他rule2、3、4规则了。使用场景是:各个规则之间互斥
场景4-动态规则MVEL表达式+Json字符串
如图1
输入facts:NORMAL_NUMBER和ERROR_NUMBER两个值,使用Mvel表达式解析facts是否满足上述json中定义的condition
- 操作符枚举类(加减乘除,> 、<)
package com.mjp.easyrules.csdn;
public enum OperationEnum {
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 OperationEnum getOperationByOperator(String operator) {
OperationEnum[] list = OperationEnum.values();
for (OperationEnum 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;
OperationEnum(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;
}
}
- 逻辑运算符(&&、||、and、or)
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 = "||";
}
- 工具类(负责解析自定义json字符串中的condition和action内容,赋值给Mvel规则when、then)
package com.mjp.easyrules.csdn;
@Slf4j
public 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);
// 规则内容
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 mvelExpressionSB = 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);
mvelExpressionSB.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 sb = new StringBuilder();
for (int i = 0; i < children.size(); i++) {
JSONObject child = children.getJSONObject(i);
// 递归构建单个规则条件
String childExpression = buildWhenExpression(child);
if (!childExpression.isEmpty()) {
if (sb.length() > 0) {
sb.append(SYMBOL_SPACE).append(operator).append(SYMBOL_SPACE);
}
sb.append(LEFT_BRACKETS).append(childExpression).append(RIGHT_BRACKETS);
}
}
mvelExpressionSB.append(sb);
break;
default:
break;
}
return mvelExpressionSB.toString();
}
/**
* 构建mvel表达式
* @param operator 操作符
* @param fieldName 字段名称
* @param value 字段值
*/
private static String buildOperatorExpress(String operator, String fieldName, Object value) {
OperationEnum operationEnum = OperationEnum.getOperationByOperator(operator);
if (ObjectUtils.isNotEmpty(operationEnum)) {
String expression = operationEnum.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);
}
/**
* 转换条件连接符
* @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结果表达式
*/
public static String buildThenExpression() {
StringBuilder sb = new StringBuilder();
sb.append(RESULT_ALIAS).append(".setValue(\"").append(MATCH_SUCCESS_MESSAGE).append("\");");
log.info("thenExpression: {}", sb);
return sb.toString();
}
@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;
}
}
}
- 测试
// 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"));
ruleModel.setWhenExpression(whenExpression);
// 4. 设置结果表达式
ruleModel.setThenExpression(EasyRulesUtil.buildThenExpression());
// 5. 设置匹配条件
JSONObject json = new JSONObject();
json.put("NORMAL_NUMBER", 12);
json.put("ERROR_NUMBER", 12);
json.put("省=陕西;市=西安;", 100);
// 6. 调用规则匹配
EasyRulesUtil.RuleResult result = EasyRulesUtil.match(json, ruleModel);
System.out.println(result);
结果分析:显然结果匹配成功。原因如图2
facts:NORMAL_NUMBER = 10、ERROR_NUMBER = 10
condition:如图1
显然NORMAL_NUMBER = 10,满足第一个条件 < 11,直接返回true。
如果我们设置fact:NORMAL_NUMBER = 12,则NORMAL_NUMBER 不满足第一个条件。
但是fact中ERROR_NUMBER = 10 <= 11满足第二个条件,直接返回True
场景5-QLExpress
1、使用阿里的QLExpress
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>QLExpress</artifactId>
<version>3.3.2</version>
</dependency>
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, MetaRuleResult> context = new DefaultContext<>();
context.put("o", MetaRuleResult.builder().skuId(1L).result(true).metaRule("o").failureReason("").build());
context.put("l", MetaRuleResult.builder().skuId(1L).result(false).metaRule("l").failureReason("锁库存不可更改").build());
context.put("s", MetaRuleResult.builder().skuId(1L).result(true).metaRule("s").failureReason("").build());
context.put("w", MetaRuleResult.builder().skuId(1L).result(true).metaRule("w").failureReason("售罄预警").build());
context.put("lo", MetaRuleResult.builder().skuId(1L).result(true).metaRule("lo").failureReason("").build());
context.put("llo", MetaRuleResult.builder().skuId(1L).result(false).metaRule("llo").failureReason("锁库且修改值小于等于OR值可以更改").build());
Object result;
DefaultContext<String, Object> computeContext = new DefaultContext<>();
for (Map.Entry<String, MetaRuleResult> entry : context.entrySet()) {
computeContext.put(entry.getKey(), entry.getValue().getResult());
}
String ruleExpress = "o&&l&&s&&w&&lo&&llo";
result = runner.execute(ruleExpress, computeContext, null, true, false);
Boolean bResult = (Boolean) result;
System.out.println(bResult);//false
- MetaRuleResult
@Data
@Builder
public class MetaRuleResult {
private Long skuId;
private Boolean result;
private String metaRule;
private String failureReason;
}
2、使用EasyRules实现上述功能
- 规则1
@Rule(name = "oRule", description = "修改值>=OR可修改")
public class ORule {
@Condition
public boolean when(@Fact("oRule") MetaRuleResult oRule) {
return oRule.getResult();
}
@Action
public void then(Facts facts) {
System.out.println("修改值>=OR可修改");
}
}
这里的规则是原子规则
- 规则2
@Rule(name = "lRule", description = "锁库不可更改")
public class LRule {
@Condition
public boolean when(@Fact("lRule") MetaRuleResult lRule) {
return lRule.getResult();
}
@Action
public void then(Facts facts) {
System.out.println("没锁库可更改");
}
}
- 自定义组合规则
public class MyUnitRuleGroup extends CompositeRule {
public MyUnitRuleGroup() {
}
public MyUnitRuleGroup(String name) {
super(name);
}
public MyUnitRuleGroup(String name, String description) {
super(name, description);
}
public MyUnitRuleGroup(String name, String description, int priority) {
super(name, description, priority);
}
public boolean evaluate(Facts facts) {
if (!this.rules.isEmpty()) {
Iterator var2 = this.rules.iterator();
Rule rule;
do {
if (!var2.hasNext()) {
return true;
}
rule = (Rule)var2.next();
} while(rule.evaluate(facts));
// 将失败的facts记录失败的原因
String ruleName = rule.getName();
MetaRuleResult metaRuleResult = facts.get(ruleName);
facts.put("执行失败" + ruleName, metaRuleResult);
return false;
} else {
return false;
}
}
public void execute(Facts facts) throws Exception {
Iterator var2 = this.rules.iterator();
while(var2.hasNext()) {
Rule rule = (Rule)var2.next();
rule.execute(facts);
}
}
}
作用:这里的规则是组合规则,是原子规则的组合形式,可扩展
这里的自定义规则组合,是快速失败机制:即l&&o中如果lRule的condiotion为false,则直接失败,使用facts记录一个失败原因。也可以自定义将每个rule-condition为false的原因都记录下来
- 自定义condition-after-listeren
public class MyRuleListener implements RuleListener {
@Override
public boolean beforeEvaluate(Rule rule, Facts facts) {
return true;
}
@Override
public void afterEvaluate(Rule rule, Facts facts, boolean b) {
String ruleName = rule.getName();
if (b) {
// 只有l&&o为true,才会走到这个逻辑,否则走下面逻辑
facts.put(ruleName, MetaRuleResult.builder().skuId(1L).result(true).metaRule(ruleName).failureReason("").build());
} else {
// l&&o有一个不满足,则总体失败,将各个失败的原因都记录下来
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, Object> fact : facts) {
String key = fact.getKey();
if (key.contains("失败")) {
MetaRuleResult result = (MetaRuleResult)fact.getValue();
// 这里result中有中间参数比如lockStatus,则原因就可以写:lockStatus=true,已锁库,不允许修改
sb.append(result.getFailureReason()+ "且");
}
}
facts.put(ruleName, MetaRuleResult.builder().skuId(1L).result(false).metaRule(ruleName).failureReason(sb.toString()).build());
}
}
@Override
public void beforeExecute(Rule rule, Facts facts) {
}
@Override
public void onSuccess(Rule rule, Facts facts) {
}
@Override
public void onFailure(Rule rule, Facts facts, Exception e) {
}
}
作用:组合规则,执行结果。成功|失败,已经失败原因
- 执行
// 1.fact
Facts facts = new Facts();
facts.put("oRule", MetaRuleResult.builder().skuId(1L).result(true).metaRule("o").failureReason("").build());
facts.put("lRule", MetaRuleResult.builder().skuId(1L).result(false).metaRule("l").failureReason("").build());
// 2.rule
ORule oRule = new ORule();
LRule lRule = new LRule();
String oAndLRuleName = "o&&l";
MyUnitRuleGroup oAndL = new MyUnitRuleGroup(oAndLRuleName, ">=OR且未锁过库规则校验");
oAndL.addRule(oRule);
oAndL.addRule(lRule);
// 3.rules
Rules rules = new Rules();
rules.register(oAndL);
// 4.引擎
DefaultRulesEngine engine = new DefaultRulesEngine();
engine.registerRuleListener(new MyRuleListener());
engine.fire(rules,facts);
MetaRuleResult result = facts.get(oAndLRuleName);
if (!result.getResult()) {
System.out.println(oAndLRuleName + result.getFailureReason());
}
- 扩展
1)db获取规则表达式:
先根据网店+品类+角色+修改类型,查询db获取组合规则,比如l&&o
2)工厂模式解析组合规则
然后根据l&&o,解析出规则为l和o,组合成l&&o
3)facts获取数据
自定义策略模式,key为枚举类型,value为对应的rpc查询接口
facts.put("o", MetaRuleResult.builder().skuId(1L).result(queryData("o")).metaRule("o").failureReason("").build());
这里的queryData方法,根据规则类型o,获取对应的Rpc接口-ORGateway,然后查询or值,然后比较结果
4)组合规则中,判断每个原子规则是否执行通过,失败则记录对应执行失败原因
5)在condition-after中自定义listeren,如果组合规则condition为false,则记录组合规则整体的执行失败以及失败原因
6)如果组合规则整体执行失败,则本次结果为false
场景6- 动态规则Mvel + Json文件
1、背景
动态规则就是由于业务场景的变化,之前的规则已经不适用现在的业务场景,需要更改相对应的规则。
例如:之前是满300减30,现在是满200-30
- 正常情况下我们需要改规则类里面的比较参数代码,然后打包上线。
- 如果使用动态规则,修改一下配置中的规则json文件即可,线上代码会读取最新的规则配置文件,无需上线
2、前提说明
1)规则类中的condtion方法,可以入参传入Facts参数,然后使用facts.get()方法获取内容 ,但是规则文件(eg:json)的condtion中无法传入Facts参数,也就无法使用此参数
2) 自定义RuleListener监听会作用到所有执行的规则,如何仅处理我们指定的规则
@Override
public void afterEvaluate(Rule rule, Facts facts, boolean evaluateResult) {
if(evaluateResult && Objects.equals(rule.getName(), "我们指定的规则名称")) {
}
}
3、场景
-
输入一个人的信息,信息中包含了这个人的学历等级,作为规则事实
- 方式一:condition中制定(推荐,可以动态配置0和11)
- @Condition修饰方法入参( @Fact(“person”) Person person )
[ { "name": "newEducationAdd", "description": "修改学历添加列表", "condition": "person.getQualifications() >= 0 && person.getQualifications()<=11", "priority": 3, "actions": [ "System.out.println(\"新规则执行了\")" ] } ]
- 方式二:将condition条件判断,自定在fact-key中对象的属性和方法中(不推荐)
[{
"name": "newEducationAdd",
"description": "修改学历添加列表",
"condition": "person.getResult(person.getQualifications())",
"priority": 3,
"actions": [
"System.out.println(\"新规则执行了\")"
]
}]
@Data
public class Person {
// 姓名
private String name;
// 年龄
private int age;
// 描述
private String dec;
// 学历等级
private int qualifications;
private List<Education> educationList;
public boolean getResult(int level){
return AddEducation.getResult(level);
}
}
@UtilityClass
public class AddEducation {
public static boolean getResult(int level){
return level >= 0 && level <= 11;
}
}
-
如果学历等级符合规则,则去查询学历证书情况(集合存储)
-
查出完学历证书后,在检测学历证书与他的学历等级是否匹配,匹配规则为:
- 学历证书数量与学历等级相同
- 最大学历证书的学历等级与学历等级一致
-
匹配通过则学历真实,信息中会添加真实学历匹配结果
-
未匹配通过则学历造假嫌疑,信息中会添加造假学历信息
上线
2、前提说明
1)规则类中的condtion方法,可以入参传入Facts参数,然后使用facts.get()方法获取内容 ,但是规则文件(eg:json)的condtion中无法传入Facts参数,也就无法使用此参数
2) 自定义RuleListener监听会作用到所有执行的规则,如何仅处理我们指定的规则
@Override
public void afterEvaluate(Rule rule, Facts facts, boolean evaluateResult) {
if(evaluateResult && Objects.equals(rule.getName(), "我们指定的规则名称")) {
}
}
3、场景
-
输入一个人的信息,信息中包含了这个人的学历等级,作为规则事实
- 方式一:condition中制定(推荐,可以动态配置0和11)
- @Condition修饰方法入参( @Fact(“person”) Person person )
[ { "name": "newEducationAdd", "description": "修改学历添加列表", "condition": "person.getQualifications() >= 0 && person.getQualifications()<=11", "priority": 3, "actions": [ "System.out.println(\"新规则执行了\")" ] } ]
- 方式二:将condition条件判断,自定在fact-key中对象的属性和方法中(不推荐)
[{
"name": "newEducationAdd",
"description": "修改学历添加列表",
"condition": "person.getResult(person.getQualifications())",
"priority": 3,
"actions": [
"System.out.println(\"新规则执行了\")"
]
}]
@Data
public class Person {
// 姓名
private String name;
// 年龄
private int age;
// 描述
private String dec;
// 学历等级
private int qualifications;
private List<Education> educationList;
public boolean getResult(int level){
return AddEducation.getResult(level);
}
}
@UtilityClass
public class AddEducation {
public static boolean getResult(int level){
return level >= 0 && level <= 11;
}
}
-
如果学历等级符合规则,则去查询学历证书情况(集合存储)
-
查出完学历证书后,在检测学历证书与他的学历等级是否匹配,匹配规则为:
- 学历证书数量与学历等级相同
- 最大学历证书的学历等级与学历等级一致
-
匹配通过则学历真实,信息中会添加真实学历匹配结果
-
未匹配通过则学历造假嫌疑,信息中会添加造假学历信息