一、前言

  接上一篇 .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                 else107                 {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获取下个节点id177         /// </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 下,后续会持续更新。

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

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 

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

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

    一.前言 实际上权限系统老早之前我就在一直开发,大概在刚毕业没多久就想一个人写一个系统,断断续续一直坚持到现在,毕竟自己亲动手自写的系统才有收获,本篇仅介绍权限. 小小系统上不了台面,望各位大神勿喷. ...

  2. 微服务权限控制(二)共享Session方式的登录认证

    接上一篇的权限控制,再讨论再网关zuul的登录认证实现. 网关使用SpringCloud的zuul,登录认证选择使用自定义共享session的方式,来实现集群的登录验证.保护接口的私密,保证系统安全. ...

  3. NET Core微服务之路:实战SkyWalking+Exceptionless体验生产下追踪系统

    前言 当一个APM或一个日志中心实际部署在生产环境中时,是有点力不从心的. 比如如下场景分析的问题: 从APM上说,知道某个节点出现异常,或延迟过过高,却不能及时知道日志反馈情况,总不可能去相应的节点 ...

  4. 【NET CORE微服务一条龙应用】第三章 认证授权与动态权限配置

    [NET CORE微服务一条龙应用]第三章 认证授权与动态权限配置 介绍 系列目录:[NET CORE微服务一条龙应用]开始篇与目录 在微服务的应用中,统一的认证授权是必不可少的组件,本文将介绍微服务 ...

  5. ASP.NET Core微服务(二)——【ASP.NET Core Swagger配置】

    ASP.NET Core微服务(二)--[ASP.NET Core Swagger配置]: 环境:win10专业版+vs2019+sqlserver2014/2019 ASP.NET Core微服务( ...

  6. .Net Core微服务入门——Ocelot API网关接入(二)

    Net Core微服务入门--Ocelot API网关接入(二) 我们先接入Consul,实现服务发现 服务发现 1.引入 Ocelot.Provider.Consul 包 2.修改ocelot.js ...

  7. springcloud 微服务鉴权_springcloud 微服务权限校验JWT模式获取 token 实战(十二)...

    springcloud 微服务权限校验JWT模式获取 token 实战(十二) springcloud 微服务权限校验JWT模式获取 token 实战(十二) JWT:json web token 是 ...

  8. .NET Core微服务之基于Ocelot+IdentityServer实现统一验证与授权

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.案例结构总览 这里,假设我们有两个客户端(一个Web网站,一个移动App),他们要使用系统,需要通过API网关(这里API网关始终作为 ...

  9. .NET Core微服务系列基础文章索引(目录导航Final版)

    一.为啥要总结和收集这个系列? 今年从原来的Team里面被抽出来加入了新的Team,开始做Java微服务的开发工作,接触了Spring Boot, Spring Cloud等技术栈,对微服务这种架构有 ...

最新文章

  1. vue中使用MD5加密
  2. ThinkPHP5整合LayUI编辑器图片上传
  3. 电脑配置清单_2020电脑配置清单AMD指南
  4. Boost:异步操作,需要boost :: asio :: async_initiate函数的测试程序
  5. dede 验证码不显示 vdimgck.php,Dede后台验证码不显示解决方法详解(dedecms 5.7)
  6. 使用Apache Flume抓取数据(1)
  7. 测试回收站测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站
  8. visual studio 2017 显示行号
  9. cmake could not find openssl_使用CMake构建C++项目
  10. 01Hypertext Preprocessor
  11. Hadoop2.4.1(QJM HA)+HBASE0.98 双MASTER问题分析
  12. 国家代号(CountryCode) - 常用国家地区代码和国际电话代码
  13. Opencv3.2各个模块功能详细简介(包括与Opencv2.4的区别)
  14. OpenGL ES 之uniform和varying
  15. 中南大学计算机学院研究生录取分数线,中南大学研究生录取分数线
  16. 1、cell 内容最大长度 The maximum length of cell contents (text) is 32767 characters
  17. TKK: 更新 TKK 失败,请检查网络连接(亲测有效)
  18. linux操作的进程调度没有采用,Linux进程调度分析
  19. vue+springboot实现调用本地摄像头拍照上传后端使用百度ocr识别身份证信息
  20. 数控编程软件可模拟刀具在三维曲面上的实时加工过程

热门文章

  1. 保存页面供以后使用Firefox的阅读列表扩展
  2. IT:如何在Windows Server 2008 R2上安装Hyper-V虚拟化
  3. 三阶魔方魔方公式_观看此魔方的自我解决
  4. ea 备份码是什么_EA的原始访问是什么,值得吗?
  5. 亚马逊的vps多少钱一个月_如何查看您在亚马逊上花了多少钱
  6. Vue源码解析之数组变异
  7. 操作系统与多核处理器
  8. linux 学习笔记 显示压缩文件 gong.zip 的文件内容
  9. 以软件推动工业进步 -嵌入式学习网站
  10. 基于事件驱动架构构建微服务第3部分:Presenters, Views和Controllers