• easy-rules规则引擎最佳落地实践


    写作目的

    这是一个头部互联网公司中的一个问题。因为有很多业务产品线,作为一个新人或者团队外的人员是很难区分不同的产品线之间的区别的,因此需要给某个产品线一个描述。但是随着业务的发展,产品线下可能又根据某个字段进一步划分,那么子产品线就是父产品线 + 字段 去区分。后面根据两个字段划分…。人都麻了。因为不同的组合有不同的链路。因此针对一个产品,我们要提供针对这个产品的具体规则描述,从而减少答疑。

    接下来以 餐厅、套餐、菜品进行举例。
    在这里插入图片描述

    比如你想加盟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 (亲测可用,真实有效)

    gitee代码下载地址
    启动方法和工程目录如下
    在这里插入图片描述

    java规则引擎easy-rules简单使用

    以上面的牛肉火锅(prouductId = 1) 自营店(type =1) 为例

    正常情况下可以写代码判断

          int productId = 1;
          int type = 1;
          if(productId == 1 && type ==1){
            System.out.println("牛肉火锅自营店。请从【餐品】开始进行向上申请");
          }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果这样的规则能通过配置的方式进行实现,那简直无敌。

    下面先写一个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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    看打印结果
    在这里插入图片描述

    上面还存在以下问题

    • 规则还是手动通过代码定义的,如果通过配置文件定义那就最好了
    • 命中的规则后结果只能打印,我想获取规则的一些信息比如规则描述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);"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    创建规则引擎工厂类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);
        }
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    这样我就可以针对餐厅这样一个特殊的实例创建自己独有的规则引擎

    RulesEngineFactory.BizRuleEngine canteenRuleEngine = RulesEngineFactory.buildRuleEngine4Canteen();
    
    Canteen canteen = new Canteen().setName("西餐厅").setProductId(1).setType(1);
    //todo
    
    • 1
    • 2
    • 3
    • 4

    创建监听器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;
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    可以直接通过规则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;"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    总结

    • 这样的一个工具案例其实写文章还挺难组织思路的,代码贴的多显示不出核心思路。代码贴的少大家又看不太懂。
    • 百度了一些文章,其实有些都没有跑通,所以自己写一篇文章。
    • 其实单场景下对一个实体类进行规则校验那很简单,本文通过工厂模式设计的是对多实体类进行规则校验。总体还是有难度的。

    参考

    https://www.cnblogs.com/rongfengliang/p/12686702.html
    https://www.jianshu.com/p/3bc5773a1545

  • 相关阅读:
    Excel下拉填充时,如何使得数字不递增?
    归并排序 图解 递归 + 非递归 + 笔记
    实心轮胎的优缺点
    golang map clear
    ActiveMQ面试题(二)
    力扣周赛 -- 370周赛
    深度学习可以求解特定函数的参数么?
    Qt QLable 字符过长省略
    python列表list的index方法的用法和实例
    for与for range
  • 原文地址:https://blog.csdn.net/qq_37171353/article/details/128050384