示例項(xiàng)目:https://gitee.com/easyxaf/recharge-rules-engine-sample
前言
繼上一篇文章對規(guī)則引擎編輯器進(jìn)行了初步介紹之后,本文將通過實(shí)際應(yīng)用案例深入探討規(guī)則引擎編輯器的使用方法。編輯器的操作相對簡單,我們將重點(diǎn)放在RulesEngine的講解上。請注意,本文不是RulesEngine的入門教程,如果您對RulesEngine尚不熟悉,建議先行查閱其官方文檔, https://microsoft.github.io/RulesEngine
RulesEngine
這里要說一下在使用RulesEngine時(shí)的一些注意事項(xiàng)
RulesEngine中的Workflow類是規(guī)則信息的核心載體。它不僅包含了一個(gè)規(guī)則列表(Rules),而且每個(gè)Rule內(nèi)部同樣嵌套著一個(gè)規(guī)則列表。這樣的設(shè)計(jì)形成了一個(gè)多層次的樹狀結(jié)構(gòu)。然而,值得注意的是,在這個(gè)結(jié)構(gòu)中,只有葉節(jié)點(diǎn)的表達(dá)式會被實(shí)際執(zhí)行。也就是說,如果一個(gè)Rule內(nèi)部的Rules列表非空,那么即使該Rule定義了表達(dá)式,它也不會被執(zhí)行,它的運(yùn)行結(jié)果由子Rule來決定。
對于嵌套的Rule(即子Rule),其執(zhí)行方式可以通過NestedRuleExecutionMode進(jìn)行配置。默認(rèn)情況下,該模式設(shè)置為All,意味著所有規(guī)則都將被執(zhí)行,而不考慮Rule中設(shè)置的運(yùn)算符(Operator)。另一種模式是Performance,即性能模式,它會根據(jù)Rule中Operator的值來決定執(zhí)行邏輯:當(dāng)Operator為And或AndAlso時(shí),如果任一子Rule返回false,則停止執(zhí)行;當(dāng)Operator為Or或OrElse時(shí),如果任一子Rule返回true,則停止執(zhí)行。這種模式是全局性的,適用于所有子Rule。需要注意的是,Workflow中的Rules是頂級Rule,不是嵌套Rule,不受這個(gè)設(shè)置的限制。除非有特殊需求,否則通常建議保持默認(rèn)的All設(shè)置。后文將進(jìn)一步介紹這兩種模式的具體應(yīng)用場景。
每個(gè)Rule都包含一個(gè)Actions屬性,Actions同時(shí)又包含OnSuccess和OnFailure這兩個(gè)子屬性。需要注意的是,Workflow中的所有Rule執(zhí)行完畢后,才會根據(jù)結(jié)果執(zhí)行相應(yīng)的OnSuccess或OnFailure動作。當(dāng)Rule的結(jié)果IsSuccess為true時(shí),將執(zhí)行OnSuccess;反之,則執(zhí)行OnFailure。RulesEngine內(nèi)部默認(rèn)提供了OutputExpressionAction和EvaluateRuleAction這兩種動作。通過OutputExpressionAction,我們可以設(shè)置輸出表達(dá)式。每個(gè)Rule都保存有自己的輸出值,因此在規(guī)則執(zhí)行完畢后,我們需要自行遍歷并檢索這些輸出值,需要注意的是,輸出結(jié)果只有一個(gè)Output屬性,如果我們想?yún)^(qū)分不同的輸出值,我們需要在Contenxt中設(shè)置類型信息,在讀取值時(shí)再通過這個(gè)類型信息用于區(qū)分不同的值。
示例
在深入探討之前,我想向大家推薦一個(gè)項(xiàng)目:http://waitmoon.com/zh/guide 。這是一個(gè)基于Java語言開發(fā)的規(guī)則引擎,該項(xiàng)目的設(shè)計(jì)理念和功能實(shí)現(xiàn)在我設(shè)計(jì)規(guī)則引擎編輯器的過程中給予了我極大的啟發(fā)。接下來的示例將借鑒它文檔中的案例,以助于我們更好地理解和應(yīng)用規(guī)則引擎的概念。如果您對規(guī)則引擎感興趣,或者正在尋求靈感,這個(gè)項(xiàng)目絕對值得一看。
示例是一個(gè)充值活動,充值返現(xiàn)或送積分,我先從簡單開始,一步步的豐富它。
上面是一個(gè)最簡單的規(guī)則,"充100返現(xiàn)5元" 與 "充50送10積分" 這兩個(gè)規(guī)則在RulesEngine是頂級規(guī)則,就是它們都會被執(zhí)行,如果 "充100" 那兩個(gè)優(yōu)惠會被疊加。如果不想被疊加,我們需要給它們創(chuàng)建一個(gè)父規(guī)則,如下圖
你會看到"充值活動"的操作符是"或"(OR),同時(shí)它底下有"一個(gè)"的字樣,它還有一個(gè)選項(xiàng)是"全部",這是"嵌套規(guī)則輸出方式",它主要針對OR操作符,這是擴(kuò)展出來的功能,在上面的介紹中我們知道RulesEngine默認(rèn)會執(zhí)行所有規(guī)則,同時(shí)輸出值會存儲在每個(gè)規(guī)則結(jié)果中,這樣我們可以取一個(gè)也可以取全部,你可以把"嵌套規(guī)則輸出方式"看作是取輸出值的標(biāo)識,需要注意的是,AND操作符是沒有這個(gè)選項(xiàng)的,因?yàn)橹灰粋€(gè)子規(guī)則失敗,父級規(guī)則就是失敗的,所以也不會執(zhí)行OnSuccess動作了。如上面的示例,取全部就是疊加。如下圖
但這里有一個(gè)注意事項(xiàng),前面提到的NestedRuleExecutionMode設(shè)置,如果設(shè)置為Performance,則上面的"全部"選項(xiàng)則不起作用,它只會執(zhí)行一個(gè),所以如果想更靈活的使用RulesEngine,建議使用默認(rèn)設(shè)置,除非確認(rèn)沒有上面示例中的疊加場景。
下面我們再給這個(gè)規(guī)則加個(gè)日期限制,我們可以直接修改"充值活動"為"活動日期為10.1到10.7"
現(xiàn)在面臨一個(gè)問題,我們是否可以為"活動日期為10.1到10.7",直接設(shè)定一個(gè)表達(dá)式呢?根據(jù)我們之前對RulesEngine的了解,它僅執(zhí)行樹狀結(jié)構(gòu)中的葉節(jié)點(diǎn)表達(dá)式。這意味著,對于"活動日期為10.1到10.7"這一節(jié)點(diǎn),其內(nèi)部的表達(dá)式不會被執(zhí)行,除非它是葉節(jié)點(diǎn)。然而,如果我們有一個(gè)具有多層次節(jié)點(diǎn)的復(fù)雜規(guī)則結(jié)構(gòu),那么為每個(gè)葉節(jié)點(diǎn)添加父級規(guī)則的條件將變得異常繁瑣。這不僅增加了配置的復(fù)雜性,還可能導(dǎo)致維護(hù)上的困難。因此,我們需要尋找一種更為高效和簡潔的方法來處理這種情況,來簡化規(guī)則的設(shè)置過程。RulesEngine的默認(rèn)執(zhí)行方式我們改變不了,但我們可以在編譯規(guī)則之前對規(guī)則進(jìn)行一次預(yù)處理。下面是預(yù)處理代碼
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);
}
}
}
通過上面的擴(kuò)展方法,我們可以將父級的表達(dá)式與其合并,這樣葉節(jié)點(diǎn)就可以擁有其父級表達(dá)式了。
那如果我們再給"充50送10積分"添加一個(gè)時(shí)間限制,如"活動日期為10.5到10.7",就非常簡單了,添加"活動日期為10.5到10.7"節(jié)點(diǎn)并為其設(shè)置表達(dá)式就可以了,如下圖
我們又有新的需求了,如果老客戶在充值100元后,他會得到5積分,如下圖
大家想想上面的規(guī)則可以嗎?RulesEngine總是執(zhí)行葉節(jié)點(diǎn),這個(gè)一定要謹(jǐn)記。如果新客戶充100元,"老客戶送5積分"不會被執(zhí)行,那"充100返5元"也不會被執(zhí)行,最終是選擇下面的節(jié)點(diǎn)。
這里我們有兩個(gè)處理方案
1、在不改變"充100返5元"節(jié)點(diǎn)的情況下,直接在其下面創(chuàng)建一個(gè)子規(guī)則,子規(guī)則的表達(dá)式直接返回true,這樣"老客戶送5積分"返回false,也不影響"充100返5元"的執(zhí)行,如下圖
2、我們可以再優(yōu)化一下,將"返現(xiàn)5元"放到子規(guī)則中,需要注意,當(dāng)前操作符為"或",同時(shí)"嵌套規(guī)則輸出方式"為"全部",如下圖
關(guān)于規(guī)則創(chuàng)建的基本概念,我們的討論就先進(jìn)行到這里。請記住,無論規(guī)則邏輯多么復(fù)雜,它們都可以通過這些基本元素逐步組合起來。通過巧妙地拼接簡單的規(guī)則節(jié)點(diǎn),我們可以創(chuàng)造出功能強(qiáng)大、邏輯清晰的規(guī)則邏輯。
接下來,讓我們探討一下輸出。在前述示例中,涉及到了兩種輸出類型:"現(xiàn)金"和"積分",我們可以在Workflow節(jié)點(diǎn)下配置相應(yīng)的輸出類型,配置完后,我們可以在輸出表達(dá)式動作(OutputExpressionAction)中選擇輸出類型。如下圖
輸出表達(dá)式動作中的表達(dá)式,是 DynamicLinq的表達(dá)式語法 https://dynamic-linq.net/expression-language ,下面我們基于該表達(dá)式創(chuàng)建一個(gè)新的規(guī)則需求,如上面的示例"充100返5元",我們把它改為每充100返5元,也就是充值200直接返10元。如下圖
通過上面的表達(dá)式就可以實(shí)現(xiàn)"每充100返5元"
當(dāng)我們設(shè)置完輸出后,我們?nèi)绾卧趫?zhí)行完規(guī)則后,獲取到輸出值呢,下面是結(jié)合輸出類型獲取輸出值的代碼,它會返回一個(gè)字典,Key是輸出類型,Value是輸出值列表(每一個(gè)成功的規(guī)則結(jié)果值),后續(xù)大家可以根據(jù)自己的業(yè)務(wù)邏輯組織這一些值,上述示例,我們是對"現(xiàn)金"返回最大值,對"積分"是求和。
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("現(xiàn)金", out List<object> moneyList))
{
var money = moneyList.Select(m => double.Parse(m.ToString())).Max();
Console.Write($" {money}元現(xiàn)金");
}
if (outputResults.TryGetValue("積分", out List<object> scoreList))
{
var score = scoreList.Select(m => double.Parse(m.ToString())).Sum();
Console.Write($" {score}積分");
}
寫在最后
RulesEngine是一款輕量的規(guī)則引擎類庫,它不僅提供了一套核心的基礎(chǔ)功能,而且其設(shè)計(jì)具有卓越的擴(kuò)展性。這使得開發(fā)者得以在此基礎(chǔ)上構(gòu)建更為強(qiáng)大和定制化的功能,滿足各種復(fù)雜的業(yè)務(wù)邏輯需求。然而,手動編輯RulesEngine的規(guī)則文件無疑是一項(xiàng)耗時(shí)且繁瑣的任務(wù)。正是為了減輕這一工作負(fù)擔(dān),開發(fā)規(guī)則編輯器的想法應(yīng)運(yùn)而生。編輯器的引入旨在簡化規(guī)則的創(chuàng)建和管理過程,使得規(guī)則的維護(hù)變得更加高效和直觀,從而將開發(fā)者從重復(fù)且繁雜的手工編輯工作中解放出來。文章來源:http://www.zghlxwxcb.cn/news/detail-840154.html
https://www.cnblogs.com/haoxj/p/18073710文章來源地址http://www.zghlxwxcb.cn/news/detail-840154.html
到了這里,關(guān)于基于 XAF Blazor 的規(guī)則引擎編輯器 - 實(shí)戰(zhàn)篇的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!