接上一篇 .NET Core微服務 權限系統+工做流(一)權限系統 ,再來一發html
工做流,我在接觸這塊開發的時候一直好奇它的實現方式,翻看各類工做流引擎代碼,探究其實現方式,我的總結出來一個核心要點:node
實際上工做流引擎處理流轉的核心要義是如何解析流轉XML或者JSON或者其它持久化方式,工做流經過解析XML或者JSON判斷當前節點的狀態和下個節點的信息並作出一些處理。感受等於沒說?直白一點,就是經過解析JSON文件獲得下一步是誰處理。git
工做流的流轉線路其實是固定死的,排列組合便可知道全部可能的線路,並無想象中的那麼難以理解。理解好這點,那麼接下來開發就很簡單了,壘代碼而已(手動微笑.ing)。本系統着重分析工做流具體的實現方式,不闡述具體的實現步驟,詳細代碼請看GitHub地址。程序員
深刻研究過工做流的朋友可能會知道,流程表單它分爲兩種:github
一、定製表單。更加貼近業務,但會累死開發人員。之前的公司都是這種方式開發,這個和具體的業務邏輯有關係,比較複雜的建議使用定製表單方式,即開發人員把業務功能開發完了,與流程關聯便可。數據庫
二、代碼生成的表單。不須要編寫代碼,系統可自動生成,方便,可是功能擴展性較差。json
固然各有好處。本系統兩種方式都已經實現,着重闡述定製流程。本系統人爲規定:一個流程只能綁定一個表單,一個表單只能綁定一個流程。即一對一,這是一切的前提。至於爲何這麼作?微信
一般狀況下一個流程的走向是跟表單邏輯是相掛鉤的,基本上不存在多個的可能性,並且容易形成組織錯亂,有的話,那就在再畫一個流程一個表單。@_^_@async
仍是以面向數據庫的方法來開發,先看錶:微服務
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服務,並簡單寫了個請假流程方便測試。工做流依賴於以前的權限系統,若是登陸人員顯示沒有權限,請先進行受權
每一個程序員剛畢業的時候都有一種我要獨立寫一個超級牛逼系統的衝動,我也是,都不記得多少年了,斷斷續續堅持到如今,雖然不算完善,更談不上多麼牛逼,寫這兩篇算是給本身一個交代吧。若是你們以爲有研究價值的話,我會繼續升級迭代。
運行方式參考 上一篇 (末尾)
管理員登陸帳號wms,密碼:全部帳號密碼都是123
代碼地址:
https://github.com/wangmaosheng/MsSystem-BPM-ServiceAndWebApps
若是以爲有點做用的話,能夠 start 下,後續會持續更新。
歡迎加微信討論,共同進步(妹子更好喲@--@)