I. Introduction
Connect one . NET Micro Core Services permissions system + workflow (a) rights system , come out of
Workflow, I've been curious about this development at the time of contact with its implementation, look at a variety of workflow engine code, explore its implementation, the individuals summed up a core elements:
Indeed workflow engine handles the transfer of the core essence is how to parse XML or JSON circulation or other persistent way , the workflow information by parsing XML or JSON determine the current state of the node and the next node and make some processing. Feel equal say anything? Bluntly, is to get through the next step is to parse JSON file who handles.
Circulation line workflow is actually fixed dead , you can know all the possible permutations and combinations of lines, and did not imagine so difficult to understand. Good understanding of this point, then the next development is very simple, just the code base (manual smile .ing). The system analyzes the workflow specific implementation, specific implementation steps are not set forth, a detailed look at the code address GitHub.
Second, the system introduced
In-depth study over work flow friends may know, it is divided into two processes form:
1, custom forms. Closer to the business, but it will be exhausted developers. Previous company are developed this way, the business logic and specific relationship, recommend the use of more complex custom form way that developers over the development of the business functions, and processes related to.
2, the code generated form. No code, the system can be generated automatically, easily, but poor scalability functionality.
Of course, each good. This system have been achieved in two ways, it focuses on the customization process. The system artificially provides: a process can be bound to a form, a form, a process can be bound . That one, which is the premise of all. As for why do it?
Typically a process is to be linked with the form logic, basically there is no possibility of multiple, and likely to cause tissue disorder, yes, it would then draw a process in a form. ^ _ @ _ @
Third, implement workflows
Or database-oriented approach to development, look at the table:
wf_workflow: Workflow table, store basic information workflow
wf_workflow_category: Process Classification
wf_workflow_form: Process table form, divided into two types, the system generates forms and systems custom forms, custom forms only storage system URL address
wf_workflow_instance : process instance table, the core
wf_workflow_instance_form: Examples of process association table form
wf_workflow_line: Process netlist. Instead of the current two forms of storage (agree, disagree), the latter will add custom business logic flow node SQL judge
wf_workflow_operation_history: Process operation history table. Obtaining approval for opinions
wf_workflow_transition_history: process flow records. Return for obtaining a step acquisition nodes, etc.
Currently workflow implements these functions: save, submit, agree, disagree, surrender, termination, flowcharts, approval of the views , the latter will continue to upgrade iteration, such as adding countersigned, suspended, notification, and now these function should be able to meet the general business needs, like countersign this function with less than 99%, but is indeed more complex functions, involving parallel, serial calculation, 80% less than the time spent with these functions up, the so-called Pareto rule it.
All function more, to name a few: the current flow classification function not only achieve, the subsequent write it, but does not affect the use of functions, but only for screening
Process design interface: The GooFlow plug-in, make some changes to the code and its interface is indeed ugly, relatively simple design, graphic design, after all, I will not, if you do not feel ugly, when I did not say.
Core code: actually parse JSON file, easy to read and to write some node, connection method
1 /// <summary> 2 /// workflow context 3 /// </summary> 4 public class MsWorkFlowContext : WorkFlowContext 5 { 6 /// <summary> 7 /// 构造器传参 8 /// </summary> 9 /// <param name="dbworkflow"></param> 10 public MsWorkFlowContext(WorkFlow dbworkflow) 11 { 12 if (dbworkflow.FlowId == default(Guid)) 13 { 14 throw new ArgumentNullException("FlowId", " input workflow flowid is null"); 15 } 16 if (dbworkflow.FlowJSON.IsNullOrEmpty()) 17 { 18 throw new ArgumentException("FlowJSON", "input workflow json is null"); 19 } 20 if (dbworkflow.ActivityNodeId == null) 21 { 22 throw new ArgumentException("ActivityNodeId", "input workflow ActivityNodeId is null"); 23 } 24 25 this.WorkFlow = dbworkflow; 26 27 dynamic jsonobj = JsonConvert.DeserializeObject(this.WorkFlow.FlowJSON); 28 //获取节点 29 this.WorkFlow.Nodes = this.GetNodes(jsonobj.nodes); 30 //获取连线 31 this.WorkFlow.Lines = this.GetFromLines(jsonobj.lines); 32 33 this.WorkFlow.ActivityNodeId = dbworkflow.ActivityNodeId == default(Guid) ? this.WorkFlow.StartNodeId : dbworkflow.ActivityNodeId; 34 35 this.WorkFlow.ActivityNodeType = this.GetNodeType(this.WorkFlow.ActivityNodeId); 36 37 //会签开始节点和流程结束节点没有下一步 38 if (this.WorkFlow.ActivityNodeType == WorkFlowInstanceNodeType.ChatNode || this.WorkFlow.ActivityNodeType == WorkFlowInstanceNodeType.EndRound) 39 { 40 this.WorkFlow.NextNodeId = default(Guid);//未找到节点 41 this.WorkFlow.NextNodeType = WorkFlowInstanceNodeType.NotRun; 42 } 43 else 44 { 45 var nodeids = this.GetNextNodeId(this.WorkFlow.ActivityNodeId); 46 if (nodeids.Count == 1) 47 { 48 this.WorkFlow.NextNodeId = nodeids[0]; 49 this.WorkFlow.NextNodeType = this.GetNodeType(this.WorkFlow.NextNodeId); 50 } 51 else 52 { 53 //多个下个节点情况 54 this.WorkFlow.NextNodeId = default(Guid); 55 this.WorkFlow.NextNodeType = WorkFlowInstanceNodeType.NotRun; 56 } 57 } 58 } 59 60 /// <summary> 61 /// 下个节点是否是多个 62 /// </summary> 63 public bool IsMultipleNextNode { get; set; } 64 65 /// <summary> 66 /// 获取节点集合 67 /// </summary> 68 /// <param name="nodesobj"></param> 69 /// <returns></returns> 70 private Dictionary<Guid, FlowNode> GetNodes(dynamic nodesobj) 71 { 72 Dictionary<Guid, FlowNode> nodes = new Dictionary<Guid, FlowNode>(); 73 74 foreach (JObject item in nodesobj) 75 { 76 FlowNode node = item.ToObject<FlowNode>(); 77 if (!nodes.ContainsKey(node.Id)) 78 { 79 nodes.Add(node.Id, node); 80 } 81 if (node.Type == FlowNode.START) 82 { 83 this.WorkFlow.StartNodeId = node.Id; 84 } 85 } 86 return nodes; 87 } 88 89 /// <summary> 90 /// 获取工作流节点及以节点为出发点的流程 91 /// </summary> 92 /// <param name="linesobj"></param> 93 /// <returns></returns> 94 private Dictionary<Guid, List<FlowLine>> GetFromLines(dynamic linesobj) 95 { 96 Dictionary<Guid, List<FlowLine>> lines = new Dictionary<Guid, List<FlowLine>>(); 97 98 foreach (JObject item in linesobj) 99 { 100 FlowLine line = item.ToObject<FlowLine>(); 101 102 if (!lines.ContainsKey(line.From)) 103 { 104 lines.Add(line.From, new List<FlowLine> { line }); 105 } 106 else 107 { 108 lines[line.From].Add(line); 109 } 110 } 111 112 return lines; 113 } 114 115 /// <summary> 116 /// 获取全部流程线 117 /// </summary> 118 /// <returns></returns> 119 public List<FlowLine> GetAllLines() 120 { 121 dynamic jsonobj = JsonConvert.DeserializeObject(this.WorkFlow.FlowJSON); 122 List<FlowLine> lines = new List<FlowLine>(); 123 foreach (JObject item in jsonobj.lines) 124 { 125 FlowLine line = item.ToObject<FlowLine>(); 126 lines.Add(line); 127 } 128 return lines; 129 } 130 131 /// <summary> 132 /// 根据节点ID获取From(流入的线条) 133 /// </summary> 134 /// <param name="nodeid"></param> 135 /// <returns></returns> 136 public List<FlowLine> GetLinesForFrom(Guid nodeid) 137 { 138 var lines = GetAllLines().Where(m => m.To == nodeid).ToList(); 139 return lines; 140 } 141 142 public List<FlowLine> GetLinesForTo(Guid nodeid) 143 { 144 var lines = GetAllLines().Where(m => m.From == nodeid).ToList(); 145 return lines; 146 } 147 148 /// <summary> 149 /// 获取全部节点 150 /// </summary> 151 /// <returns></returns> 152 public List<FlowNode> GetAllNodes() 153 { 154 dynamic jsonobj = JsonConvert.DeserializeObject(this.WorkFlow.FlowJSON); 155 List<FlowNode> nodes = new List<FlowNode>(); 156 foreach (JObject item in jsonobj.nodes) 157 { 158 FlowNode node = item.ToObject<FlowNode>(); 159 nodes.Add(node); 160 } 161 return nodes; 162 } 163 164 /// <summary> 165 /// 根据节点ID获取节点类型 166 /// </summary> 167 /// <param name="nodeId"></param> 168 /// <returns></returns> 169 public WorkFlowInstanceNodeType GetNodeType(Guid nodeId) 170 { 171 var _thisnode = this.WorkFlow.Nodes[nodeId]; 172 return _thisnode.NodeType(); 173 } 174 175 /// <summary> 176 /// 根据节点id获取下个节点id 177 /// </summary> 178 /// <param name="nodeId"></param> 179 /// <returns></returns> 180 public List<Guid> GetNextNodeId(Guid nodeId) 181 { 182 List<FlowLine> lines = this.WorkFlow.Lines[nodeId]; 183 if (lines.Count > 1) 184 { 185 this.IsMultipleNextNode = true; 186 } 187 return lines.Select(m => m.To).ToList(); 188 } 189 190 /// <summary> 191 /// 节点驳回 192 /// </summary> 193 /// <param name="rejectType">驳回节点类型</param> 194 /// <param name="rejectNodeid">要驳回到的节点</param> 195 /// <returns></returns> 196 public Guid RejectNode(NodeRejectType rejectType, Guid? rejectNodeid) 197 { 198 switch (rejectType) 199 { 200 case NodeRejectType.PreviousStep: 201 return this.WorkFlow.PreviousId; 202 case NodeRejectType.FirstStep: 203 var startNextNodeId = this.GetNextNodeId(this.WorkFlow.StartNodeId).First(); 204 return startNextNodeId; 205 case NodeRejectType.ForOneStep: 206 if (rejectNodeid == null || rejectNodeid == default(Guid)) 207 { 208 throw new Exception("驳回节点没有值!"); 209 } 210 var fornode = this.WorkFlow.Nodes[rejectNodeid.Value]; 211 return fornode.Id; 212 case NodeRejectType.UnHandled: 213 default: 214 return this.WorkFlow.PreviousId; 215 } 216 } 217 218 }
流程流转代码(主要部分):这段代码是处理流转核心功能,只完成了部分核心功能
1 /// <summary> 2 /// 流程过程流转处理 3 /// </summary> 4 /// <param name="model"></param> 5 /// <returns></returns> 6 public async Task<WorkFlowResult> ProcessTransitionFlowAsync(WorkFlowProcessTransition model) 7 { 8 WorkFlowResult result = new WorkFlowResult(); 9 switch (model.MenuType) 10 { 11 case WorkFlowMenu.Submit: 12 break; 13 case WorkFlowMenu.ReSubmit: 14 result = await ProcessTransitionReSubmitAsync(model); 15 break; 16 case WorkFlowMenu.Agree: 17 result = await ProcessTransitionAgreeAsync(model); 18 break; 19 case WorkFlowMenu.Deprecate: 20 result = await ProcessTransitionDeprecateAsync(model); 21 break; 22 case WorkFlowMenu.Back: 23 result = await ProcessTransitionBackAsync(model); 24 break; 25 case WorkFlowMenu.Stop://刚开始提交,下一个节点未审批情况,流程发起人可以终止 26 result = await ProcessTransitionStopAsync(model); 27 break; 28 case WorkFlowMenu.Cancel: 29 break; 30 case WorkFlowMenu.Throgh: 31 break; 32 case WorkFlowMenu.Assign: 33 break; 34 case WorkFlowMenu.View: 35 break; 36 case WorkFlowMenu.FlowImage: 37 break; 38 case WorkFlowMenu.Approval: 39 break; 40 case WorkFlowMenu.CC: 41 break; 42 case WorkFlowMenu.Suspend: 43 break; 44 case WorkFlowMenu.Resume: 45 break; 46 case WorkFlowMenu.Save: 47 case WorkFlowMenu.Return: 48 default: 49 result = WorkFlowResult.Error("未找到匹配按钮!"); 50 break; 51 } 52 return result; 53 }
如果以定制表单关联流程的方式开发,会遇到一个重要问题:流程状态如何与表单同步?因为工作流与业务流是区分开的,怎么办?
我的做法是(以请假为例):让实体先继承流程状态实体,通过CAP的方式推送和订阅,我以前的公司工作流是通过页面回调的方式实现,我感觉这个很不靠谱,实际上也是经常出问题
流程状态的判断:WfWorkflowInstance实体下的两个字段, 这块可能不太好理解,尤其是没有开发过的朋友,简单解释下:IsFinish 是表示流程运行的状态,Status表示用户操作流程的状态,我们判断这个流程是否结束不能单纯的判断根据IsFinish进行判断,
举个例子(请假):
我提交了一个请假申请==>下个节点审批不同意。你说这个流程有没有结束?当然结束了,只不过它没有审批通过而已。简而言之,IsFinish表示流程流转是否结束,即是否最终到了最后一个结束节点。
1 #region 结合起来判断流程是否结束 2 /* 流转状态判断 实际情况组合 3 * IsFinish=1 & Status=WorkFlowStatus.IsFinish 表示通过 4 * IsFinish==null & Status=WorkFlowStatus.UnSubmit 表示未提交 5 * IsFinish=0 & Status=WorkFlowStatus.Running 表示运行中 6 * IsFinish=0 & Status=WorkFlowStatus.Deprecate 表示不同意 7 * IsFinish=0 & Status=WorkFlowStatus.Back 表示流程被退回 8 * **/ 9 /// <summary> 10 /// 流程节点是否结束 11 /// 注:此字段代表工作流流转过程中运行的状态判断 12 /// </summary> 13 public int? IsFinish { get; set; } 14 15 /// <summary> 16 /// 用户操作状态<see cref="WorkFlowStatus"/> 17 /// 注:此字段代表用户操作流程的状态 18 /// </summary> 19 public int Status { get; set; } 20 21 #endregion
至于页面审批按钮的展示,因为这个功能是公用的,我把它写在了组件里面,共两个菜单组件,一个是定制一个是系统生成,代码稍微有些不同,组件视图代码比较多,就不展示了。
下面走一个不同意的请假流程:
1、wms账号先选择要发起的流程
2、流程发起界面
3、流程提交之后的界面,注:终止:当用户提交表单之后,下个节点未进行审批的时候,流程发起人有权终止(取消流程)
4、wangwu账号登录
5、结果展示
6、审批意见查看
7、流程图查看,绿色节点表示流程当前节点。
8、也可以在OA员工请假看到结果
注:因为工作流引擎不涉及具体的业务逻辑,通常与OA系统进行表单绑定,所以我建了OA服务,并简单写了个请假流程方便测试。工作流依赖于之前的权限系统,如果登录人员显示没有权限,请先进行授权
四、结束
每个程序员刚毕业的时候都有一种我要独立写一个超级牛逼系统的冲动,我也是,都不记得多少年了,断断续续坚持到现在,虽然不算完善,更谈不上多么牛逼,写这两篇算是给自己一个交代吧。如果大家觉得有研究价值的话,我会继续升级迭代。
运行方式参考 上一篇 (末尾)
管理员登录账号wms,密码:所有账号密码都是123
代码地址:
https://github.com/wangmaosheng/MsSystem-BPM-ServiceAndWebApps
如果觉得有点作用的话,可以 start 下,后续会持续更新。
欢迎加微信讨论,共同进步(妹子更好哟@--@)