『低代码平台』规则引擎、表达式解析
背景
低代码平台中的一个核心内容是规则引擎,规则引擎致力于解决灵活繁复的硬编码问题,以全新的思想和更高的灵活性解决复杂规则/表单流程规则等问题。
示例一、员工加班申请表单,当加班小时总数>36小时时,需要提示"加班小时总数已超过36H,不允许申请";
功能介绍
灵活的表达式规则解析
1、不带数据的表达式解析
接口:/api/Rule/GetRuleResult/{ruleText}
用swagger模拟请求
普通计算:
比较值:
带自定义函数的计算(如Days为获取两个日期相隔的天数):
2、带数据的表达式解析
表达式中使用"{字段}"来获取数据字段值
对于比较常用的三元表达式,程序中内置了IIF函数(IIF(a,b,c) :如果表达式a为真,则取b,否则取c)。
老婆给我打电话:下班顺路买四个包子带回来,如果看到卖西瓜的,买一个。
当晚,我手捧一个包子进了家门……
老婆怒道:你怎么就买了一个包子?!
我答曰:因为看到了卖西瓜的。
完整的表达式规则校验
接口:/api/Rule/ValidateRule/{ruleText}
提供表达式校验的接口,校验表达式正确性。
表达式错误:
方法不存在错误:
方法格式错误:
方法参数错误:
代码
目录说明
└──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),
};
}
git地址:LsqParserEngine