一、前言

  接上一篇 .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文件,并写一些方便读取节点、连线的方法

 /// <summary>
/// workflow context
/// </summary>
public class MsWorkFlowContext : WorkFlowContext
{
/// <summary>
/// 构造器传参
/// </summary>
/// <param name="dbworkflow"></param>
public MsWorkFlowContext(WorkFlow dbworkflow)
{
if (dbworkflow.FlowId == default(Guid))
{
throw new ArgumentNullException("FlowId", " input workflow flowid is null");
}
if (dbworkflow.FlowJSON.IsNullOrEmpty())
{
throw new ArgumentException("FlowJSON", "input workflow json is null");
}
if (dbworkflow.ActivityNodeId == null)
{
throw new ArgumentException("ActivityNodeId", "input workflow ActivityNodeId is null");
} this.WorkFlow = dbworkflow; dynamic jsonobj = JsonConvert.DeserializeObject(this.WorkFlow.FlowJSON);
//获取节点
this.WorkFlow.Nodes = this.GetNodes(jsonobj.nodes);
//获取连线
this.WorkFlow.Lines = this.GetFromLines(jsonobj.lines); this.WorkFlow.ActivityNodeId = dbworkflow.ActivityNodeId == default(Guid) ? this.WorkFlow.StartNodeId : dbworkflow.ActivityNodeId; this.WorkFlow.ActivityNodeType = this.GetNodeType(this.WorkFlow.ActivityNodeId); //会签开始节点和流程结束节点没有下一步
if (this.WorkFlow.ActivityNodeType == WorkFlowInstanceNodeType.ChatNode || this.WorkFlow.ActivityNodeType == WorkFlowInstanceNodeType.EndRound)
{
this.WorkFlow.NextNodeId = default(Guid);//未找到节点
this.WorkFlow.NextNodeType = WorkFlowInstanceNodeType.NotRun;
}
else
{
var nodeids = this.GetNextNodeId(this.WorkFlow.ActivityNodeId);
if (nodeids.Count == )
{
this.WorkFlow.NextNodeId = nodeids[];
this.WorkFlow.NextNodeType = this.GetNodeType(this.WorkFlow.NextNodeId);
}
else
{
//多个下个节点情况
this.WorkFlow.NextNodeId = default(Guid);
this.WorkFlow.NextNodeType = WorkFlowInstanceNodeType.NotRun;
}
}
} /// <summary>
/// 下个节点是否是多个
/// </summary>
public bool IsMultipleNextNode { get; set; } /// <summary>
/// 获取节点集合
/// </summary>
/// <param name="nodesobj"></param>
/// <returns></returns>
private Dictionary<Guid, FlowNode> GetNodes(dynamic nodesobj)
{
Dictionary<Guid, FlowNode> nodes = new Dictionary<Guid, FlowNode>(); foreach (JObject item in nodesobj)
{
FlowNode node = item.ToObject<FlowNode>();
if (!nodes.ContainsKey(node.Id))
{
nodes.Add(node.Id, node);
}
if (node.Type == FlowNode.START)
{
this.WorkFlow.StartNodeId = node.Id;
}
}
return nodes;
} /// <summary>
/// 获取工作流节点及以节点为出发点的流程
/// </summary>
/// <param name="linesobj"></param>
/// <returns></returns>
private Dictionary<Guid, List<FlowLine>> GetFromLines(dynamic linesobj)
{
Dictionary<Guid, List<FlowLine>> lines = new Dictionary<Guid, List<FlowLine>>(); foreach (JObject item in linesobj)
{
FlowLine line = item.ToObject<FlowLine>(); if (!lines.ContainsKey(line.From))
{
lines.Add(line.From, new List<FlowLine> { line });
}
else
{
lines[line.From].Add(line);
}
} return lines;
} /// <summary>
/// 获取全部流程线
/// </summary>
/// <returns></returns>
public List<FlowLine> GetAllLines()
{
dynamic jsonobj = JsonConvert.DeserializeObject(this.WorkFlow.FlowJSON);
List<FlowLine> lines = new List<FlowLine>();
foreach (JObject item in jsonobj.lines)
{
FlowLine line = item.ToObject<FlowLine>();
lines.Add(line);
}
return lines;
} /// <summary>
/// 根据节点ID获取From(流入的线条)
/// </summary>
/// <param name="nodeid"></param>
/// <returns></returns>
public List<FlowLine> GetLinesForFrom(Guid nodeid)
{
var lines = GetAllLines().Where(m => m.To == nodeid).ToList();
return lines;
} public List<FlowLine> GetLinesForTo(Guid nodeid)
{
var lines = GetAllLines().Where(m => m.From == nodeid).ToList();
return lines;
} /// <summary>
/// 获取全部节点
/// </summary>
/// <returns></returns>
public List<FlowNode> GetAllNodes()
{
dynamic jsonobj = JsonConvert.DeserializeObject(this.WorkFlow.FlowJSON);
List<FlowNode> nodes = new List<FlowNode>();
foreach (JObject item in jsonobj.nodes)
{
FlowNode node = item.ToObject<FlowNode>();
nodes.Add(node);
}
return nodes;
} /// <summary>
/// 根据节点ID获取节点类型
/// </summary>
/// <param name="nodeId"></param>
/// <returns></returns>
public WorkFlowInstanceNodeType GetNodeType(Guid nodeId)
{
var _thisnode = this.WorkFlow.Nodes[nodeId];
return _thisnode.NodeType();
} /// <summary>
/// 根据节点id获取下个节点id
/// </summary>
/// <param name="nodeId"></param>
/// <returns></returns>
public List<Guid> GetNextNodeId(Guid nodeId)
{
List<FlowLine> lines = this.WorkFlow.Lines[nodeId];
if (lines.Count > )
{
this.IsMultipleNextNode = true;
}
return lines.Select(m => m.To).ToList();
} /// <summary>
/// 节点驳回
/// </summary>
/// <param name="rejectType">驳回节点类型</param>
/// <param name="rejectNodeid">要驳回到的节点</param>
/// <returns></returns>
public Guid RejectNode(NodeRejectType rejectType, Guid? rejectNodeid)
{
switch (rejectType)
{
case NodeRejectType.PreviousStep:
return this.WorkFlow.PreviousId;
case NodeRejectType.FirstStep:
var startNextNodeId = this.GetNextNodeId(this.WorkFlow.StartNodeId).First();
return startNextNodeId;
case NodeRejectType.ForOneStep:
if (rejectNodeid == null || rejectNodeid == default(Guid))
{
throw new Exception("驳回节点没有值!");
}
var fornode = this.WorkFlow.Nodes[rejectNodeid.Value];
return fornode.Id;
case NodeRejectType.UnHandled:
default:
return this.WorkFlow.PreviousId;
}
} }

流程流转代码(主要部分):这段代码是处理流转核心功能,只完成了部分核心功能

         /// <summary>
/// 流程过程流转处理
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public async Task<WorkFlowResult> ProcessTransitionFlowAsync(WorkFlowProcessTransition model)
{
WorkFlowResult result = new WorkFlowResult();
switch (model.MenuType)
{
case WorkFlowMenu.Submit:
break;
case WorkFlowMenu.ReSubmit:
result = await ProcessTransitionReSubmitAsync(model);
break;
case WorkFlowMenu.Agree:
result = await ProcessTransitionAgreeAsync(model);
break;
case WorkFlowMenu.Deprecate:
result = await ProcessTransitionDeprecateAsync(model);
break;
case WorkFlowMenu.Back:
result = await ProcessTransitionBackAsync(model);
break;
case WorkFlowMenu.Stop://刚开始提交,下一个节点未审批情况,流程发起人可以终止
result = await ProcessTransitionStopAsync(model);
break;
case WorkFlowMenu.Cancel:
break;
case WorkFlowMenu.Throgh:
break;
case WorkFlowMenu.Assign:
break;
case WorkFlowMenu.View:
break;
case WorkFlowMenu.FlowImage:
break;
case WorkFlowMenu.Approval:
break;
case WorkFlowMenu.CC:
break;
case WorkFlowMenu.Suspend:
break;
case WorkFlowMenu.Resume:
break;
case WorkFlowMenu.Save:
case WorkFlowMenu.Return:
default:
result = WorkFlowResult.Error("未找到匹配按钮!");
break;
}
return result;
}

如果以定制表单关联流程的方式开发,会遇到一个重要问题:流程状态如何与表单同步?因为工作流与业务流是区分开的,怎么办?

  我的做法是(以请假为例):让实体先继承流程状态实体,通过CAP的方式推送和订阅,我以前的公司工作流是通过页面回调的方式实现,我感觉这个很不靠谱,实际上也是经常出问题

流程状态的判断:WfWorkflowInstance实体下的两个字段, 这块可能不太好理解,尤其是没有开发过的朋友,简单解释下:IsFinish 是表示流程运行的状态,Status表示用户操作流程的状态,我们判断这个流程是否结束不能单纯的判断根据IsFinish进行判断,

举个例子(请假):

  我提交了一个请假申请==>下个节点审批不同意。你说这个流程有没有结束?当然结束了,只不过它没有审批通过而已。简而言之,IsFinish表示流程流转是否结束,即是否最终到了最后一个结束节点。

         #region 结合起来判断流程是否结束
/* 流转状态判断 实际情况组合
* IsFinish=1 & Status=WorkFlowStatus.IsFinish 表示通过
* IsFinish==null & Status=WorkFlowStatus.UnSubmit 表示未提交
* IsFinish=0 & Status=WorkFlowStatus.Running 表示运行中
* IsFinish=0 & Status=WorkFlowStatus.Deprecate 表示不同意
* IsFinish=0 & Status=WorkFlowStatus.Back 表示流程被退回
* **/
/// <summary>
/// 流程节点是否结束
/// 注:此字段代表工作流流转过程中运行的状态判断
/// </summary>
public int? IsFinish { get; set; } /// <summary>
/// 用户操作状态<see cref="WorkFlowStatus"/>
/// 注:此字段代表用户操作流程的状态
/// </summary>
public int Status { get; set; } #endregion

至于页面审批按钮的展示,因为这个功能是公用的,我把它写在了组件里面,共两个菜单组件,一个是定制一个是系统生成,代码稍微有些不同,组件视图代码比较多,就不展示了。

下面走一个不同意的请假流程:

1、wms账号先选择要发起的流程

2、流程发起界面

3、流程提交之后的界面,注:终止:当用户提交表单之后,下个节点未进行审批的时候,流程发起人有权终止(取消流程)

4、wangwu账号登录

5、结果展示

6、审批意见查看

7、流程图查看,绿色节点表示流程当前节点。

8、也可以在OA员工请假看到结果

注:因为工作流引擎不涉及具体的业务逻辑,通常与OA系统进行表单绑定,所以我建了OA服务,并简单写了个请假流程方便测试。工作流依赖于之前的权限系统,如果登录人员显示没有权限,请先进行授权

四、结束

  每个程序员刚毕业的时候都有一种我要独立写一个超级牛逼系统的冲动,我也是,都不记得多少年了,断断续续坚持到现在,虽然不算完善,更谈不上多么牛逼,写这两篇算是给自己一个交代吧。如果大家觉得有研究价值的话,我会继续升级迭代。

运行方式参考 上一篇 (末尾)

管理员登录账号wms,密码:所有账号密码都是123

代码地址:

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

如果觉得有点作用的话,可以 start 下,后续会持续更新。

欢迎加微信讨论,共同进步(妹子更好哟@--@

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

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

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

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

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

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

    原文:NET Core微服务之路:实战SkyWalking+Exceptionless体验生产下追踪系统 前言 当一个APM或一个日志中心实际部署在生产环境中时,是有点力不从心的. 比如如下场景分析的 ...

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

    介绍 系列目录:[NET CORE微服务一条龙应用]开始篇与目录 在微服务的应用中,统一的认证授权是必不可少的组件,本文将介绍微服务中网关和子服务如何使用统一的权限认证 主要介绍内容为: 1.子服务如 ...

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

    .NET Core微服务一:Consul服务中心 .NET Core微服务二:Ocelot API网关 .NET Core微服务三:polly熔断与降级 本文的项目代码,在文章结尾处可以下载. 本文使 ...

  6. .Net Core微服务入门全纪录(二)——Consul-服务注册与发现(上)

    前言 上一篇[.Net Core微服务入门全纪录(一)--项目搭建]讲到要做到服务的灵活伸缩,那么需要有一种机制来实现它,这个机制就是服务注册与发现.当然这也并不是必要的,如果你的服务实例很少,并且很 ...

  7. .NET Core 微服务—API网关(Ocelot) 教程 [二]

    上篇文章(.NET Core 微服务—API网关(Ocelot) 教程 [一])介绍了Ocelot 的相关介绍. 接下来就一起来看如何使用,让它运行起来. 环境准备 为了验证Ocelot 网关效果,我 ...

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

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

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

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

随机推荐

  1. JVM体系结构之二:类加载器

    一.概述 定义:虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型.类加载和连接的过程都是在运行期间完成的. 二. 类的加载 ...

  2. RS485波特率问题

    转载请注明出处:http://blog.csdn.net/qq_26093511/article/details/51683648 最近再做一个项目,发现485不能发送数据,感到非常奇怪!后来查阅相关 ...

  3. Android Studio配置使用git

    一.准备 如果没有安装git,那么先要到到Git官网下载git,然后按照提示一步一步安装即可,这个没有什么难度,不过要记得安装的目录. 二.Android Studio配置git File->S ...

  4. [解决问题]ubuntu无法virtualenv创建python虚拟环境的解决

    刚有人问我Ubuntu python虚拟环境无法创建问题,报错same file error,防止今后遇到忘记,记录下可能的问题. 1.先在windows上试了下: pip install virtu ...

  5. UCSC数据库数据调用cruzdb

    https://github.com/Wy2160640/cruzdb UCSC基因组数据库是注释,调节和变异以及越来越多的分类群的各种数据的重要资源. 该库旨在简化数据的利用,以便我们可以进行复杂的 ...

  6. 【jeasyui5】样式:调整页面显示的顶部菜单和左侧菜单

    1.顶部菜单修改:修改index2.js里面的InitTopMenu方法,将icon +2 2.左侧菜单宽度调整: 修改index.html,加上width:170的定长 <!-- 左侧菜单 - ...

  7. 数据库 连接(join)

    转自http://www.cnblogs.com/caozengling/p/5318696.html 数据库中飞内连接,自然连接,外连接 数据库中的连接join氛围内连接,自然连接,外连接,外连接又 ...

  8. 1.4 如何防止sql注入

    如何防止sql注入   1.检查变量数据类型和格式 如果你的SQL语句是类似where id={$id}这种形式,数据库里所有的id都是数字,那么就应该在SQL被执行前,检查确保变量id是int类型: ...

  9. Multi-task Pose-Invariant Face Recognition 论文笔记

    摘要: 在不受限制的环境中拍摄的人脸图像通常包含显著的姿态变化,这会显著降低设计用于识别正面的算法的性能.本文提出了一种新颖的面部识别框架,能够处理±90°偏航范围内的全方位姿势变化.所提出的框架首先 ...

  10. elasticsearch 增删改流程和写一致性

    增删改流程: 1. 客户端和任一节点(假设 Node1)发出请求,这个node就是coordinating node(协调节点) 2. coordinating node,对document进行路由, ...