• 『低代码平台』规则引擎、表达式解析


    背景

    低代码平台中的一个核心内容是规则引擎,规则引擎致力于解决灵活繁复的硬编码问题,以全新的思想和更高的灵活性解决复杂规则/表单流程规则等问题。
    示例一、员工加班申请表单,当加班小时总数>36小时时,需要提示"加班小时总数已超过36H,不允许申请";


    示例二、一套标准的生活费领取流程如下



    ...

    功能介绍

    • 普通规则解析
    • 自定义规则解析(支持扩展函数)
    • 带数据的规则解析
    • 规则校验

    灵活的表达式规则解析

    1、不带数据的表达式解析

    接口:/api/Rule/GetRuleResult/{ruleText}
    用swagger模拟请求
    普通计算:



    比较值:



    带自定义函数的计算(如Days为获取两个日期相隔的天数):

    2、带数据的表达式解析

    表达式中使用"{字段}"来获取数据字段值



    对于比较常用的三元表达式,程序中内置了IIF函数(IIF(a,b,c) :如果表达式a为真,则取b,否则取c)。
    老婆给我打电话:下班顺路买四个包子带回来,如果看到卖西瓜的,买一个。
    当晚,我手捧一个包子进了家门……
    老婆怒道:你怎么就买了一个包子?!
    我答曰:因为看到了卖西瓜的。

    完整的表达式规则校验

    接口:/api/Rule/ValidateRule/{ruleText}
    提供表达式校验的接口,校验表达式正确性。
    表达式错误:




    方法不存在错误:



    方法格式错误:



    方法参数错误:

    代码


    示例程序使用DDD架构,包含规则解析领域和组织机构领域

    目录说明

    └──LsqParserEngine.Application              应用层:调用领域对象
        ├── Organization               
        ├── RuleParser        									         
    └──LsqParserEngine.Common	            公共类
    └──LsqParserEngine.Domain                   领域层:包含所有的业务规则
        ├── Organization      
          ├── Repositories				组织机构仓储  
        ├── RuleParser 
    └──LsqParserEngine.Entity                   实体层:数据实体、接口等
        ├── Organization    			组织机构实体           
        ├── RuleParser  				规则实体
          ├── Math
            ├── ExecutionItem			  各种类型的解析器
            ├── Function				  自定义函数
            ├── VariableTable                         数据集类型(暂时只加了字典类型)
    └──LsqParserEngine.WebApi		    接口层
    

    规则解析类图

    添加自定义函数

    如何添加自定义函数,以Days(d2,d1)为例
    1.添加函数类,继承Function (LsqParserEngine.Entity=>RuleParser=>Math=>Function)

    /// <summary>
    /// Days获取两个日期相隔的天数
    /// </summary>
    internal class DaysFunction : Function
    {
        public const string Name = "Days";
    
        public DaysFunction(IOrganization organization) : base(organization)
        {
        }
        /// <summary>
        /// 描述的内容
        /// </summary>
        /// <param name="Parameters"></param>
        /// <returns></returns>
        public override string Describe(List<string> Parameters)
        {
           
        }
        /// <summary>
        /// 描述的html
        /// </summary>
        /// <param name="Parameters"></param>
        /// <returns></returns>
        public override string DescribeAsHtml(List<string> Parameters)
        {
           
        }
        
        /// <summary>
        /// 方法帮助:控制参数个数及参数类型
        /// </summary>
        /// <param name="Parameters"></param>
        /// <returns></returns>
        public override FunctionHelper GetHelper()
        {
           
        }
        /// <summary>
        /// 解析的实现
        /// </summary>
        /// <param name="Parameters"></param>
        /// <returns></returns>
        public override Variant Parse(FunctionExpression desc, IVariableTable variables)
        {
            
        }
    
        public override string FunctionName
        {
            get
            {
                return "Days";
            }
        }
    }
    
    

    方法说明:

    • Describe:函数描述,前台函数列表可取此内容
    • DescribeAsHtml:函数描述的html,前台函数列表可取此内容
    • GetHelper:函数帮助,包含函数名、描述、示例、入参约束(支持非必填属性)、出参约束
    • Parse:函数解析的实现

    完整代码:
    1.添加函数类,继承Function (LsqParserEngine.Entity=>RuleParser=>Math=>Function)

    /// <summary>
    /// Days获取两个日期相隔的天数
    /// </summary>
    internal class DaysFunction : Function
    {
        public const string Name = "Days";
    
        public DaysFunction(IOrganization organization) : base(organization)
        {
            return string.Format("获取日期{1}与{0}相隔的天数", Parameters[0], Parameters[1]);
        }
        /// <summary>
        /// 描述的内容
        /// </summary>
        /// <param name="Parameters"></param>
        /// <returns></returns>
        public override string Describe(List<string> Parameters)
        {
            if (Parameters.Count > 1)
            {
                return string.Format("<a>{0}</a>(<a>{1}</a>,<a>{2}</a>,<a>{3}</a>)", this.FunctionName, Parameters[0], Parameters[1]);
            }
            return base.DescribeAsHtml(Parameters);
        }
        /// <summary>
        /// 描述的html
        /// </summary>
        /// <param name="Parameters"></param>
        /// <returns></returns>
        public override string DescribeAsHtml(List<string> Parameters)
        {
            if (Parameters.Count > 1)
            {
                return string.Format("<a>{0}</a>(<a>{1}</a>,<a>{2}</a>,<a>{3}</a>)", this.FunctionName, Parameters[0], Parameters[1]);
            }
            return base.DescribeAsHtml(Parameters);
        }
        
        /// <summary>
        /// 方法帮助:控制参数个数及参数类型
        /// </summary>
        /// <param name="Parameters"></param>
        /// <returns></returns>
        public override FunctionHelper GetHelper()
        {
           return new FunctionHelper(this.FunctionName,
               "获取两个日期相隔的天数",
               this.FunctionName + "({EndData},{StartData})",
               new Parameter[] {
                   //Parameter重载中提供参数是否必填设置
                   new Parameter("EndData", "结束日期", new DataLogicType[] { DataLogicType.DateTime }),
                   new Parameter("StartData", "开始日期", new DataLogicType[] { DataLogicType.DateTime })
               },
               new Parameter("Return", "天数", new DataLogicType[] { DataLogicType.Int }));
        }
        /// <summary>
        /// 解析的实现
        /// </summary>
        /// <param name="Parameters"></param>
        /// <returns></returns>
        public override Variant Parse(FunctionExpression desc, IVariableTable variables)
        {
            if (desc == null || desc.Count < 1 || desc.Count > 2)
            {
                throw new CalcException("The function \"" + this.FunctionName + "\" must have two parameter.");
            }
    
            Variant variant = desc[0];
            Variant variant2 = desc[1];
            DateTime def1 = DateTime.Now;
            if (variant.Value == null || string.IsNullOrEmpty(variant.Value.ToString()) || !DateTime.TryParse(variant.Value.ToString(), out def1))
            {
                return new Variant(-1);
            }
            DateTime def2 = DateTime.Now;
            if (variant2.Value == null || string.IsNullOrEmpty(variant2.Value.ToString()) || !DateTime.TryParse(variant2.Value.ToString(), out def2))
            {
                return new Variant(0);
            }
            DateTime t1 = Convert.ToDateTime(def1.ToShortDateString());
            DateTime t2 = Convert.ToDateTime(def2.ToShortDateString());
            TimeSpan ts = t1.Subtract(t2);
            double diffInDays = ts.TotalDays;
            return new Variant(diffInDays);
        }
    
        public override string FunctionName
        {
            get
            {
                return "Days";
            }
        }
    }
    
    

    2.方法工厂中注入该方法

     public static Function[] Create(IOrganization organization)
     {
         return new Function[] {
             //...
             new DaysFunction(organization),
         };
     }
    

    规则表达式中便可以使用Days()函数了。

    git地址LsqParserEngine

  • 相关阅读:
    JS学习专辑(3) DOM
    JS学习专辑(4) 变量作用域和语句
    JS学习专辑(6) 函数
    JS学习专辑(2) BOM
    WPF Adorner学习(1)
    C# 递归
    IEnumerable和IEnumerator
    C# 索引器
    JS学习专辑(5) 对象和数组
    JS学习专辑(1) 入门
  • 原文地址:https://www.cnblogs.com/liaoshiqi/p/16274187.html
Copyright © 2020-2023  润新知