.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 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微服务 权限系统+工作流(二)工作流系统相关推荐
- .NET Core微服务 权限系统+工作流(一)权限系统
一.前言 实际上权限系统老早之前我就在一直开发,大概在刚毕业没多久就想一个人写一个系统,断断续续一直坚持到现在,毕竟自己亲动手自写的系统才有收获,本篇仅介绍权限. 小小系统上不了台面,望各位大神勿喷. ...
- 微服务权限控制(二)共享Session方式的登录认证
接上一篇的权限控制,再讨论再网关zuul的登录认证实现. 网关使用SpringCloud的zuul,登录认证选择使用自定义共享session的方式,来实现集群的登录验证.保护接口的私密,保证系统安全. ...
- NET Core微服务之路:实战SkyWalking+Exceptionless体验生产下追踪系统
前言 当一个APM或一个日志中心实际部署在生产环境中时,是有点力不从心的. 比如如下场景分析的问题: 从APM上说,知道某个节点出现异常,或延迟过过高,却不能及时知道日志反馈情况,总不可能去相应的节点 ...
- 【NET CORE微服务一条龙应用】第三章 认证授权与动态权限配置
[NET CORE微服务一条龙应用]第三章 认证授权与动态权限配置 介绍 系列目录:[NET CORE微服务一条龙应用]开始篇与目录 在微服务的应用中,统一的认证授权是必不可少的组件,本文将介绍微服务 ...
- ASP.NET Core微服务(二)——【ASP.NET Core Swagger配置】
ASP.NET Core微服务(二)--[ASP.NET Core Swagger配置]: 环境:win10专业版+vs2019+sqlserver2014/2019 ASP.NET Core微服务( ...
- .Net Core微服务入门——Ocelot API网关接入(二)
Net Core微服务入门--Ocelot API网关接入(二) 我们先接入Consul,实现服务发现 服务发现 1.引入 Ocelot.Provider.Consul 包 2.修改ocelot.js ...
- springcloud 微服务鉴权_springcloud 微服务权限校验JWT模式获取 token 实战(十二)...
springcloud 微服务权限校验JWT模式获取 token 实战(十二) springcloud 微服务权限校验JWT模式获取 token 实战(十二) JWT:json web token 是 ...
- .NET Core微服务之基于Ocelot+IdentityServer实现统一验证与授权
Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.案例结构总览 这里,假设我们有两个客户端(一个Web网站,一个移动App),他们要使用系统,需要通过API网关(这里API网关始终作为 ...
- .NET Core微服务系列基础文章索引(目录导航Final版)
一.为啥要总结和收集这个系列? 今年从原来的Team里面被抽出来加入了新的Team,开始做Java微服务的开发工作,接触了Spring Boot, Spring Cloud等技术栈,对微服务这种架构有 ...
最新文章
- vue中使用MD5加密
- ThinkPHP5整合LayUI编辑器图片上传
- 电脑配置清单_2020电脑配置清单AMD指南
- Boost:异步操作,需要boost :: asio :: async_initiate函数的测试程序
- dede 验证码不显示 vdimgck.php,Dede后台验证码不显示解决方法详解(dedecms 5.7)
- 使用Apache Flume抓取数据(1)
- 测试回收站测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站2测试回收站
- visual studio 2017 显示行号
- cmake could not find openssl_使用CMake构建C++项目
- 01Hypertext Preprocessor
- Hadoop2.4.1(QJM HA)+HBASE0.98 双MASTER问题分析
- 国家代号(CountryCode) - 常用国家地区代码和国际电话代码
- Opencv3.2各个模块功能详细简介(包括与Opencv2.4的区别)
- OpenGL ES 之uniform和varying
- 中南大学计算机学院研究生录取分数线,中南大学研究生录取分数线
- 1、cell 内容最大长度 The maximum length of cell contents (text) is 32767 characters
- TKK: 更新 TKK 失败,请检查网络连接(亲测有效)
- linux操作的进程调度没有采用,Linux进程调度分析
- vue+springboot实现调用本地摄像头拍照上传后端使用百度ocr识别身份证信息
- 数控编程软件可模拟刀具在三维曲面上的实时加工过程