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

背景

低代码平台中的一个核心内容是规则引擎,规则引擎致力于解决灵活繁复的硬编码问题,以全新的思想和更高的灵活性解决复杂规则/表单流程规则等问题。
示例一、员工加班申请表单,当加班小时总数>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

posted @ 2022-05-15 19:53  齐大齐  阅读(1832)  评论(0编辑  收藏  举报