基於 XAF Blazor 的規則引擎編輯器 - 實戰篇

来源:https://www.cnblogs.com/haoxj/p/18073710
-Advertisement-
Play Games

示例項目:https://gitee.com/easyxaf/recharge-rules-engine-sample 前言 繼上一篇文章對規則引擎編輯器進行了初步介紹之後,本文將通過實際應用案例深入探討規則引擎編輯器的使用方法。編輯器的操作相對簡單,我們將重點放在RulesEngine的講解上。請 ...


示例項目: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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 故事 春天,辦公室外的世界總是讓人神往的,小貓帶著耳機,托著腮幫,望著外面美好的春光神游著... 一聲不和諧的座機電話聲打破這份本該屬於小貓的寧靜,“hi,小貓,線上有個客戶想購買A產品規格的商品,投訴說下單總是失敗,幫忙看一下啥原因。”客服部小姐姐甜美的聲音從電話那頭傳來。“哦哦,好,我看一下,把 ...
  • roncoo-education —— 一個分散式線上教育系統。目前主要功能有課程點播功能,支持多家視頻雲的接入,課程附件管理功能,支持多家存儲雲的接入,可以幫助個人或者企業快速搭建一個輕量級的線上教育平臺。 ...
  • 在Java多線程編程中,正確且安全地停止線程是一項關鍵技能。簡單粗暴地“殺死”線程不僅可能導致數據不一致性,還可能引發各種難以預測的錯誤。 ...
  • 這個作業屬於哪個課程 軟體工程2024 這個作業要求在哪裡 個人項目 這個作業的目標 瞭解軟體項目開發的整體流程,實現自己的個人項目,學習單元測試、性能優化和 git 操作,學會使用 PSP 表格 Github地址 : 點擊此處,進入我的倉庫 一、項目需求 題目:論文查重 設計一個論文查重演算法,給出 ...
  • 大家好,我是R哥。 周末愉快呀,最近我在做 Java 面試輔導,也模擬面試了好些個學員,說說其中一個學員吧,一個工作 5 年的 Java 程式員,模擬面試,居然一個問題也不會。。 當晚模擬面試完,我的心情很複雜。 我之前做系統架構師,同時也是面試官,這些年,少說也面試過幾百上千人,不乏知識淵博、技能 ...
  • .NET 6 引入了 LoggerMessageAttribute 類型。 使用時,它會以source-generators的方式生成高性能的日誌記錄 API。 source-generators可在編譯代碼時,可以提供其他源代碼作為編譯的輸入。 LoggerMessageAttribute依賴於 ...
  • 概述:ValueStopwatch是.NET中輕量級計時器,用於高性能時間測量。作為值類型,避免了裝箱拆箱開銷,記憶體占用小。通過簡單的使用方法,輕鬆實現代碼塊執行時間測量,且相比Stopwatch更為高效。 在.NET中,ValueStopwatch是一個輕量級的計時器類,用於測量代碼塊的執行時間。 ...
  • 概述:`Directory.Packages.props`和`Directory.Build.props`是.NET項目中的配置文件,分別用於統一管理NuGet包引用和自定義MSBuild構建過程。它們提高瞭解決方案的可維護性,通過集中配置,簡化了項目文件,使團隊協作更一致,同時避免了在每個項目中重 ...
一周排行
    -Advertisement-
    Play Games
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...