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

一、前言

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

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

AgileEAS.NET.png

    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:publicinterface 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, paramsobject[] 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:publicclass 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, paramsstring[] funcParams)
  47:         {
  48:this.Name = name;
  49:this.Description = description;
  50:this.Params = funcParams;
  51:         }
  52:
  53:/// <summary>
  54:/// 函數名稱。
  55:/// </summary>
  56:publicstring Name
  57:         {
  58:             get;
  59:internal set;
  60:         }
  61:
  62:/// <summary>
  63:/// 函數描述。
  64:/// </summary>
  65:publicstring Description
  66:         {
  67:             get;
  68:internal set;
  69:         }
  70:
  71:/// <summary>
  72:/// 參數說明。
  73:/// </summary>
  74:publicstring[] 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:publicint 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:publicsealedclass LuaFramework
  15:     {
  16:// 摘要:
  17://     LuaFramework對象的唯一實例。
  18:publicstatic LuaFramework Instance { get; }
  19:
  20:// 摘要:
  21://     註冊lua函數,實現Lua綁定。
  22://
  23:// 參數:
  24://   luaAPIClass:
  25://     lua函數類
  26:publicstaticvoid BindLuaFunctions(object luaAPIClass);
  27://
  28:// 摘要:
  29://     從應用程序上下文環境之中獲取指定名稱的對象實例。
  30://
  31:// 參數:
  32://   componentKey:
  33://     組件名稱。
  34://
  35:// 返回結果:
  36://     對象實例。
  37:publicobject GetComponent(string componentKey);
  38://
  39:// 摘要:
  40://     Lua腳本引擎。
  41:publicstatic ILuaEngine GetLuaEngine();
  42://
  43:// 摘要:
  44://     Lua腳本引擎。
  45:publicstatic 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.smarteas.net

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

QQ:47920381,AgileEAS.NET

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人

郵件:[email protected],[email protected],

電話:18629261335。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章