使用Lua腳本語言開發出高擴展性的系統,AgileEAS.NET SOA中間件Lua腳本引擎介紹


一、前言

     AgileEAS.NET SOA 中間件平台是一款基於基於敏捷並行開發思想和Microsoft .Net構件(組件)開發技術而構建的一個快速開發應用平台。用於幫助中小型軟件企業建立一條適合市場快速變化的開發團隊,以達到節省開發成本、縮短開發時間,快速適應市場變化的目的。

     AgileEAS.NET SOA中間件平台提供了敏捷快速開發軟件工程的最佳實踐,通過提供大量的基礎支撐功能如IOC、ORM、SOA、分布式體系及敏捷並發開發方法所支撐的插件開發體系,以及提供了大量的實體、數據模型設計生成工具、代碼生成工具,用於幫助中小軟件開發商快速成長。

     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年的醫療行業開發之中,做過多年的電子病歷,開發、指導開發過多套電子病歷系統,在電子病歷系統之中,有以下幾個問題一直得不到好的解決方案:

     在電子病歷質控之中有一個自動質控評分,其由程序自動的預先為某個病案打出一個得分:

]3L0{AR35LX4WKWXUAU}(2S

     在沒有使用Lua教本之前呢,也是能實現這種自動評分,但是為了實現這樣的功能,系統設計之中就會要增加一些輔助的表和大量的輔助設計以幫助完成這樣的功能,因為畢竟不存在統一的評分規則,所以這個設計會極其的復雜,以致於很難把這樣的功能做好,或者說在成本和效益估量之中,得不償失。

     另外還有比較簡單的例子就是電子病歷之間的宏替換問題,在書寫電子病歷的過程之中需要動態的插入如病人基本信息、醫院名稱這樣的東西進入病歷,但是因為其數據源並不是唯一確定的,如果不使用動態腳本設計,那么其程序這中就會存在大量的硬編碼,並且如果想要在后期實施階段增加宏,那么則必須要修改程序才能完成。

     下面我們就以自動評分案例向大家講解一下是如何實現功能的呢:

     首先,系統之中必須管理和維護用於自動評分的所有評分規則:

BF$CE1XPYCZES13DJ%Z4XP5

     這里與其他很多開發者的做法不一樣的是,對於每個缺陷項目,我們都為其提供了一個評分規則的腳本的設計,例如對於缺陷“*缺入院記錄”的評分腳本如下:

   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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM