基於 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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...