1. 程式人生 > >.NET Core微服務 許可權系統+工作流(二)工作流系統

.NET Core微服務 許可權系統+工作流(二)工作流系統

一、前言

  接上一篇 .NET Core微服務 許可權系統+工作流(一)許可權系統 ,再來一發

  工作流,我在接觸這塊開發的時候一直好奇它的實現方式,翻看各種工作流引擎程式碼,探究其實現方式,個人總結出來一個核心要點:

    實際上工作流引擎處理流轉的核心要義是如何解析流轉XML或者JSON或者其它持久化方式,工作流通過解析XML或者JSON判斷當前節點的狀態和下個節點的資訊並做出一些處理。感覺等於沒說?直白一點,就是通過解析JSON檔案得到下一步是誰處理。

  工作流的流轉線路實際上是固定死的,排列組合即可知道所有可能的線路,並沒有想象中的那麼難以理解。理解好這點,那麼接下來開發就很簡單了,壘程式碼而已(手動微笑.ing)。本系統著重分析工作流具體的實現方式,不闡述具體的實現步驟,詳細程式碼請看GitHub地址。

二、系統介紹

深入研究過工作流的朋友可能會知道,流程表單它分為兩種:

1、定製表單。更加貼近業務,但會累死開發人員。以前的公司都是這種方式開發,這個和具體的業務邏輯有關係,比較複雜的建議使用定製表單方式,即開發人員把業務功能開發完了,與流程關聯即可。

2、程式碼生成的表單。不需要編寫程式碼,系統可自動生成,方便,但是功能擴充套件性較差。

當然各有好處。本系統兩種方式都已經實現,著重闡述定製流程。本系統人為規定:一個流程只能繫結一個表單,一個表單只能繫結一個流程。即一對一,這是一切的前提。至於為什麼這麼做?

通常情況下一個流程的走向是跟表單邏輯是相掛鉤的,基本上不存在多個的可能性,而且容易造成組織錯亂,有的話,那就在再畫一個流程一個表單。@_^_@

三、工作流實現

還是以面向資料庫的方法來開發,先看錶:

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

 

至於頁面審批按鈕的展示,因為這個功能是公用的,我把它寫在了元件裡面,共兩個選單元件,一個是定製一個是系統生成,程式碼稍微有些不同,元件檢視程式碼比較多,就不展示了。

下面走一個不同意的請假流程:

1、wms賬號先選擇要發起的流程

2、流程發起介面

3、流程提交之後的介面,注:終止:當用戶提交表單之後,下個節點未進行審批的時候,流程發起人有權終止(取消流程)

4、wangwu賬號登入

5、結果展示

6、審批意見檢視

7、流程圖檢視,綠色節點表示流程當前節點。

8、也可以在OA員工請假看到結果

注:因為工作流引擎不涉及具體的業務邏輯,通常與OA系統進行表單繫結,所以我建了OA服務,並簡單寫了個請假流程方便測試。工作流依賴於之前的許可權系統,如果登入人員顯示沒有許可權,請先進行授權

四、結束

  每個程式設計師剛畢業的時候都有一種我要獨立寫一個超級牛逼系統的衝動,我也是,都不記得多少年了,斷斷續續堅持到現在,雖然不算完善,更談不上多麼牛逼,寫這兩篇算是給自己一個交代吧。如果大家覺得有研究價值的話,我會繼續升級迭代。

執行方式參考 上一篇 (末尾)

管理員登入賬號wms,密碼:所有賬號密碼都是123

程式碼地址:

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

如果覺得有點作用的話,可以 start 下,後續會持續更新。

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