YbSoftwareFactory 代码生成插件【十六】:Web 下灵活、强大的审批流程实现(含流程控制组件、流程设计器和表单设计器)

    程序=数据结构+算法,而企业级的软件=数据+流程,流程每每千差万别,客户自身有时都搞不清楚,随时变化的状况更是屡见不鲜,抛开功能等不谈,需求变化很大程度上就是流程的变化,流程的变化会给开发工做形成很大麻烦。而本审批流程具备较强的通用性,同时也有很大的灵活性,虽然没法100%的解决各类很是个性化的审批流程,但至少也能解决其中80%以上的较为通用的流程了。本文将就分享部分心得!前端

    本审批流程从实现上来讲由流程设计器、流程控制组件和表单设计器三大部分组成。下面将分别进行描述:算法

    1. 流程设计器 express

    流程设计器基于 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. 流程控制组件app

    流程控制组件主要完成流程的跳转控制,数据的加载和保存等。流程控制组件和前面文章中提到过的底层组件同样,使用了 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,非常蛋疼,只有放弃。ide

    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/

相关文章
相关标签/搜索