示例项目:https://gitee.com/easyxaf/recharge-rules-engine-sample

前言

继上一篇文章对规则引擎编辑器进行了初步介绍之后,本文将通过实际应用案例深入探讨规则引擎编辑器的使用方法。编辑器的操作相对简单,我们将重点放在RulesEngine的讲解上。请注意,本文不是RulesEngine的入门教程,如果您对RulesEngine尚不熟悉,建议先行查阅其官方文档, https://microsoft.github.io/RulesEngine

RulesEngine

这里要说一下在使用RulesEngine时的一些注意事项

RulesEngine中的Workflow类是规则信息的核心载体。它不仅包含了一个规则列表(Rules),而且每个Rule内部同样嵌套着一个规则列表。这样的设计形成了一个多层次的树状结构。然而,值得注意的是,在这个结构中,只有叶节点的表达式会被实际执行。也就是说,如果一个Rule内部的Rules列表非空,那么即使该Rule定义了表达式,它也不会被执行,它的运行结果由子Rule来决定。

对于嵌套的Rule(即子Rule),其执行方式可以通过NestedRuleExecutionMode进行配置。默认情况下,该模式设置为All,意味着所有规则都将被执行,而不考虑Rule中设置的运算符(Operator)。另一种模式是Performance,即性能模式,它会根据Rule中Operator的值来决定执行逻辑:当Operator为And或AndAlso时,如果任一子Rule返回false,则停止执行;当Operator为Or或OrElse时,如果任一子Rule返回true,则停止执行。这种模式是全局性的,适用于所有子Rule。需要注意的是,Workflow中的Rules是顶级Rule,不是嵌套Rule,不受这个设置的限制。除非有特殊需求,否则通常建议保持默认的All设置。后文将进一步介绍这两种模式的具体应用场景。

每个Rule都包含一个Actions属性,Actions同时又包含OnSuccess和OnFailure这两个子属性。需要注意的是,Workflow中的所有Rule执行完毕后,才会根据结果执行相应的OnSuccess或OnFailure动作。当Rule的结果IsSuccess为true时,将执行OnSuccess;反之,则执行OnFailure。RulesEngine内部默认提供了OutputExpressionAction和EvaluateRuleAction这两种动作。通过OutputExpressionAction,我们可以设置输出表达式。每个Rule都保存有自己的输出值,因此在规则执行完毕后,我们需要自行遍历并检索这些输出值,需要注意的是,输出结果只有一个Output属性,如果我们想区分不同的输出值,我们需要在Contenxt中设置类型信息,在读取值时再通过这个类型信息用于区分不同的值。

示例

在深入探讨之前,我想向大家推荐一个项目:http://waitmoon.com/zh/guide 。这是一个基于Java语言开发的规则引擎,该项目的设计理念和功能实现在我设计规则引擎编辑器的过程中给予了我极大的启发。接下来的示例将借鉴它文档中的案例,以助于我们更好地理解和应用规则引擎的概念。如果您对规则引擎感兴趣,或者正在寻求灵感,这个项目绝对值得一看。

示例是一个充值活动,充值返现或送积分,我先从简单开始,一步步的丰富它。

上面是一个最简单的规则,"充100返现5元" 与 "充50送10积分" 这两个规则在RulesEngine是顶级规则,就是它们都会被执行,如果 "充100" 那两个优惠会被叠加。如果不想被叠加,我们需要给它们创建一个父规则,如下图

你会看到"充值活动"的操作符是"或"(OR),同时它底下有"一个"的字样,它还有一个选项是"全部",这是"嵌套规则输出方式",它主要针对OR操作符,这是扩展出来的功能,在上面的介绍中我们知道RulesEngine默认会执行所有规则,同时输出值会存储在每个规则结果中,这样我们可以取一个也可以取全部,你可以把"嵌套规则输出方式"看作是取输出值的标识,需要注意的是,AND操作符是没有这个选项的,因为只要一个子规则失败,父级规则就是失败的,所以也不会执行OnSuccess动作了。如上面的示例,取全部就是叠加。如下图

但这里有一个注意事项,前面提到的NestedRuleExecutionMode设置,如果设置为Performance,则上面的"全部"选项则不起作用,它只会执行一个,所以如果想更灵活的使用RulesEngine,建议使用默认设置,除非确认没有上面示例中的叠加场景。

下面我们再给这个规则加个日期限制,我们可以直接修改"充值活动"为"活动日期为10.1到10.7"

现在面临一个问题,我们是否可以为"活动日期为10.1到10.7",直接设定一个表达式呢?根据我们之前对RulesEngine的了解,它仅执行树状结构中的叶节点表达式。这意味着,对于"活动日期为10.1到10.7"这一节点,其内部的表达式不会被执行,除非它是叶节点。然而,如果我们有一个具有多层次节点的复杂规则结构,那么为每个叶节点添加父级规则的条件将变得异常繁琐。这不仅增加了配置的复杂性,还可能导致维护上的困难。因此,我们需要寻找一种更为高效和简洁的方法来处理这种情况,来简化规则的设置过程。RulesEngine的默认执行方式我们改变不了,但我们可以在编译规则之前对规则进行一次预处理。下面是预处理代码

public static void PreProcess(this Rule rule, Rule parentRule = null)
{
if (!string.IsNullOrWhiteSpace(parentRule?.Expression))
{
if (!string.IsNullOrWhiteSpace(rule.Expression))
{
rule.Expression = $"({parentRule.Expression}) && ({rule.Expression})";
}
else
{
rule.Expression = parentRule.Expression;
}
} if (rule.Rules != null)
{
foreach (var childRule in rule.Rules.ToList())
{
PreProcess(childRule, rule);
}
}
}

通过上面的扩展方法,我们可以将父级的表达式与其合并,这样叶节点就可以拥有其父级表达式了。

那如果我们再给"充50送10积分"添加一个时间限制,如"活动日期为10.5到10.7",就非常简单了,添加"活动日期为10.5到10.7"节点并为其设置表达式就可以了,如下图

我们又有新的需求了,如果老客户在充值100元后,他会得到5积分,如下图

大家想想上面的规则可以吗?RulesEngine总是执行叶节点,这个一定要谨记。如果新客户充100元,"老客户送5积分"不会被执行,那"充100返5元"也不会被执行,最终是选择下面的节点。

这里我们有两个处理方案

1、在不改变"充100返5元"节点的情况下,直接在其下面创建一个子规则,子规则的表达式直接返回true,这样"老客户送5积分"返回false,也不影响"充100返5元"的执行,如下图

2、我们可以再优化一下,将"返现5元"放到子规则中,需要注意,当前操作符为"或",同时"嵌套规则输出方式"为"全部",如下图

关于规则创建的基本概念,我们的讨论就先进行到这里。请记住,无论规则逻辑多么复杂,它们都可以通过这些基本元素逐步组合起来。通过巧妙地拼接简单的规则节点,我们可以创造出功能强大、逻辑清晰的规则逻辑。

接下来,让我们探讨一下输出。在前述示例中,涉及到了两种输出类型:"现金"和"积分",我们可以在Workflow节点下配置相应的输出类型,配置完后,我们可以在输出表达式动作(OutputExpressionAction)中选择输出类型。如下图

输出表达式动作中的表达式,是 DynamicLinq的表达式语法 https://dynamic-linq.net/expression-language ,下面我们基于该表达式创建一个新的规则需求,如上面的示例"充100返5元",我们把它改为每充100返5元,也就是充值200直接返10元。如下图

通过上面的表达式就可以实现"每充100返5元"

当我们设置完输出后,我们如何在执行完规则后,获取到输出值呢,下面是结合输出类型获取输出值的代码,它会返回一个字典,Key是输出类型,Value是输出值列表(每一个成功的规则结果值),后续大家可以根据自己的业务逻辑组织这一些值,上述示例,我们是对"现金"返回最大值,对"积分"是求和。

public static Dictionary<string, List<object>> GetOutputResults(this RuleResultTree resultTree)
{
var outputResults = new Dictionary<string, List<object>>(); if (resultTree.IsSuccess)
{
if (resultTree.ActionResult?.Output != null)
{
var context = resultTree.Rule.Actions.OnSuccess.Context;
var outputType = context.GetValueOrDefault("type", "default") as string;
if (!outputResults.ContainsKey(outputType))
{
outputResults[outputType] = [];
}
outputResults[outputType].Add(resultTree.ActionResult.Output);
}
} if (resultTree.ChildResults != null)
{
var outputMode = resultTree.Rule.Properties?.GetValueOrDefault("nestedRuleOutputMode") as string;
foreach (var childResult in resultTree.ChildResults)
{
var childOutputResults = GetOutputResults(childResult); foreach (var childOutputResult in childOutputResults)
{
if (!outputResults.ContainsKey(childOutputResult.Key))
{
outputResults[childOutputResult.Key] = [];
}
outputResults[childOutputResult.Key].AddRange(childOutputResult.Value);
} if (childOutputResults.Any() && outputMode == "one")
{
break;
}
}
} return outputResults;
}

下面是对输出值的处理

var outputResults = ruleResults.First().GetOutputResults();

Console.Write("共返");

if (outputResults.TryGetValue("现金", out List<object> moneyList))
{
var money = moneyList.Select(m => double.Parse(m.ToString())).Max();
Console.Write($" {money}元现金");
} if (outputResults.TryGetValue("积分", out List<object> scoreList))
{
var score = scoreList.Select(m => double.Parse(m.ToString())).Sum();
Console.Write($" {score}积分");
}

写在最后

RulesEngine是一款轻量的规则引擎类库,它不仅提供了一套核心的基础功能,而且其设计具有卓越的扩展性。这使得开发者得以在此基础上构建更为强大和定制化的功能,满足各种复杂的业务逻辑需求。然而,手动编辑RulesEngine的规则文件无疑是一项耗时且繁琐的任务。正是为了减轻这一工作负担,开发规则编辑器的想法应运而生。编辑器的引入旨在简化规则的创建和管理过程,使得规则的维护变得更加高效和直观,从而将开发者从重复且繁杂的手工编辑工作中解放出来。

https://www.cnblogs.com/haoxj/p/18073710

基于 XAF Blazor 的规则引擎编辑器 - 实战篇的更多相关文章

  1. 第2-4-8章 规则引擎Drools实战(1)-个人所得税计算器

    目录 9. Drools实战 9.1 个人所得税计算器 9.1.1 名词解释 9.1.2 计算规则 9.1.2.1 新税制主要有哪些变化? 9.1.2.2 资较高人员本次个税较少,可能到年底扣税增加? ...

  2. 第2-4-10章 规则引擎Drools实战(3)-保险产品准入规则

    目录 9.3 保险产品准入规则 9.3.1 决策表 9.3.2 规则介绍 9.3.3 实现步骤 9.3 保险产品准入规则 全套代码及资料全部完整提供,点此处下载 9.3.1 决策表 前面我们编写的规则 ...

  3. 第2-4-9章 规则引擎Drools实战(2)-信用卡申请

    目录 9.2 信用卡申请 9.2.1 计算规则 9.2.2 实现步骤 9.2 信用卡申请 全套代码及资料全部完整提供,点此处下载 本小节我们需要通过Drools规则引擎来根据规则进行申请人的合法性检查 ...

  4. 基于ABP做一个简单的系统——实战篇:4.基于富文本编辑器,Razor模板引擎生成内容并导出Word 填坑记录

    起因 需求是这样的,有一种协议需要生成,协议的模板是可配置的,在生成过程中,模板中的内容可以根据约定的标记进行替换(就像mvc的razor模板一样).生成后的内容还需要导出成word或pdf. 常见的 ...

  5. 基于ABP做一个简单的系统——实战篇:2.代码生成器

    上一篇正说着呢,代码生成器就来了. 1.适用于ABP官网的Startup Template V3.x的包含了登录.用户等页面的MPA应用模板2.当前view仅支持文本框生成,远期规划根据字段类型生成不 ...

  6. HGE游戏引擎之实战篇,渐变的游戏开场

    #include <hge.h> #include "menuitem.h" //#include <hgefont.h> #include <hge ...

  7. 基于ABP做一个简单的系统——实战篇:1.项目准备

    现阶段需要做一个小项目,体量很小,业务功能比较简单,就想到用最熟悉的.net来做,更何况现在.net core已经跨平台,也可以在linux服务器上部署.所以决定用.net core 3.1+mysq ...

  8. 《android基于andFix的热修复方案》实战篇

    有篇文章说的比较简洁,大家可以参考下:AndFix使用说明 下面说说实际使用中遇到的问题 1:如何继承到gradle项目中 dependencies { compile 'com.alipay.eul ...

  9. Drools 规则引擎应用

    规则引擎-drools 1 .场景 1.1需求 商城系统消费赠送积分 100元以下, 不加分 100元-500元 加100分 500元-1000元 加500分 1000元 以上 加1000分 .... ...

  10. Drools 规则引擎应用 看这一篇就够了

    1 .场景 1.1需求 商城系统消费赠送积分 100元以下, 不加分 100元-500元 加100分 500元-1000元 加500分 1000元 以上 加1000分 ...... 1.2传统做法 1 ...

随机推荐

  1. 教你用JavaScript获取大转盘

    案例介绍 欢迎来到我的小院,我是霍大侠,恭喜你今天又要进步一点点了!我们来用JavaScript编程实战案例,做一个大转盘.当你难以抉择的时候不妨用这个案例来帮你做选择.通过编程实战我们可以学到按钮的 ...

  2. (C语言)关于printf的新发现: 可以用字符串变量替代第一个字符串参数

    char a[] = "hello%d\n%dworld"; printf(a, 2, 3); //输出: //hello2 //2world 事情的起因是使用printf(&qu ...

  3. 索引构建磁盘IO太高,巧用tmpfs让内存来帮忙

    在文本索引构建这种需要大量占用磁盘IO的任务,如果正巧你的内存还有点余粮,是否可以先索引存储到内存,然后再顺序写入到磁盘呢?,需要大量占用磁盘IO,如果正巧你的内存还有点余粮,是否可以先索引存储到内存 ...

  4. Flink CDC写入数据到kafka几种格式

    Flink CDC写入kafka几种常见的数据格式,其中包括upsert-kafka写入后正常的json格式,debezium-json格式以及changelog-json格式. upsert-kaf ...

  5. Linux 在线安装MySQL8.0

    1.更新Linux yum yum update 2.安装wget工具(如果已经安装wget,可以跳过该步骤) yum install wget 3.使用wget下载MySQL Yum Reposit ...

  6. NC19158 失衡天平

    题目链接 题目 题目描述 终于Alice走出了大魔王的陷阱,可是现在傻傻的她忘了带武器了,这可如何是好???这个时候,一个神秘老人走到她面前答应无偿给她武器,但老人有个条件,需要将所选武器分别放在天平 ...

  7. logging --- Python 的日志记录工具

    logging --- Python 的日志记录工具 源代码: Lib/logging/__init__.py Important 此页面仅包含 API 参考信息.教程信息和更多高级用法的讨论,请参阅 ...

  8. 当我忘记SQL怎么写的时候,我……

    当我忘记SQL怎么写的时候,我-- 以往的我,打开电脑百度一番,打开笔记查询一轮,发现没找到我最想要的 不要慌,问题不大 后来发现MySQL本身就带有帮助文档. 于是我一顿操作:win+R >& ...

  9. CentOS8-pacemaker+corosync高可用部署

    部署pacemaker yum install pacemaker pcs corosync fence-agents resource-agents 启动pcs服务 systemctl enable ...

  10. 新零售SaaS架构:什么是订单履约系统?

    什么是订单履约系统? 订单履约系统用来管理从接到销售订单,到把货品送到客户手中的整个业务过程.它是上游交易(如销售和客户下单环节)和下游仓储配送(如库存管理.物流)之间的桥梁,确保信息流的顺畅和操作的 ...