.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 下,後續會持續更新。

歡迎加微信討論,共同進步(妹子更好喲@--@

相關文章
相關標籤/搜索