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

news2025/7/20 17:15:47

写作目的

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

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

比如你想加盟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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/37721.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

招生CRM系统|基于Springboot实现培训机构招生CRM管理系统

作者主页:编程指南针 作者简介:Java领域优质创作者、CSDN博客专家 、掘金特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容:Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助 收藏点赞不迷路 关注作者有好处 文末获取源…

Mongodb操作基础 分片

Mongodb分片 MongoDB分片是MongoDB支持的另一种集群形式,它可以满足MongoDB数据量呈爆发式增长的需求。当MongoDB存储海量的数据时,一台机器可能无法满足数据存储的需求,也可能无法提供可接受的读写吞吐量,这时,我们就…

基于内容的个性化推荐算法

一、什么是推荐算法 随着移动互联网的高速发展与智能手机的普及,海量的有用信息虽然为人们提供了更多的价值,然而信息的泛滥也意味着为了寻找合适的信息必须付出更多的时间成本。事实上,有时候仅仅是浏览和简单的查询来寻找有用的信息变得相…

「强烈收藏」Python第三方库资源大全,1000+工具包

前言 awesome-python 是 vinta 发起维护的 Python 资源列表,内容包括:Web 框架、网络爬虫、网络内容提取、模板引擎、数据库、数据可视化、图片处理、文本处理、自然语言处理、机器学习、日志、代码分析等。 (文末送读者福利) …

超全!程序员必备的20个学习网站,看这一篇就够了!

之前一直想出个程序员学习清单,终于腾出时间弄出来了,也趁此机会整理了收藏夹。 此篇对于新手程序员比较有用,技术老鸟们也可以查缺补漏。 话不多说,纯纯干货呈上,赶紧点个赞收藏,以后会用得上!…

CMake中include_directories的使用

CMake中include_directories命令用于在构建(build)中添加包含目录,其格式如下: include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...]) 将给定的目录添加到编译器(compiler)用于搜索包含文件的目录。相对路径被解释为相对于当前源目录。 包含目录被添加到当前C…

【网络】tcpdump、Wireshark 案例超详细介绍

文章目录网络分层应用层找到服务器的 IP查接口、对象的耗时删除指定网站的Cookie表示层、会话层tcpdump、wireshard传输层telnet: 路径可达性测试nc: 路径可达性测试netstat:查看当前连接状态iftop:查看当前连接的传输速率netstat -s: 查看丢包和乱序的统…

万字 HashMap 详解,基础(优雅)永不过时

本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问。 前言 大家好,我是小彭。 在上一篇文章里,我们聊到了散列表的整体设计思想,在后续几篇文章里,我们将以 Java 语言为例&#xff…

【王道计算机组成原理Note】5.5 指令流水线

5 指令流水线 5.1 指令流水的定义 一条指令的执行过程可以分成多个阶段(或过程)。根据计算机的不同,具体的分法也不同。 取指:根据Pc内容访问主存储器,取出一条指令送到IR中。分析:对指令操作码进行译码,按照给定的寻…

Visio 安装暴雷记录

Visio 安装记录起因: office2016家庭学生版中,安装visio2021后,插入word的vsdx图形右键显示unkown类型,无法识别,给学习工作带来很多麻烦!   搜查一圈没找到对应可用的方法,想着可能是visio20…

MobPush for Flutter

集成准备 这是一个基于 MobPush 功能的扩展的 Flutter 插件。使用此插件能够帮助您在使用 Flutter 开发应用时,快速地实现推送功能。 在pubspec.yaml文件中加入下面依赖 dependencies:mobcommonlib:mobpush_plugin: 然后执行:flutter packages get 导…

倍增(小试牛刀)

二分每次折半,倍增每次2的倍数 原理先存储每个小区间的最值也就是初始化,之后直接查询 1. 把数列按倍增分成小区间 对数列的每个元素,把从它开始的数列分成长度为1、2、4、8、…的小区间。下图给出了一个分区的例子,它按小区间…

java--JVM

JVM1.JVM的内存结构2.哪些部分会出现内存溢出3.方法区与永久代、元空间之间的关系4.JVM内存参数5.JVM垃圾回收算法(1)标记清除(2)标记整理(3)标记复制6.GC和分代回收算法7.三色标记和并发漏标问题8.垃圾回收…

【分布式技术专题】「Zookeeper中间件」给大家学习一下Zookeeper的”开发伴侣”—Curator-Framework(基础篇)

CuratorFramework基本介绍 CuratorFramework是Netflix公司开源的一套Zookeeper客户端框架,它作为一款优秀的ZooKeeper客户端开源工具,主要提供了对客户端到服务的连接管理和连接重试机制,以及一些扩展功能,它解决了很多ZooKeeper…

Linux服务器配置与管理(基于Centos7.2)任务目标(四)

文章目录一、任务目标二、任务资讯三、任务实施3-1.RPM软件包管理3-2.YUM方式安装软件一、任务目标 实施该工单的任务目标如下: 知识目标 1.了解RPM提供的功能。 2.了解YUM相对于RPM所具有的优点。 能力目标 1.能够通过RPM安装及管理软件包。 2.能够通过YUM安装及管…

uni-app —— 小程序加入购物车实现过程

文章目录 前言一、示意图二、整体实现思路三、实现过程 1.加入购物车2.获取当前用户购物车信息3.解决数据获取不及时的问题总结前言 前文已经讲解了如何实现商品规格的选择,那么接下来就应该将用户选中的商品加入购物车啦!那么如何实现呢?请…

[附源码]计算机毕业设计JAVA儒家文化网站

[附源码]计算机毕业设计JAVA儒家文化网站 项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis M…

FilterListenerAjax的介绍

目录 一、Filter 1、Filter概述 2、过滤器链 二、Listener 三、Ajax 1、基本介绍 2、快速入门案例 3、axios 4、JSON 一、Filter 1、Filter概述 ▶ 过滤器 Filter 表示过滤器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。过滤器可以把对资源的请…

Centos--基于Jdk1.8环境安装+卸载Jenkins

基础准备 本人选择的安装的环境基于jdk1.8 操作系统:Centos7.9 java: 1.8.0_262 检查是否有旧版本 $rpm -ql jenkins 如果有老版本可以卸载后,再执行后面的安装步骤 卸载 jenkins $rpm -e jenkins —删除遗留文件: $find / -iname jenkins | xa…

【Lilishop商城】No2-4.确定软件架构搭建三(本篇包括ES检索)

仅涉及后端,全部目录看顶部专栏,代码、文档、接口路径在: 【Lilishop商城】记录一下B2B2C商城系统学习笔记~_清晨敲代码的博客-CSDN博客 全篇只介绍重点架构逻辑,具体编写看源代码就行,读起来也不复杂~ 谨慎&#xff…