接下来以 餐厅、套餐、菜品进行举例。
比如你想加盟XX火锅店,你需要像区域经理申请开店。
经理说 你开一个 牛肉火锅(prouductId = 1) 自营店(type =1)。
经理让李四 开一个 羊肉火锅(prouductId = 2) 自营店(type =1)。
经理让王五 开一个 羊肉火锅(prouductId = 2) 旗舰店(type =2)。
。。。。。
那么针对不同的场景(product && type),需要走的审批流程不一样。
●牛肉火锅(prouductId = 1) 自营店(type =1)
开店规则:需要审批菜品,审批通过后套餐自动审批通过,套餐都审批通过后 餐厅自动审批通过,审批通过后即可运营。
●羊肉火锅(prouductId = 2) 自营店(type =1)
开店规则:只审批餐厅,审批通过后即可运营。
●羊肉火锅(prouductId = 2) 旗舰店(type =2)
开店规则:
只审批餐厅,审批通过后即可运营。
但是菜品也可以申请,审批通过后套餐自动审批通过,审批通过的套餐可以每天赠送100份。
那么问题来了,如果你作为审批流程客服工作人员,当一个开店的审批工单来了以后,总有人问你为什么他的工单还在审批中,你怎么办呢?最好的方式就是你告诉他你的工单是菜品、套餐、餐厅没审批通过,请找相关同学咨询。
源码下载Gitee (亲测可用,真实有效)
启动方法和工程目录如下
java规则引擎easy-rules简单使用
以上面的牛肉火锅(prouductId = 1) 自营店(type =1) 为例
正常情况下可以写代码判断
int productId = 1; int type = 1; if(productId == 1 && type ==1){ System.out.println("牛肉火锅自营店。请从【餐品】开始进行向上申请"); }
如果这样的规则能通过配置的方式进行实现,那简直无敌。
下面先写一个demo版本
Canteen canteen = new Canteen().setProductId(1).setType(1); // define rules 定义规则 Rule canteenRule = new RuleBuilder() .name("牛肉火锅自营店") // 规则名称 .description("productId = 1 && type =1 。文案:牛肉火锅自营店。请从【餐品】开始进行向上申请") // 规则描述 .when(facts -> facts.get("productId").equals(1) && facts.get("type").equals(1)) // 规则条件 .then(facts -> System.out.println("牛肉火锅自营店。请从【餐品】开始进行向上申请")) // 命中规则后的操作 .build(); // 定义规则集合 Rules rules = new Rules(); rules.register(canteenRule); // fire rules on known facts 创建执行引擎 RulesEngine rulesEngine = new DefaultRulesEngine(); // define facts 定义需要验证的参数 Facts facts = new Facts(); facts.put("productId", canteen.getProductId()); facts.put("type", canteen.getType()); // 进行规则校验 rulesEngine.fire(rules, facts);
看打印结果
上面还存在以下问题
- ●规则还是手动通过代码定义的,如果通过配置文件定义那就最好了
- ●命中的规则后结果只能打印,我想获取规则的一些信息比如规则描述description应该怎么办
最佳落地实践
注意:部分代码没有展示,可以去仓库查看全部源码
通过配置文件定义规则 canteenRule.yml
--- name: "牛肉火锅自营店" description: "prouductId = 1 && type = 1 " condition: "canteen.productId==1&&canteen.type==1" priority: 1 actions: - "System.out.println(1);" --- name: "牛肉火锅旗舰店" description: "prouductId = 1 && type = 2" condition: "canteen.productId == 2 && canteen.type == 1" priority: 2 actions: - "System.out.println(2);"
创建规则引擎工厂类RulesEngineFactory
目的:上述例子中,规则引擎不可能只为 餐厅 服务,还需要为 套餐、菜品服务。因此肯定是有不同的规则和规则引擎的。因此这里需要一个工厂。
package com.example.demo.rulesEngine.listener; import com.example.demo.rulesEngine.common.RuleCommonInterface; import lombok.Data; import org.jeasy.rules.api.Facts; import org.jeasy.rules.api.Rules; import org.jeasy.rules.core.DefaultRulesEngine; import org.jeasy.rules.mvel.MVELRuleFactory; import org.jeasy.rules.support.YamlRuleDefinitionReader; import java.io.FileReader; /** * @author chaird * @create 2022-11-26 13:02 */ public class RulesEngineFactory { /** * 构建食堂规则。特殊 * * @return */ public static BizRuleEngine buildRuleEngine4Canteen() { String entityType = "canteen"; String reulePath = "D:\\work\\IntelliJ IDEA 2018.2.4Workspace\\Demooo\\springboot-easu-rules-demo\\src\\main\\resources\\canteenRule.yml"; return buildRuleEngine(entityType, reulePath); } // 可以有N个 public static BizRuleEngine buildRuleEngine4MealGroup() { String entityType = "mealGroup"; String reulePath = "xxxxx"; // return buildRuleEngine(entityType, reulePath); return null; } private static BizRuleEngine buildRuleEngine(String entityType, String rulePath) { BizRuleEngine bizRuleEngine = new BizRuleEngine(entityType, rulePath); return bizRuleEngine; } @Data public static class BizRuleEngine { private String entityType; private MVELRuleFactory ruleFactory; private DefaultRulesEngine rulesEngine; private Rules rules; public BizRuleEngine(String entityType, String rulePath) { try { this.entityType = entityType; ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader()); rules = ruleFactory.createRules(new FileReader(rulePath)); rulesEngine = new DefaultRulesEngine(); rulesEngine.registerRuleListener(new YmlRulesListener(entityType)); } catch (Exception e) { e.printStackTrace(); } } public void fire(RuleCommonInterface input) { Facts facts = new Facts(); facts.put(entityType, input); rulesEngine.fire(rules, facts); } } }
这样我就可以针对餐厅这样一个特殊的实例创建自己独有的规则引擎
RulesEngineFactory.BizRuleEngine canteenRuleEngine = RulesEngineFactory.buildRuleEngine4Canteen(); Canteen canteen = new Canteen().setName("西餐厅").setProductId(1).setType(1); //todo
创建监听器YmlRulesListener
目的:其实有有的时候命中规则后我们要做一些事情,比如取到规则的一些描述等信息好组织文案
package com.example.demo.rulesEngine.listener; import com.example.demo.rulesEngine.common.RuleCommonInterface; import org.jeasy.rules.api.Facts; import org.jeasy.rules.api.Rule; import org.jeasy.rules.api.RuleListener; /** * @author chaird * @create 2022-11-26 1:54 */ public class YmlRulesListener implements RuleListener { private String entityType ; @Override public boolean beforeEvaluate(Rule rule, Facts facts) { return true; } @Override public void afterEvaluate(Rule rule, Facts facts, boolean evaluationResult) { } @Override public void beforeExecute(Rule rule, Facts facts) { } @Override public void onSuccess(Rule rule, Facts facts) { //获取需要验证的对象,比如 【餐厅、套餐、菜品 implement RuleCommonInterface】 RuleCommonInterface ruleCommon = facts.get(entityType); //把规则信息进行一个赋值 ruleCommon.setDescription(rule.getDescription()); } @Override public void onFailure(Rule rule, Facts facts, Exception exception) { } public YmlRulesListener(){ } public YmlRulesListener(String entityType) { this.entityType = entityType; } }
可以直接通过规则action进行赋值
有的时候会有转换操作,针对本文提出的案例。我想让productId =2的时候和productId = 9527的后续流程一样,可以在actions中使用下面的命令
name: "牛肉火锅旗舰店" description: "prouductId = 1 && type = 2" condition: "canteen.productId == 2 && canteen.type == 1" priority: 2 actions: - "canteen.productId = 9527;"
总结
●这样的一个工具案例其实写文章还挺难组织思路的,代码贴的多显示不出核心思路。代码贴的少大家又看不太懂。
●百度了一些文章,其实有些都没有跑通,所以自己写一篇文章。
●其实单场景下对一个实体类进行规则校验那很简单,本文通过工厂模式设计的是对多实体类进行规则校验。总体还是有难度的。