基于 XAF Blazor 的规则引擎编辑器 - 实战篇
示例项目: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 的规则引擎编辑器 - 实战篇的更多相关文章
- 第2-4-8章 规则引擎Drools实战(1)-个人所得税计算器
目录 9. Drools实战 9.1 个人所得税计算器 9.1.1 名词解释 9.1.2 计算规则 9.1.2.1 新税制主要有哪些变化? 9.1.2.2 资较高人员本次个税较少,可能到年底扣税增加? ...
- 第2-4-10章 规则引擎Drools实战(3)-保险产品准入规则
目录 9.3 保险产品准入规则 9.3.1 决策表 9.3.2 规则介绍 9.3.3 实现步骤 9.3 保险产品准入规则 全套代码及资料全部完整提供,点此处下载 9.3.1 决策表 前面我们编写的规则 ...
- 第2-4-9章 规则引擎Drools实战(2)-信用卡申请
目录 9.2 信用卡申请 9.2.1 计算规则 9.2.2 实现步骤 9.2 信用卡申请 全套代码及资料全部完整提供,点此处下载 本小节我们需要通过Drools规则引擎来根据规则进行申请人的合法性检查 ...
- 基于ABP做一个简单的系统——实战篇:4.基于富文本编辑器,Razor模板引擎生成内容并导出Word 填坑记录
起因 需求是这样的,有一种协议需要生成,协议的模板是可配置的,在生成过程中,模板中的内容可以根据约定的标记进行替换(就像mvc的razor模板一样).生成后的内容还需要导出成word或pdf. 常见的 ...
- 基于ABP做一个简单的系统——实战篇:2.代码生成器
上一篇正说着呢,代码生成器就来了. 1.适用于ABP官网的Startup Template V3.x的包含了登录.用户等页面的MPA应用模板2.当前view仅支持文本框生成,远期规划根据字段类型生成不 ...
- HGE游戏引擎之实战篇,渐变的游戏开场
#include <hge.h> #include "menuitem.h" //#include <hgefont.h> #include <hge ...
- 基于ABP做一个简单的系统——实战篇:1.项目准备
现阶段需要做一个小项目,体量很小,业务功能比较简单,就想到用最熟悉的.net来做,更何况现在.net core已经跨平台,也可以在linux服务器上部署.所以决定用.net core 3.1+mysq ...
- 《android基于andFix的热修复方案》实战篇
有篇文章说的比较简洁,大家可以参考下:AndFix使用说明 下面说说实际使用中遇到的问题 1:如何继承到gradle项目中 dependencies { compile 'com.alipay.eul ...
- Drools 规则引擎应用
规则引擎-drools 1 .场景 1.1需求 商城系统消费赠送积分 100元以下, 不加分 100元-500元 加100分 500元-1000元 加500分 1000元 以上 加1000分 .... ...
- Drools 规则引擎应用 看这一篇就够了
1 .场景 1.1需求 商城系统消费赠送积分 100元以下, 不加分 100元-500元 加100分 500元-1000元 加500分 1000元 以上 加1000分 ...... 1.2传统做法 1 ...
随机推荐
- 【算法】priority_queue在力扣题中的应用 | 力扣692 | 力扣347 | 力扣295 【超详细的注释和算法解释】
说在前面的话 博主也好长一段时间没有更新力扣的刷题系列了,今天给大家带来一些优先队列的经典题目,今天博主还是用C++给大家讲解,希望大家可以从中学到一些东西. 前言 那么这里博主先安利一下一些干货满满 ...
- CH59X/CH58X/CH57X 片上flash的使用
以CH592F为例:在使用时先看手册对code和data区的划分 一.DataFlash的读写的操作 先看几个操作dataflash的API(读擦写): /** * @brief read Data- ...
- Java并发(五)----线程常见方法总结
常见方法 方法名 static 功能说明 注意 start() 启动一个新线程,在新的线程运行 run 方法中的代码 start 方法只是让线程进入就绪,里面代码不一定立刻运行(CPU 的时间片还 ...
- 【Flink入门修炼】1-1 为什么要学习 Flink?
流处理和批处理是什么? 什么是 Flink?为什么要学习 Flink? Flink 有什么特点,能做什么? 本文将为你解答以上问题. 一.批处理和流处理 早些年,大数据处理还主要为批处理,一般按天或小 ...
- [数据库] 数据库中的DDL、DML、DQL、DCL
SQL 程序语言有四种类型,对数据库的基本操作都属于这四种类,也就是标题上显示的 DDL.DML.DQL.DCL. 1. DDL DDL(Data Definition Language 数据定义语言 ...
- NC51032 八数码
题目链接 题目 题目描述 The 15-puzzle has been around for over 100 years; even if you don't know it by that nam ...
- logging --- Python 的日志记录工具
logging --- Python 的日志记录工具 源代码: Lib/logging/__init__.py Important 此页面仅包含 API 参考信息.教程信息和更多高级用法的讨论,请参阅 ...
- Spring Boot图书管理系统项目实战-11.检索图书
导航: pre:10.借还统计 next: 只挑重点的讲,具体的请看项目源码. 1.项目源码 需要源码的朋友,请捐赠任意金额后留下邮箱发送:) 2.页面设计 2.1 index.html <!D ...
- java zTree异步加载实战
zTree简介 zTree 是一个依靠 jQuery 实现的多功能 "树插件".优异的性能.灵活的配置.多种功能的组合是 zTree 最大优点. zTree 是开源免费的软件(MI ...
- junit使用stub进行单元测试
stub是代码的一部分,我们要对某一方法做单元测试时,可能涉及到调用第三方web服务.假如当前该服务不存在或不可用咋办?好办,写一段stub代码替代它. stub 技术就是把某一部分代码与环境隔离起来 ...