使用Lua脚本语言开发出高扩展性的系统,AgileEAS.NET SOA中间件Lua脚本引擎介绍

时间:2024-01-16 13:38:14

一、前言

AgileEAS.NET SOA 中间件平台是一款基于基于敏捷并行开发思想和Microsoft .Net构件(组件)开发技术而构建的一个快速开发应用平台。用于帮助中小型软件企业建立一条适合市场快速变化的开发团队,以达到节省开发成本、缩短开发时间,快速适应市场变化的目的。

AgileEAS.NET SOA中间件平台提供了敏捷快速开发软件工程的最佳实践,通过提供大量的基础支撑功能如IOC、ORM、SOA、分布式体系及敏捷并发开发方法所支撑的插件开发体系,以及提供了大量的实体、数据模型设计生成工具、代码生成工具,用于帮助中小软件开发商快速成长。

使用Lua脚本语言开发出高扩展性的系统,AgileEAS.NET SOA中间件Lua脚本引擎介绍

AgileEAS.NET平台充分把握目前软件行业快速发展的新趋势,基于敏捷并行开发、快速适应市场这样淳朴的软件工程实践,采用业界广泛使用的Microsoft .Net构件(组件)开发技术实践了这种开发思想,帮助软件企业实现“敏捷变化、快速适合”的目标,从而帮助软件企业在激烈的市场竞争中赢得先机并获得更高的回报。

二、关于Lua语言

Lua 是一个小巧的脚本语言。是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组,由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo所组成并于1993年开发。 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

Lua语言目前应用最广泛的领域是游戏编程领域,最早接触和认识Lua也是因为在2008年玩金山的剑网3,通过解包读过其中的一些游戏脚本,慢慢也看过一些其他游戏的Lua脚本,在我们开发自己的电子病历系统的过程之中,引入了Lua脚本语言实现那些可变性非常高的场景。

.NET通过LuaInterface开源项目类库实现对Lua的脚本调用以及Lua与C#的相互绑定,有关于这此方面的内容请大家搜索相关文章以获得帮助。

在AgileEAS.NET SOA5.0版本之中,我们决定把在开发过程之中形成的Lua脚本引擎一并集成入AgileEAS.NET SOA中间件平台,目前Lua脚本语言被我们广泛的应用电子病历系统这中的病案自动评分、短信系统之中的交互式应答、和一些计划任系统之中的计划任务定义。

三、AgileEAS.NET SOA平台Lua引擎

AgileEAS.NET SOA5.0平台目前使用的是Lua5.1版本,使用LuaInterface实现C#与Lua的相互绑定处理,平台已经对其进行了二次封装以提供统一的API支持,目前由EAS.LuaScript.dll程序集承载所有业务。

AgileEAS.NET SOA平台Lua引擎提供了以下接口或类型的API:

ILuaEngine,定义为一个Lua脚本引擎:

   1: using System;

   2:  

   3: namespace EAS.LuaScript

   4: {

   5:     /// <summary>  

   6:     /// Lua脚本解析引擎。  

   7:     /// </summary>  

   8:     /// <remarks>

   9:     /// 用于完成程序之中嵌入的动态Lua脚本,Lua脚本在AgileEAS.NET SOA 中间件之中主要用于环境的各种参数的动态解析处理之中。

  10:     /// </remarks>

  11:     public interface ILuaEngine:IDisposable

  12:     {

  13:         /// <summary>

  14:         /// 脚本路径。

  15:         /// </summary>

  16:         string ScriptDirectory

  17:         {

  18:             get;

  19:             set;

  20:         }

  21:  

  22:         /// <summary>

  23:         /// 输出重定向方法/用于调试。

  24:         /// </summary>

  25:         Action<object> OutAction

  26:         {

  27:             get;

  28:             set;

  29:         }

  30:  

  31:         /// <summary>  

  32:         /// 注册lua函数,实现Lua绑定。

  33:         /// </summary>  

  34:         /// <param name="luaAPIClass">lua函数类</param>  

  35:         void BindLuaFunctions(object luaAPIClass);

  36:  

  37:         /// <summary>  

  38:         /// 执行lua脚本文件。 

  39:         /// </summary>  

  40:         /// <param name="luaFileName">脚本文件名。</param>  

  41:         /// <returns>lua脚本执行结果。</returns>

  42:         object[] DoFile(string luaFileName);

  43:  

  44:         /// <summary>  

  45:         /// 执行lua脚本文本。

  46:         /// </summary>  

  47:         /// <param name="chunk">lua指令。</param>  

  48:         /// <returns>lua脚本执行结果。</returns>

  49:         object[] DoString(string chunk);

  50:  

  51:         /// <summary>

  52:         /// 调用Lua函数。

  53:         /// </summary>

  54:         /// <param name="luaFunction">函数名称。</param>

  55:         /// <param name="args">调用参数。</param>

  56:         /// <returns>lua脚本执行结果。</returns>

  57:         object[] Invoke(string luaFunction, params object[] args);

  58:     }

  59: }

其中脚本路径ScriptDirectory指示Lua脚本文件的存储位置,在DoFile执行脚本文过程之中如果传入为相关路径则可以自动在ScriptDirectory之中寻址,ScriptDirectory默认为当前程序目录。

DoFile:执行脚本文件并且返回执行结果。

DoString:执行脚本代码并且返回执行结果。

Invoke:执行指定的脚本函数。

      LuaFunctionAttribute,Lua脚本函数特性:

   1: using System;

   2: using System.Collections.Generic;

   3: using System.Linq;

   4: using System.Text;

   5:  

   6: namespace EAS.LuaScript

   7: {

   8:     /// <summary>  

   9:     /// Lua函数描述特性。

  10:     /// </summary>  

  11:     /// <remarks>

  12:     /// 用于标记Lua接口方法。

  13:     /// </remarks>

  14:     [AttributeUsage(AttributeTargets.Method,

  15:                     Inherited = false, AllowMultiple = false)]

  16:     [Serializable]

  17:     public class LuaFunctionAttribute : Attribute

  18:     {

  19:         /// <summary>

  20:         /// 初始化LuaFunctionAttribute对象实例。

  21:         /// </summary>

  22:         /// <param name="name">函数名称。</param>

  23:         public LuaFunctionAttribute(string name)

  24:             : this(name, string.Empty)

  25:         {

  26:  

  27:         }

  28:  

  29:         /// <summary>

  30:         /// 初始化LuaFunctionAttribute对象实例。

  31:         /// </summary>

  32:         /// <param name="name">函数名称。</param>

  33:         /// <param name="description">函数描述。</param>

  34:         public LuaFunctionAttribute(string name, string description)

  35:             : this(name, description,null)

  36:         {

  37:  

  38:         }

  39:  

  40:         /// <summary>

  41:         /// 初始化LuaFunctionAttribute对象实例。

  42:         /// </summary>

  43:         /// <param name="name">函数名称。</param>

  44:         /// <param name="description">函数描述。</param>

  45:         /// <param name="funcParams">参数说明。</param>

  46:         public LuaFunctionAttribute(string name, string description, params string[] funcParams)

  47:         {

  48:             this.Name = name;

  49:             this.Description = description;

  50:             this.Params = funcParams;

  51:         }

  52:  

  53:         /// <summary>

  54:         /// 函数名称。

  55:         /// </summary>

  56:         public string Name

  57:         {

  58:             get;

  59:             internal set;

  60:         }

  61:  

  62:         /// <summary>

  63:         /// 函数描述。

  64:         /// </summary>

  65:         public string Description

  66:         {

  67:             get;

  68:             internal set;

  69:         }

  70:  

  71:         /// <summary>

  72:         /// 参数说明。

  73:         /// </summary>

  74:         public string[] Params

  75:         {

  76:             get;

  77:             internal set;

  78:         }

  79:     }

  80: }

用于C#向Lua暴露函数接口,实现C#方法与Lua脚本函数的绑定,也可以实现为把C#的方法暴露给Lua脚本调用,如以下代码:

   1: /// <summary>

   2: /// 执行指定的非查询命令。

   3: /// </summary>

   4: /// <param name="commandText">要执行的命令语句。该语句必须是标准的数据库语句。</param>

   5: /// <returns>返回命令影响的行数。</returns>

   6: [LuaFunction("ExecuteSql", "执行指定的非查询命令,返回执行所影响的行数。", "要执行的SQL语句")]

   7: public int Execute(string commandText)

   8: {

   9:     IDataAccessor da = this.GetComponent();

  10:     if (da != null)

  11:     {

  12:         return da.Execute(commandText);

  13:     }

  14:     else

  15:     {

  16:         return -1;

  17:     }

  18: }

其目标早实现C#方法Execute与Lua脚本函数ExecuteSql之间的绑定,当在Lua教本之中调用ExecuteSql,即会执行Execute方法。

LuaFramework,Lua框架,为Lua脚本引用最重要的API:

   1: #region 程序集 EAS.LuaScript.dll, v4.0.30319

   2: // G:\Health.Work\AgileEMR4.0\Publish\EAS.LuaScript.dll

   3: #endregion

   4:  

   5: using System;

   6:  

   7: namespace EAS.LuaScript

   8: {

   9:     // 摘要:

  10:     //     Lua脚本执行框架/上下文。

  11:     //

  12:     // 备注:

  13:     //     应用系统之中调用Lua脚本的入口。

  14:     public sealed class LuaFramework

  15:     {

  16:         // 摘要:

  17:         //     LuaFramework对象的唯一实例。

  18:         public static LuaFramework Instance { get; }

  19:  

  20:         // 摘要:

  21:         //     注册lua函数,实现Lua绑定。

  22:         //

  23:         // 参数:

  24:         //   luaAPIClass:

  25:         //     lua函数类

  26:         public static void BindLuaFunctions(object luaAPIClass);

  27:         //

  28:         // 摘要:

  29:         //     从应用程序上下文环境之中获取指定名称的对象实例。

  30:         //

  31:         // 参数:

  32:         //   componentKey:

  33:         //     组件名称。

  34:         //

  35:         // 返回结果:

  36:         //     对象实例。

  37:         public object GetComponent(string componentKey);

  38:         //

  39:         // 摘要:

  40:         //     Lua脚本引擎。

  41:         public static ILuaEngine GetLuaEngine();

  42:         //

  43:         // 摘要:

  44:         //     Lua脚本引擎。

  45:         public static ILuaEngine GetLuaEngine(string scriptDirectory);

  46:         //

  47:         // 摘要:

  48:         //     取系统时间。

  49:         [LuaFunction("GetTime", "取系统时间")]

  50:         public DateTime GetTime();

  51:     }

  52: }

其中BindLuaFunctions方法实现C#方法与Lua函数的绑定注册,调用时传入需要注册方法所在的C#类对象实例即可。
     GetLuaEngine获取一个ILuaEngine,为之后的执行脚本作准备,调用GetLuaEngine可选传入scriptDirectory参数,scriptDirectory为Lua脚本存储目录。

AgileEAS.NET SOA中间件已经为大家提供了少数的几个预定义API绑定:

   1: helpcmd(cmdName) - Show help for a given command or package

   2: help() - List available commands.

   3: Include(luaFileName) - 预加载指定的脚本文件

   4: echo(message) - 显示Lua调试信息,用于Lua的动态调试。

   5: Echo(message) - 显示Lua调试信息,用于Lua的动态调试。

   6: WriteLog(message) - 写日志记录,用于Lua脚本的动态调试。

   7: GetTime() - 取系统时间

   8: GetComponent(componentKey) - 从应用程序上下文环境之中获取指定名称的对象实例

   9: GetAccount() - 求当前账户信息

  10: ExecuteSql(commandText) - 执行指定的非查询命令,返回执行所影响的行数。

  11: QuerySql(commandText, resultType) - 执行给定的数据库查询命令,返回执行结果,返回结果由resultType参数决定。

  12: QuerySqlScalar(commandText) - 执行给定的数据库查询命令,仅返回第一行第一列结果

  13: QuerySqlMatrix(commandText) - 执行给定的数据库查询命令,返回第一个查询结果Matrix

  14: QuerySqlDictionary(commandText) - 执行给定的数据库查询命令,返回第一行的Key-Value

  15: QuerySqlList(commandText) - 执行给定的数据库查询命令,返回第一行的Key-Value

  16: QuerySqlDataSet(commandText) - 执行给定的数据库查询命令,返回查询结果集

  17: QuerySqlDataTable(commandText) - 执行给定的数据库查询命令,返回查询结果集的第一个DataTable

  18: GetDataSet() - 求当前系统的帐套信息

大家也可以基于AgileEAS.NET SOA 平台Lua引擎扩展自己的LuaAPI。

四、AgileEAS.NET SOA平台Lua引擎应用案例

在我12年的医疗行业开发之中,做过多年的电子病历,开发、指导开发过多套电子病历系统,在电子病历系统之中,有以下几个问题一直得不到好的解决方案:

在电子病历质控之中有一个自动质控评分,其由程序自动的预先为某个病案打出一个得分:

使用Lua脚本语言开发出高扩展性的系统,AgileEAS.NET SOA中间件Lua脚本引擎介绍

在没有使用Lua教本之前呢,也是能实现这种自动评分,但是为了实现这样的功能,系统设计之中就会要增加一些辅助的表和大量的辅助设计以帮助完成这样的功能,因为毕竟不存在统一的评分规则,所以这个设计会极其的复杂,以致于很难把这样的功能做好,或者说在成本和效益估量之中,得不偿失。

另外还有比较简单的例子就是电子病历之间的宏替换问题,在书写电子病历的过程之中需要动态的插入如病人基本信息、医院名称这样的东西进入病历,但是因为其数据源并不是唯一确定的,如果不使用动态脚本设计,那么其程序这中就会存在大量的硬编码,并且如果想要在后期实施阶段增加宏,那么则必须要修改程序才能完成。

下面我们就以自动评分案例向大家讲解一下是如何实现功能的呢:

首先,系统之中必须管理和维护用于自动评分的所有评分规则:

使用Lua脚本语言开发出高扩展性的系统,AgileEAS.NET SOA中间件Lua脚本引擎介绍

这里与其他很多开发者的做法不一样的是,对于每个缺陷项目,我们都为其提供了一个评分规则的脚本的设计,例如对于缺陷“*缺入院记录”的评分脚本如下:

   1: -----------------------------------------------------------------------------------------------

   2: -- 创建人   :  魏琼东

   3: -- 创建时间    : 2013-09-05

   4: -- 效果备注    : *缺入院记录评分脚本。

   5: --               GetErrorNumber,求缺陷数量,由脚本计算出本缺陷数量

   6: ----------------------------------------------------------------------------------------------

   7: ------------------------不怎么华丽的分割线--------------------------------------

   8: function GetErrorNumber(patient,errorItem)

   9:  

  10:     --调用C#,求监控项目

  11:     local monitorItem = GetMonitorItem("B01")    

  12:     --监控表达式,无表达式返回0

  13:     local script = monitorItem.Expression

  14:     if script == nil then

  15:         return 0

  16:     elseif script == "" then

  17:         return 0

  18:     end    

  19:     

  20:     --加载监控脚本

  21:     ExecuteScript(script)

  22:     --执行监控函数NeedWrite,如果需要写则预标存在此缺陷

  23:     local need = NeedWrite(patient,monitorItem)

  24:     

  25:     if need == 1 then

  26:         return 1

  27:     else

  28:         return 0

  29:     end

  30:  

  31: end

这样的设计的目标是将缺陷的评分动作都由Lua脚本实现,而在应用程序之中并不存在针对缺陷制定不同的评分规则的情况,只有调用脚本进行评分的代码:

   1: /// <summary>

   2: /// 求指定患者、指定缺陷的缺陷数。

   3: /// </summary>

   4: /// <param name="pRoot"></param>

   5: /// <param name="errorItem"></param>

   6: /// <returns></returns>

   7: public static int GetErrorNumber(ILuaEngine m_Lua, PatientRoot pRoot, ErrorDict errorItem)

   8: {

   9:     if (string.IsNullOrEmpty(errorItem.Expression))  //无评分脚本

  10:     {

  11:         return 0;

  12:     }

  13:  

  14:     string m_file = System.IO.Path.Combine(LuaEx.ScriptDirectory, string.Format("emr\\error\\{0}.lua", errorItem.ErrorCode));

  15:     if (!System.IO.File.Exists(m_file))

  16:     {

  17:         return 0;

  18:     }

  19:  

  20:     try

  21:     {

  22:         m_Lua.DoFile(m_file);  //加载脚本

  23:  

  24:         object[] vs = m_Lua.Invoke("GetErrorNumber", pRoot, errorItem);  //执行函数。

  25:         if (vs != null && vs.Length > 0)

  26:         {

  27:             return int.Parse(vs[0].ToString());

  28:         }

  29:         else

  30:         {

  31:             return 0;

  32:         }

  33:     }

  34:     catch

  35:     {

  36:         return 0;

  37:     }

  38: }

这样的好处在于,主体程序变的极其的简单一致,不需要各种判断和各种规则,并且灵活自动,可以做到政策变化或者客户需求变化导到的各种评分规则的修改,则会灵活自如。

实践证明,使用动态脚本语言扩展现在系统的系统是一种低投入高产出的工作,对于应对这种高扩展性和高定制性的项目是一个非常好的选择,这个可以解释游戏编程之中大量使用Lua语言的事实,针对NPC、剧情书写相关的处理脚本,让游戏的后期变更,调整变得简单可行。

四、联系我们

为完善、改进和推广AgileEAS.NET而成立了敏捷软件工程实验室,是一家研究、推广和发展新技术,并致力于提供具有自主知识产权的业务基础平台软件,以及基于业务基础平台开发的管理软件的专业软件提供商。主要业务是为客户提供软件企业研发管理解决方案、企业管理软件开发,以及相关的技术支持,管理及技术咨询与培训业务。

AgileEAS.NET SOA中间件平台自2004年秋呱呱落地一来,我就一直在逐步完善和改进,也被应用于保险、医疗、电子商务、房地产、铁路、教育等多个应用,但一直都是以我个人在推广,2010年因为我辞职休息,我就想到把AgileEAS.NET推向市场,让更多的人使用。

我的技术团队成员都是合作多年的老朋友,因为这个平台是免费的,所以也没有什么收入,都是由程序员的那种理想与信念坚持,在此我感谢一起奋斗的朋友。

团队网站:http://www.agilelab.cn

AgileEAS.NET网站:http://www.agileeas.net

官方博客:http://eastjade.cnblogs.com

github:https://github.com/agilelab/eas

QQ:47920381

QQ群:113723486(AgileEAS SOA 平台)/上限1000人

199463175(AgileEAS SOA 交流)/上限1000人

212867943(AgileEAS.NET研究)/上限500人

147168308(AgileEAS.NET应用)/上限500人

172060626(深度AgileEAS.NET平台)/上限500人

116773358(AgileEAS.NET 平台)/上限500人

125643764(AgileEAS.NET探讨)/上限500人

193486983(AgileEAS.NET 平台)/上限500人

邮件:james@agilelab.cn,mail.james@qq.com,

电话:18629261335。