.NET Core微服务 权限系统+工做流(二)工做流系统

1、前言

  接上一篇 .NET Core微服务 权限系统+工做流(一)权限系统 ,再来一发html

  工做流,我在接触这块开发的时候一直好奇它的实现方式,翻看各类工做流引擎代码,探究其实现方式,我的总结出来一个核心要点:node

    实际上工做流引擎处理流转的核心要义是如何解析流转XML或者JSON或者其它持久化方式,工做流经过解析XML或者JSON判断当前节点的状态和下个节点的信息并作出一些处理。感受等于没说?直白一点,就是经过解析JSON文件获得下一步是谁处理。git

  工做流的流转线路其实是固定死的,排列组合便可知道全部可能的线路,并无想象中的那么难以理解。理解好这点,那么接下来开发就很简单了,垒代码而已(手动微笑.ing)。本系统着重分析工做流具体的实现方式,不阐述具体的实现步骤,详细代码请看GitHub地址。程序员

2、系统介绍

深刻研究过工做流的朋友可能会知道,流程表单它分为两种:github

一、定制表单。更加贴近业务,但会累死开发人员。之前的公司都是这种方式开发,这个和具体的业务逻辑有关系,比较复杂的建议使用定制表单方式,即开发人员把业务功能开发完了,与流程关联便可。数据库

二、代码生成的表单。不须要编写代码,系统可自动生成,方便,可是功能扩展性较差。json

固然各有好处。本系统两种方式都已经实现,着重阐述定制流程。本系统人为规定:一个流程只能绑定一个表单,一个表单只能绑定一个流程。即一对一,这是一切的前提。至于为何这么作?微信

一般状况下一个流程的走向是跟表单逻辑是相挂钩的,基本上不存在多个的可能性,并且容易形成组织错乱,有的话,那就在再画一个流程一个表单。@_^_@async

3、工做流实现

仍是以面向数据库的方法来开发,先看表:微服务

wf_workflow : 工做流表,存放工做流基本信息

wf_workflow_category : 流程分类表

wf_workflow_form : 流程表单表,分为两种类型,系统生成表单和系统定制表单,系统定制表单只存放URL地址

wf_workflow_instance : 流程实例表,核心

wf_workflow_instance_form : 流程实例表单关联表

wf_workflow_line : 流程连线表。目前之存放两种相反的形式(赞成、不一样意),后期会添加自定义SQL判断业务逻辑流转节点

wf_workflow_operation_history : 流程操做历史表。用于获取审批意见等

wf_workflow_transition_history : 流程流转记录。用于获取 退回某一步获取节点等。

目前工做流实现了这几个功能:保存、提交、赞成、不一样意、退回、终止、流程图、审批意见,后期会继续升级迭代,如添加会签、挂起、通知等等,目前这几个功能应该能应付通常业务需求了,像会签这种功能99%用不到,可是确是比较复杂的功能,涉及并行、串行计算方式,80%时间都花在这些用不到的功能上来,所谓的二八法则吧。

所有功能较多,不一一列举了:目前只有流程分类功能没实现,后续再写吧,可是不影响功能使用,只是用于筛选而已

流程设计界面:采用GooFlow插件,并对其代码作出一些修改,界面确实比较难看,设计比较简陋,毕竟本人不会平面设计,若是以为不丑,就当我没说。

核心代码:实际上就是解析JSON文件,并写一些方便读取节点、连线的方法

 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

 

至于页面审批按钮的展现,由于这个功能是公用的,我把它写在了组件里面,共两个菜单组件,一个是定制一个是系统生成,代码稍微有些不一样,组件视图代码比较多,就不展现了。

下面走一个不一样意的请假流程:

一、wms帐号先选择要发起的流程

二、流程发起界面

三、流程提交以后的界面,注:终止:当用户提交表单以后,下个节点未进行审批的时候,流程发起人有权终止(取消流程)

四、wangwu帐号登陆

五、结果展现

六、审批意见查看

七、流程图查看,绿色节点表示流程当前节点。

八、也能够在OA员工请假看到结果

注:由于工做流引擎不涉及具体的业务逻辑,一般与OA系统进行表单绑定,因此我建了OA服务,并简单写了个请假流程方便测试。工做流依赖于以前的权限系统,若是登陆人员显示没有权限,请先进行受权

4、结束

  每一个程序员刚毕业的时候都有一种我要独立写一个超级牛逼系统的冲动,我也是,都不记得多少年了,断断续续坚持到如今,虽然不算完善,更谈不上多么牛逼,写这两篇算是给本身一个交代吧。若是你们以为有研究价值的话,我会继续升级迭代。

运行方式参考 上一篇 (末尾)

管理员登陆帐号wms,密码:全部帐号密码都是123

代码地址:

https://github.com/wangmaosheng/MsSystem-BPM-ServiceAndWebApps

若是以为有点做用的话,能够 start 下,后续会持续更新。

欢迎加微信讨论,共同进步(妹子更好哟@--@

原文出处:https://www.cnblogs.com/wms01/p/10940565.html

相关文章
相关标签/搜索