YbSoftwareFactory 代碼生成插件【十六】:Web 下靈活、強大的審批流程實現(含流程控制組件、流程設計器和表單設計器)


    程序=數據結構+算法,而企業級的軟件=數據+流程,流程往往千差萬別,客戶自身有時都搞不清楚,隨時變化的情況更是家常便飯,拋開功能等不談,需求變化很大程度上就是流程的變化,流程的變化會給開發工作造成很大麻煩。而本審批流程具有較強的通用性,同時也有很大的靈活性,雖然無法100%的解決各種非常個性化的審批流程,但至少也能解決其中80%以上的較為通用的流程了。本文將就分享部分心得!

    本審批流程從實現上來說由流程設計器、流程控制組件和表單設計器三大部分組成。下面將分別進行描述:

    1. 流程設計器 

    流程設計器基於 Web,使用了JQuery UI、EasyUI、Bootstrape、Knockout.js等等前端框架,可隨意設計符合自己的流程,首先看看效果圖:

 

    目前的流程庫支持5種類型的活動,其中並行活動表示其前面的活動需進行並行審批,其入口事件都執行通過后才允許執行當前活動;分支活動主要用來判定后續到底哪些活動需要進一步執行,分支活動由系統自動根據所設條件自動執行,條件的聲明采用 C# 的標准語法;每種活動均設有抵達入口和出口的外部方法接口,可在后台編寫好代碼后再在界面上進行配置綁定,目的是提供諸如發送郵件通知等類似的接口功能;同時還能隨意自定義任何活動上的審批事件,例如保存、上報、回退、駁回、通過、結束等等,如下是事件的參數設置界面: 

 

    在此不得不提一下knockout.js,進行屬性的綁定和界面的更新實在是太方便了,但就是聲明 ViewModel 的定義太麻煩。因此本人此處還用到了其 knockout.mapping 插件,通過 ko.mapping.fromJS(data, mapping, this) 一句代碼就可直接把 Json 數據轉換為 ViewModel,也能一句代碼就能把 ViewModel 轉換回要保存的 Json 數據,如果你使用knockout.js 的話,簡直沒有不使用該插件的道理,可讓你從聲明 ViewModel 的繁雜體力活中解脫出來。如下是工作流定義從 WebApi 加載后綁定至界面所需的 ViewModel 的創建代碼:

var ViewModel =  function(data) {
     var self =  this;
    ko.mapping.fromJS(data, mapping,  this);
    self.StartCount = ko.computed( function() {
         var total = 0;
        $.each(self.ActivityDefinitions(),  function() {
             if ( this.ActivityType() == 0) total++;
        });
         return total;
    });
    self.FinishCount = ko.computed( function() {
         var total = 0;
        $.each(self.ActivityDefinitions(),  function() {
             if ( this.ActivityType() == 4) total++;
        });
         return total;
    });
    self.refresh =  function(item) {
        ko.mapping.fromJS(item, self);
    };}

    2. 流程控制組件

    流程控制組件主要完成流程的跳轉控制,數據的加載和保存等。流程控制組件和前面文章中提到過的底層組件一樣,使用了 Provider 模式。因為需要在界面上直接配置自定義跳轉執行條件,而在C#中目前還沒有類似於 Javascript 的 eval 方法。為實現該功能,需要對字符串腳本進行動態編譯,目前.NET 下支持C# 腳本的工具還是很多的,在此先后用過CS-SCRIPT,Javascript.NET和Roslyn,但都沒成功。

     CS-SCRIPT 很好用,但.NET 4.0 下的版本有問題,雖然是 .NET 4.0 的,但實際上還得安裝.NET 4.5,當時在本機測試沒任何問題,一旦部署至服務器上就會報錯,提示不能加載 System.Runtime.CompilerServices 類型之類的錯誤,最后發現是其依賴的 Mono.CSharp.dll 的問題;最終的原因也搞明白了個大概,是.NET 4.0 下安裝了 .NET 4.5 后會修改默認的 .NET 4.0 底層框架,也就是說,這是升級至 .NET 4.5 帶來的兼容性問題,導致在安裝了 .NET 4.5 的環境下所生成的針對.NET 4.0 的 Mono.CSharp.dll 組件無法在沒安裝.NET 4.5 的環境下運行。因為目前很多的服務器環境還是 Windows Server 2003,還沒法安裝.NET 4.5,很是蛋疼,只有放棄。

    Javascript.NET 的最大的特點是使用非常簡單,使用JS兼容的語法,但無法傳遞Dynamic類型的參數。

    最后又使用了Roslyn,和Javascript.NET一樣,.NET 4.0 版本的貌似不支持 dynamic 類型參數,反正我是沒測試成功,估計.NET 4.5 的版本倒是支持,但因很多部署環境還是Windows Server 2003,因此最后還是放棄了。最后沒折,只有自己實現了,通過 CSharpCodeProvider 動態編譯技術實現了一個Eval方法,一番折騰后發現其實是很簡單的,效果也還不錯,該 Eval 的代碼如下,在此粘出代碼共享下勞動成果:

 1  ///   <summary>
 2           ///  動態編譯,獲取條件表達式的執行結果
 3           ///   </summary>
 4           ///   <param name="expression"> 判斷條件表達式 </param>
 5           ///   <param name="objectInstance"> 對象實例,各個屬性和Form表單對應 </param>
 6           ///   <param name="activityInstance"> 當前執行的活動實例對象 </param>
 7           ///   <returns> 編譯並執行條件表達式后返回的結果 </returns>
 8          private  object Eval( string expression, dynamic objectInstance, ActivityInstance activityInstance)
 9         {
10              var codeProvider =  new CSharpCodeProvider();
11              var cpt =  new CompilerParameters();
12 
13              // 引用程序集
14             cpt.ReferencedAssemblies.Add( " system.core.dll ");
15             cpt.ReferencedAssemblies.Add( " system.dll ");
16             cpt.ReferencedAssemblies.Add( " Microsoft.CSharp.dll ");
17              // 獲取對象實例的類型
18              var type = (Type)objectInstance.GetType();
19              // 獲取活動實例對應的程序集路徑
20              var path = activityInstance.GetType().Assembly.Location;
21             cpt.ReferencedAssemblies.Add(path);
22              if (type.Assembly.Location != path)
23             {
24                 cpt.ReferencedAssemblies.Add(type.Assembly.Location);
25             }
26             cpt.CompilerOptions =  " /t:library ";
27             cpt.GenerateInMemory =  true;
28 
29              var builder =  new StringBuilder( "");
30             builder.Append( " using System;\n ");
31             builder.Append( " using System.Dynamic;\n ");
32             builder.Append( " using Yb.Workflow.Provider;\n ");
33 
34              var ns = type.Namespace;
35              if (ns !=  " Yb.Workflow.Provider ")
36             {
37                 builder.Append( string.Format( " using {0};\n ", ns));
38                 cpt.ReferencedAssemblies.Add(activityInstance.GetType().Assembly.Location);
39             }
40 
41             builder.Append( " namespace CSCodeEvaler{ \n ");
42             builder.Append( " public class CSCodeEvaler{ \n ");
43             builder.Append( " public bool EvalCode(dynamic objectInstance,ActivityInstance activityInstance){\n ");
44             builder.Append( " return  " + expression +  " ; \n ");
45             builder.Append( " } \n ");
46             builder.Append( " } \n ");
47             builder.Append( " }\n ");
48              // 在內存中編譯
49              var cr = codeProvider.CompileAssemblyFromSource(cpt, builder.ToString());
50              if (cr.Errors.Count >  0)
51             {
52                  throw  new InvalidExpressionException(
53                      string.Format( " Error ({0}) evaluating: {1} ",
54                     cr.Errors[ 0].ErrorText, expression));
55             }
56 
57              var a = cr.CompiledAssembly;
58              object instance = a.CreateInstance( " CSCodeEvaler.CSCodeEvaler ");
59 
60             Type t = instance.GetType();
61              var mi = t.GetMethod( " EvalCode ");
62              // 反射調用方法的執行結果
63              object result = mi.Invoke(instance,  new  object[] { objectInstance, activityInstance });
64              return result;
65         }

    3. 表單設計器

    表單設計器將在下個版本中集成,當前還只能在流程定義中手動敲入已編輯好的表單內容。表單內容的持久化和加載使用了前面提到過的 ExtensionDataApi,因為支持 .NET 4.0 下的 dynamic 屬性,同時提供了一個名為 Properties 的字典來管理所有的屬性名和屬性值,和 MVC 下的 Request.Form.AllKeys 簡直就是絕配,因此可非常方便地和表單界面進行集成。換句話說,你只管設計好表單界面即可,具體表單數據的保存和加載完全可以交由 ExtensionDataApi 來完成。

    如需進一步了解,可點擊:http://pjdemo.yellbuy.com/

 


免責聲明!

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



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