一、前言
上一篇我给大家介绍了我的工作流的模型和基本的设计,这篇我想详细说明下我这款工作流的功能及使用示例。这款工作流主要是面向开发者设计的,为了先让大家有个全局的认识,局部功能的设计实现就不细说了,后续有时间我会继续写文章向大家介绍。
二、功能详解及使用示例代码
1、配置流程引擎,一般在程序启动过程中调用(Global.asax.cs中)
//初始化流程引擎 BpmConfiguration .Instance() .Config(@"C:\Configration\BpmConfig.xml") .Start();
如果不指定配置文件,则默认从app.config或web.config中读取流程配置
//初始化流程引擎 BpmConfiguration .Instance() .Start();
当然还支持同时启动多个流程引擎,以提供SAAS程序的支持。
//A租户引擎配置 BpmConfiguration .Instance("TenantA") .Config(@"C:\BpmConfigA.xml")
.Start(); //B租户引擎配置 BpmConfiguration .Instance("TenantB") .Config(@"C:\BpmConfigB.xml") .Start();
XML中的配置包括:数据库连接、日志配置文件、任务计划启动延时、任务计划执行周期、用户关系结构的映射、流程中变量类型的拓展等。
2、取得工作流上下文,即工作流的入口,所有的功能都集中在这个入口上。
var bpm = new BpmContext() .UseTransaction(true) .SetActor("萧秦");
当前对于不同的引擎实例,其上下文是不同的
var bpm = new BpmContext("TenantA"); var bpm = new BpmContext("TenantB");
不传构造参数时,返回的是默认实例。
3、事务支持,是否开启事务、提交、回滚。
bpm.UseTransaction(true); bpm.Commit(); bpm.Rollback();
4、流程定义
//新增流程定义 bpm.NewProcessDefinition("请假流程") .SetXmlFile(@"C:\Definition\demo1.xml") .SetCategory("分类1") .SetEffectDate(DateTime.Now) .SetExpireDate(DateTime.Now.AddDays(180)) .SetMemo("memo1") .Create() //创建流程定义 .Parse() //解析xml
流程创建时,版本号是自动生成的,默认从1.0开始,当流程名称相同时,就会生成不同版本。
在xml中可定义不同的任务节点:开始节点、自动节点、人工节点、决策节点、发散节点、聚合节点、子流程节点、会签节点、等待节点、结束节点。
及连接任务节点的路由、人员分配情况、变量定义、事件动作等信息,可参照我上篇文章中的xml定义
//加载流程定义 var processDefinition = bpm.LoadProcessDefiniton("1"); processDefinition.Deploy(); //发布流程定义 processDefinition.Revoke(); //召回流程定义 processDefinition.Delete(); //删除流程定义
5、流程实例
//发起流程实例 var process = bpm.NewProcessIntance("请假流程","萧秦"); process.SetVariable("project_id", 1399); //保存流程变量 process.Start(); //启动 process.Suspend(); //挂起 process.Resume(); //恢复 process.Cancel(); //撤销 process.End(); //结束
这里NewProcessInstance这个方法实例上有三个参数,第一个是流程定义ID,第二个是启动的业务ID,第三个是子流程的返回栈点ID,非子流程可以忽略。
//启动流程 var startTask = process.Start(); startTask.SetRecord("SO20150903001"); //保存表单数据(关联) startTask.SetAttach("01", "02"); //保存附件信息(关联),可多个 startTask.SetVariable("var1", "value1"); //保存任务变量 startTask.Signal(); //转交下一步
task.SetRecord用于保存当前表单数据id,数据本身保存在业务表中
task.SetAttach用于保存当前节点的附件id,附件信息则保存在附件管理表中
task.Signal流程流转的关键方法,根据流程定义触发token令牌离开动作
//审批任务 var task = bpm.LoadTaskInstance("00"); task.SetOpinion(SignResult.Passed, "我同意"); //设置审批意见 task.SetReceiver("颜经理"); //设置下一步的审批人 task.Signal(); //转交下一步
这里提供了SetReceiver的方法设置下一步的审批人,正常情况下流程定义中已经定义好了,是不需要再进行设置的,但是考虑在实际应用中可能会有把任务给指定领导审批的情况,在通达OA中也是可以设置下一步审批人,故添加了此方法,需要时可以调用,注意应用此方法会覆盖定义中对工作项owner属性的设置。
//任务委派 var task1 = bpm.LoadTaskInstance("01"); task1.AssignCandidate("小郑,小胡"); //添加任务候选人 task1.AssignCandidate("saler", ActorType.Role); //添加任务候选人 task1.AssignTo("李四"); //把任务分配给李四
我的工作流中,对于工作任务只能有一个所有者(owner),一个实际操作者(actor),但可以拥有多个候选人(candidate)。
候选人是当前工作可分配的一范围限制或人员列表,owner是任务的拥有者,actor是owner考虑复杂委办关系后计算出的操作者。
task.AssignCandidate这个方法用于添加任务候选人,第二个参数是对象类型,可以直接添加一个角色、机构、用户组等。
task.AssignTo即分配任务,任务的分配状态包括以下几个状态
public enum AssignStatus { //未决 Pending, //认领, 用户认领任务并接收任务输入数据 Claim, //委办, 委派给另一个人(例如,经理)以代替他或她执行任务 Depute, //到期, 没有在指定的时间段内处理批准任务 Expire, //续订, 没有在给定的时间范围内处理此任务,则该任务将被续订,以便在另一时间段内执行 Renew }
如果流程卡到某个节点很久,我们可以发催办消息
如果一个流程节点的确需要很久才能完成,我设计了一个当前工作进度汇报接口
//工作催办 task.Urge("很急,请经理尽快处理,在线等!"); //汇报当前工作进度 task.Report(0.6, "预计这个星期就能完成");
task.Urge会向任务实际拥有者发送一条催办通知,并生成催办历史。
task.Report会向任务的订阅者(所有关注当前流程任务的人)发送一条进度报告。
//查询变量 var var0 = process.GetVariable("project_id"); var var1 = task1.GetVariable("var1"); var var2 = task1.GetVariable<DateTime>("var2"); var var3 = task1.GetVariableObject("var3");
变量分为三种:
流程变量 会持久化,存在于整个的流程周期内
任务变量 会持久化,存在在当前的任务中
临时变量 不会持久化到数据库中,只存在于当前执行上下文中(executionContext)。
设置变量SetVariable 获取变量GetVariable
支持任意数据类型
6、中国特色审批方式,主要包括会签、加签(前加签、后加签、并加签)、减签、自由流
会签:一个任务由多个人参与共同做决策
加签:这个任务我自己觉得没有把握,想加入一个人或多个人跟我共同决策(在前加签顺序在当前决策者之前,后加签顺序在当前决策者之后,并加签不分顺序并行处理)
减签:跟加签相反,取消某人参与决策的资格
自由流:流程定义中没有,临时添加的动态路由直接把当前工作发送到指定的节点审批。
转会签:由单人决策的普通审批节点转成多人共同决策的会签节点,支持递归会签,即会签子节点可以继续转会签节点 。
转审批:由多人共同决策的会签节点转成单人决策的普通审批节点
在我在工作流中,会签设计了以下几个参数:
a 运行模式,并行时如发散节点,进入会签节点时会同时激活所有参与人的工作任务,串行时则有先后顺序,所以才有了前加签和后加签
public enum RunMode { //串行 Serial, //并行 Parallel }
b 决策模式,根据子节点的结果如何去决策会签节点
public enum DecisionMode { //主办人模式 Sponsor, //投票模式 Vote, //一票通过 OnePass, //一票否决 OneVeto }
主办人模式:需要设置一个主办人,结果以主办人的决策为准,其它人的决策只是提供参考
投票模式:即设置一个通过的比例,由大家投票决定。支持设置每个人的投票权重。
一票通过:其实可以看作是投票模式通过率设置大于0%的一种。
一票否决:可以看作投票模式通过率设置100%的一种。
当然这里只是我把常用的几种模式列出来了,还可以自己拓展决策模式,只需要继承实现我定义的抽象类Decision即可。
c 是否等待,即还有人未表决,但目前已表决的情况已经可以确定会签结果的情况下,需不需要等待其它人表决完成后才继续转交下一步。
会签分两种,一种是流程定义中定义好的会签,一种是普通审批节点临时转成会签的。实际上中国式审批其实就是要灵活,如果在流程定义中定义好的,其实可以不用会签节点,用多个普通节点也可以去实现。会签节点的设计主要是为了转会签这个场景:就是当前普通审批节点的审批人觉得自己没有把握或者不想担责任,可以加入上级领导或其它更多的人一起来决策或提供参考意见。
示例:普通审批转会签 运行模式设置为并行,决策模式是权重投票,需要等待所有人都投票,通过线为65%
//转会签 var task2 = bpm.LoadTaskInstance("02"); task2.ToCountersign(RunMode.Parallel, DecisionMode.Vote, true, 0.65M); task2.CountersignAdd(new Countersigner() { actor_id = "张三", vote_weight = 1 }); task2.CountersignAdd(new Countersigner() { actor_id = "李四", vote_weight = 0.5 }); task2.CountersignAdd(new Countersigner() { actor_id = "小五", vote_weight = 2 });
并行模式即为并加签,前加签、后加签的前提是串行模式,假设task2为串行、主办人模式、原审批人为萧秦
//前加签 task2.CountersignAddBefore("萧秦", new Countersigner() { actor_id = "张三"}); //后加签 task2.CountersignAddAfter("萧秦", new Countersigner() { actor_id = "李四", is_sponsor = true});
减签则相对比较简单了
//减签 task2.CountersignRemove("王五");
会签节点转普通审批,直接让一个人决策
//转审批 task2.ToSinglesign("隔壁老王");
自由流模式,创建临时路直接跳转到指定节点进行审批
//自由流 var task3 = bpm.LoadTaskInstance("03"); task3.SetFreeRedirect("总经理审批"); task3.Signal();
7、回退机制
流程回退到指定节点
//流程实例指定任意节点回退 var process2 = bpm.LoadProcessInstance("02"); process2.Rollback("填写请假单");
任务实例回退到上一步
//当前工作任务回退到上一个审批节点 var task4 = bpm.LoadTaskInstance("04"); task4.Rollback();
8、工作委办
张三把某个任务直接委托给李四办理,支持递归委办关系,即张三委托给李四,李四再委托给王五,王五在委托给赵六…
bpm.NewDeputeService("张三", "李四") .ForTaskInstance("任务实例ID") .Depute();
把整个流程实例委托给李四,即此流程实例下所有的张三的任务都会委托给李四
bpm.NewDeputeService("张三", "李四") .ForProcessInstance("流程实例ID") .Depute();
把某个流程定义中的一个任务节点委托给李四,即所有的这个节点创建的所有任务实例如果是张三的任务都会委托给李四
bpm.NewDeputeService("张三", "李四") .ForTaskDefinition("任务定义ID") .Depute();
把某个流程定义委托给李四,即这个流程中创建的所有的任务实例,如果是张三的任务,在设置的生效期间中都会委托给李四
bpm.NewDeputeService("张三", "李四") .ForProcessDefinition("流程定义ID") .SetDateRange(DateTime.Now, DateTime.Now.AddDays(30)) //生效期间 .SetMemo("这个月出差,这个流程都委托给李四代办") //委托说明 .Depute();
收回委托关系,只要将Revoke替换Depute动作即可
//收回委办工作 bpm.NewDeputeService("张三", "李四") .ForProcessInstance("流程实例ID") .Revoke();
9、关注订阅
这个功能跟委托相似,订阅后会收到流程动态或任务动态消息提醒,如:流程已创建、启动、挂起…,任务已创建、分配给谁、进度汇报、任务完成等等
//关注订阅 bpm.NewSubscribeService("张三") .ForTaskInstance("任务实例ID") .Subscribe(); bpm.NewSubscribeService("张三") .ForProcessInstance("流程实例ID") .Subscribe(); bpm.NewSubscribeService("张三") .ForProcessDefinition("流程定义ID") .Subscribe(); bpm.NewSubscribeService("张三","李四","王五") .ForTaskDefinition("任务定义ID") .Subscribe();
取消订阅,一样只需要把Subscribe改为Unsubscribe即可
//取消订阅 bpm.NewSubscribeService("李四") .ForProcessDefinition("采购流程") .Unsubscribe();
10、数据查询
查询我没有提供接口,直接开放数据库查询比我提供的接口会更加灵活,比如:
a 查询已发布的流程定义
select * from bpm_definition_process where state = ‘Deploy‘
流程定义状态包括
public enum ProcessDefinitionState { //创建 Create, //解析 Parse, //发布 Deploy, //回收 Revoke, //删除 Delete }
b 我的流程
select * from bpm_instance_process where state = ‘Run‘ and starter = ‘萧秦‘
流程状态包括
public enum ProcessState { //创建 Create, //运行 Run, //挂起 Pending, //终止 Termination, //完成 Complete, //取消 Cancel }
c 我的待办任务
select * from bpm_instance_task where state = ‘Run‘ and actor_id = ‘萧秦‘
待办任务包括了别人委托给你的任务,如果只想看属于自己的任务则可以
select * from bpm_instance_task where state = ‘Run‘ and owner_id = ‘萧秦‘
任务状态包括
public enum TaskState { //创建, 任务已被创建 Create, //阻塞, 到达线中有阻塞任务还未完成 Blocking, //启动 Run, //完成, 用户已经完成任务并提供了任务的输出数据 Complete, //失败, 用户已经完成任务, 并且提供了错误消息 Failure, //回退 Rollback }
d 查询任务的候选人信息
select * from bpm_instance_assignment where task_instance_id = ‘ID‘
e 查询我的消息
select * from bpm_application_notify where state=‘Unread‘ and reciever_id = ‘萧秦‘
其它查询就不再举例了
三、总结
之前我有说过我开发这个的引擎的目的是为了在做项目时,有一个体积轻巧,引入方便的单dll文件(发布后大小为1.1M)的工作流引擎,接口也简单易于二次开发,支持多数据库并且功能还算强大。从前年开始的简易版本设计开发到现在基本成形,测试也是花费了大量的时间,可能还有问题没有测到,不过现在基本稳定。接下来如果有时间我会慢慢跟大家介绍功能细节的设计和实现,如果还有什么功能我考虑不周全的大家可以留言给我提提意见。