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

一、前言

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

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

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

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

二、系统介绍

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

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

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

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

通常情况下一个流程的走向是跟表单逻辑是相挂钩的,基本上不存在多个的可能性,而且容易造成组织错乱,有的话,那就在再画一个流程一个表单。@_^[email protected]

三、工作流实现

还是以面向数据库的方法来开发,先看表:

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 下,后续会持续更新。

欢迎加微信讨论,共同进步(妹子更好哟@[email protected]

原文地址:https://www.cnblogs.com/wms01/p/10940565.html

时间: 2024-08-27 00:22:07

.NET Core微服务 权限系统+工作流(二)工作流系统的相关文章

微服务 权限系统+工作流

一.前言 接上一篇 .NET Core微服务 权限系统+工作流(一)权限系统 ,再来一发 工作流,我在接触这块开发的时候一直好奇它的实现方式,翻看各种工作流引擎代码,探究其实现方式,个人总结出来一个核心要点: 实际上工作流引擎处理流转的核心要义是如何解析流转XML或者JSON或者其它持久化方式,工作流通过解析XML或者JSON判断当前节点的状态和下个节点的信息并做出一些处理.感觉等于没说?直白一点,就是通过解析JSON文件得到下一步是谁处理. 工作流的流转线路实际上是固定死的,排列组合即可知道所

.NET Core微服务二:Ocelot API网关

.NET Core微服务一:Consul服务中心 .NET Core微服务二:Ocelot API网关 .NET Core微服务三:polly熔断与降级 本文的项目代码,在文章结尾处可以下载. 本文使用的环境:Windows10 64位 + VS 2019 + .NET Core 2.1 + Ocelot 8.0.8 Ocelot 相关地址: https://github.com/ThreeMammals/Ocelot https://ocelot.readthedocs.io/en/lates

.NET Core微服务系列基础文章

今年从原来的Team里面被抽出来加入了新的Team,开始做Java微服务的开发工作,接触了Spring Boot, Spring Cloud等技术栈,对微服务这种架构有了一个感性的认识.虽然只做了两个月的开发工作,但是对微服务架构的兴趣却没有结束,又因为自己的.NET背景(虽然对.NET的生态有点恨铁不成钢),想要探索一下在.NET平台下的微服务架构的可行性,也准备一些材料作为分享的素材. 幸运的是,在.NET Core首届在线峰会上,看到了很多前辈的分享,也增强了自己要摸索和实践.NET Co

ASP.NET Core微服务框架Ocelot+Consul+IdentityServer4实战演练

一.背景介绍 API网关的流行源于最近几年移动应用与企业间接口对接的兴起,使得原来单一的PC客户端,变化到PC客户端.各种浏览器.手机移动端及智能终端等.同时系统之间大部分都不是单独运行,经常会涉及与其他系统对接.共享数据的需求.随着微服务架构概念的提出,API网关成为了微服务架构的一个标配组件.随着业务快速发展,面向手机移动应用业务越来越多,为了减少客户端与服务的耦合,节约后端微服务的开发成本,建立一个高性能.高可用.减少上线风险的API网关成为一个迫切的需求. 1).目前面临现状:假设你正好

Spring Cloud下微服务权限方案

背景从传统的单体应用转型Spring Cloud的朋友都在问我,Spring Cloud下的微服务权限怎么管?怎么设计比较合理?从大层面讲叫服务权限,往小处拆分,分别为三块:用户认证.用户权限.服务校验. 用户认证传统的单体应用可能习惯了session的存在,而到了Spring cloud的微服务化后,session虽然可以采取分布式会话来解决,但终究不是上上策.开始有人推行Spring Cloud Security结合很好的OAuth2,后面为了优化OAuth 2中Access Token的存

.Net Core 微服务容器系列基础目录篇

1.开场白 HI,各位老铁,大家端午好,之前写了些关于.net core商城系列的文章,有点乱,今天心血来潮想着整理一下(今天只是先把目录列出来,后面的每篇文章这两天会进行重新修改的,目前先将就看下). 简单介绍一下,博主目前就职于某电商公司,目前工作用的是.net core,业余时间也会看下Java,公司内部目前也是多语言并存,毕竟很多工具和技术对于这两种语言都是相通的,所以多了解下哈. 本系列项目将会以.net core+Docker+K8s来搭建,当然你用java来改写一下也是没问题的,因

【新书推荐】《ASP.NET Core微服务实战:在云环境中开发、测试和部署跨平台服务》 带你走近微服务开发

<ASP.NET Core 微服务实战>译者序:https://blog.jijiechen.com/post/aspnetcore-microservices-preface-by-translator/ "微服务"的概念在 2014 年正式提出之后,越来越多的团队开始用它来设计自己的业务系统,各种微服务框架和开发过程管理方法也同时兴起.不断成熟.微服务设计方法清晰地定义了各个开发团队的业务边界,微服务框架以不同的方式实现了服务之间的协作与集成,根据康威定律我们可以推导这

.NET Core微服务之基于Ocelot实现API网关服务(续)

一.负载均衡与请求缓存 1.1 负载均衡 为了验证负载均衡,这里我们配置了两个Consul Client节点,其中ClientService分别部署于这两个节点内(192.168.80.70与192.168.80.71). 为了更好的展示API Repsonse来自哪个节点,我们更改一下返回值: [Route("api/[controller]")] public class ValuesController : Controller { // GET api/values [Http

.NET Core微服务之基于Consul实现服务治理

一.Consul基础介绍 Consul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置.与其他分布式服务注册与发现的方案,比如 Airbnb的SmartStack等相比,Consul的方案更"一站式",内置了服务注册与发现框 架.分布一致性协议实现.健康检查.Key/Value存储.多数据中心方案,不再需要依赖其他工具(比如ZooKeeper等),使用起来也较 为简单. Consul用Golang实现,因此具有天然可移植性(支持Linux.windows和Ma