当前位置: 首页 > news >正文

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("牛肉火锅自营店。请从【餐品】开始进行向上申请");
      }

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

下面先写一个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;"

总结

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

参考

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

相关文章:

  • 看我如何连夜自建网站背刺我的求职对手们
  • 027.将有序数组转换为二叉搜索树
  • 第五届传智杯-初赛【B组-题解】
  • 最全面的SpringMVC教程(一)——SpringMVC简介
  • OpenCV-Python小应用(六):车道线检测
  • 微信小程序介绍
  • matlab实现MCMC的马尔可夫转换MS- ARMA - GARCH模型估计
  • 华为云桌面Workspace,让你的办公更加舒适惬意
  • 基于优先级的时间片轮转调度算法(C语言实现)
  • 43特征01——特征值特征向量: 特征多项式、特殊矩阵 的特征值与特征向量、Hamilton-Cayley 定理
  • [毕业设计]机器学习的运动目标跟踪-opencv
  • Ubuntu 软件管理学习笔记
  • python中pytest库用法详解
  • CSRF漏洞简介
  • C/C++航空客运订票系统
  • 「译」Node.js Streams 基础
  • 2018天猫双11|这就是阿里云!不止有新技术,更有温暖的社会力量
  • docker-consul
  • ES6系统学习----从Apollo Client看解构赋值
  • input实现文字超出省略号功能
  • JavaScript创建对象的四种方式
  • java中具有继承关系的类及其对象初始化顺序
  • JS学习笔记——闭包
  • mysql_config not found
  • Objective-C 中关联引用的概念
  • PHP CLI应用的调试原理
  • ReactNative开发常用的三方模块
  • Shell编程
  • Web设计流程优化:网页效果图设计新思路
  • 闭包--闭包作用之保存(一)
  • 诡异!React stopPropagation失灵
  • 目录与文件属性:编写ls
  • 如何正确配置 Ubuntu 14.04 服务器?
  • 文本多行溢出显示...之最后一行不到行尾的解决
  • linux 淘宝开源监控工具tsar
  • Spark2.4.0源码分析之WorldCount 默认shuffling并行度为200(九) ...
  • !!java web学习笔记(一到五)
  • # 数据结构
  • ${ }的特别功能
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (2015)JS ES6 必知的十个 特性
  • (zt)基于Facebook和Flash平台的应用架构解析
  • (非本人原创)我们工作到底是为了什么?​——HP大中华区总裁孙振耀退休感言(r4笔记第60天)...
  • (附源码)springboot掌上博客系统 毕业设计063131
  • (正则)提取页面里的img标签
  • (转)IOS中获取各种文件的目录路径的方法
  • .net 设置默认首页
  • .Net 中的反射(动态创建类型实例) - Part.4(转自http://www.tracefact.net/CLR-and-Framework/Reflection-Part4.aspx)...
  • .NET/C# 的字符串暂存池
  • .net开发时的诡异问题,button的onclick事件无效
  • [20150904]exp slow.txt
  • [Angularjs]asp.net mvc+angularjs+web api单页应用之CRUD操作
  • [Bada开发]初步入口函数介绍
  • [BZOJ]4817: [Sdoi2017]树点涂色
  • [Erlang 0129] Erlang 杂记 VI 2014年10月28日
  • 005-python基础-用户输入与模块初认识
  • 查询oracle连接数 对应的 应用程序
  • Linux系统介绍
  • Ditto - Windows剪贴板增强小工具,方便复制粘贴多条记录
  • ubuntu系统小知识
  • 指针
  • PLC控制伺服电机
  • 指针数组与数组指针 二级指针
  • 结构体 大小端 枚举
  • IntelliJ IDEA 14和Maven创建java web项目