# 工作流引擎 **Repository Path**: elwinwang/workflow-engine ## Basic Information - **Project Name**: 工作流引擎 - **Description**: 该流程引擎完全开源免费,致力于打造与平台组织架构无关、高扩展的工作流引擎。 通过自定义用户选择器和条件处理器实现既有业务的组织架构关联和审批过程处理。 - **Primary Language**: Unknown - **License**: LGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 73 - **Created**: 2023-08-14 - **Last Updated**: 2023-08-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 演示地址 ***演示环境为dev分支打包*** http://39.101.74.14:8083/index.html#/workflow/list ## QQ交流群 :558038638 ## 简介 该流程引擎完全开源免费,致力于打造与平台组织架构无关、高扩展的工作流引擎。 通过自定义用户选择器和条件处理器实现既有业务的组织架构关联和审批过程处理。 * PS:前台流程引擎用的是 https://gitee.com/xiaoka2017/easy-flow ,相关应用api需自行前往查看 ## 项目特点 工作流项目有很多,各有特点,而本项目的特点就是解耦。将审批过程中的条件选择与人员派发抽象出来,用户可以根据自身的组织架构特点进行自定义人员与条件的配置,而无需更改逻辑代码。只需要简单的实现条件和人员选择自定义接口即可。 (目前还未实现插件,后续会增加插件式扩展) ## 项目运行 在下载完源码之后,通过几个简单的操作即可运行起项目: - 在 Startup 指定启动需要的仓储类型。默认可选 Default 和 EF。 Default 是文本存取实现的仓储,无需建库,直接运行体验。 ``` services.AddWorkFlowCoreFramework(options=> { options.OrmType = WorkFlowCoreFrameworkService.FrameworkConfigOrmType.EF; }); ``` - 如果选择的是 EF : - 配置数据库字符串链接。如果是Mysql,还需要配置上 版本。建议 8.0 以上。 ``` "ConnectionStrings": { "Default": "Database=Workflow;Data Source=datasource;Port=3306;UserId=root;Password=123456;Charset=utf8;TreatTinyAsBoolean=false;Allow User Variables=True", "DefaultVersion": "8.0.26" } ``` - 设置 Host 为启动项。 - 初始化迁移脚本(如果数据库不是Mysql,默认是Mysql则不需要这一步)。 - 删除 WorkFlowCore.Framework 下的 Migrations 文件夹。 - 通过 vs 的 工具=>NuGet包管理器=>程序包管理器控制台 ,默认项目 选择 WorkFlowCore.Framework,输入 add-migration init 命令回车 - 初始化数据表。通过 vs 的 工具=>NuGet包管理器=>程序包管理器控制台 ,默认项目 选择 WorkFlowCore.Framework,输入 update-database 命令回车 ## 使用介绍 ### 业务模型 - 子域以及子域间的上下游关系 ![子域间关系](/WorkFlowCore/ReadmeImges/子域关系图.png ) - UserSelector 负责处理审批过程用户的选择,出现在流程节点 - Condtion 负责处理流程的走向,出现在流程连线 - 流程设计:一个流程设计可以有多个版本,随着业务的变化进行变更时,确保旧的流程正常流转。 - 流程实例:流程实例就是一个个发起的流程审批 - 工作流领域和业务组织领域的关系 - ![与外部系统关系](/WorkFlowCore/ReadmeImges/流程系统和业务系统关系.png ) - 流程引擎依赖业务系统的业务数据、组织 结构数据。 ### 源码结构 在介绍使用之前,简单介绍下代码结构。 整个项目包含四个部分: - 1.WorkFlowCore 是领域核心,所有的业务逻辑在这里面封装交互。在这个部分里,有几个模块: - Authorization 身份认证,里面是获取当前用户信息的抽象接口。因为项目是独立与用户的其它系统,当进行接口(或其它形式)访问时,需要获取当前登录信息时,就需要通过身份接口获取。用户根据实际情况做具体的实现。 - EventBus 事件总线,默认实现进程事件与分布式事件消息(Kafka)。事件总线用于发布和订阅事件。 - EventData 事件消息模型(发布者发布的数据对象) - IRepositories 抽象仓储。在系统设计时,考虑到不同的使用场景下,具体的数据库是不确定的,所以引入仓储,便于扩展。(默认提供了EF 的扩展) - **UserSelectors** 用户选择器。特色模块之一。为用户选择的抽象管理。实际使用时,是需要实现相应的用户选择接口接口扩展用户选择器。后面会详细介绍。 - **Conditions** 条件处理器。特色模块之一。通过自定义条件处理器,可以为用户提供丰富的处理选项和逻辑。后面会详细介绍。 - Workflows 流程设计模块,后面会介绍。 - WorkTasks 流程实例模块,后面会介绍。 - WorkFlowCore.Framework 领域核心抽象的实现。在这里实现具体用仓储用哪个数据库,有什么用户选择器和条件处理器等待 - WorkFlowCore.Host 接口 - WorkFlowCore.Test 单元测试。 ### 使用流程简介 流程引擎单独运行没有什么意义,一般需要结合具体的组织架构和业务进行应用。所以需要根据场景与组织系统进行对接。本系统设计初衷就是为了将引擎核心与业务分离,只需要简单的接口实现既可以跟既有组织架构关联。 - 实现自定义仓储。如果需要使用其它的orm,则需要实现自己的数据库仓储。在实现数据库仓储时,应相应的实现该仓储的工作单元。总之参考默认实现,实现一套自己的仓储。 - 自定义用户选择器。通过实现 IUserSelector 接口实现自己的用户选择器。 ``` [UserSelector("按用户选择","从所有用户选择")] public class UserSelectorB : IUserSelector { public List GetSelections() { return UserList.Users.Select(u => new Selection { Id = u.Id, Name = u.Name }).ToList(); } public List GetUsers(SelectorInput input) { var result = new List(); switch (input.SelectionId) { default: result.Add(new User { Id = input.SelectionId, Name = UserList.GetUserById(input.SelectionId).Name }); break; } return result; } } ``` 用户选择器说明: 1. UserSelector 特性。该特性对选择器做指定一个名称和描述,用于在前端显示。如图所示: 2. 类名全程将作为选择器唯一标识。 3. GetSelections 方法 指定这个类型的选择器有哪些选项。选择可以是多种多样的。比如作为角色选择器时,选项返回角色列表。 4. GetUsers 通过将通过选项标识获取实际的用户列表返回。比如角色选择器传入一个角色,将返回这个角色的所有成员。 - 自定义条件处理器。通过实现 ICondition 接口,可自定义条件处理器。条件处理时,将通过工作流表单信息、当前审批人信息、以及条件参数等信息进行判断是否满足某当前处理器所指定的条件。只需要返回是否满足。 ``` [Condition("条件处理器A")] public class ConditionA : ICondition { public bool CanAccept(ConditionInput input) { try { //简单的表达式解析 var keyvalue = input.Expression.Split('='); JObject jObject = JObject.Parse(input.WorkTask.FormData); var token = jObject.SelectToken(keyvalue[0]); var value = token.Value(); return value.Equals(keyvalue[1]); } catch (Exception) { return false; } } } ``` 条件处理器说: 1. Condition特性标记。 类似 用户选择器,Condition标记将提供条件名称和描述供前端查看。 2. CanAccept 只有一个布尔返回值,将通过表达式、当前工作流信息、当前审批步骤 等信息去解析表达式,并得出结果。信息来源广,可以实现解析工作流表单信息,也可以实现通过sql或者其它各种形式的数据判断。同时解析手段也可以根据实际情况扩展,比如解析json、xml或者其它非标准结构的数据,都可以通过自定义实现处理器来 解析判断。 - 创建流程设计。新建一个工作流设计,该流程设计包括各个节点、人员、条件 等的配置信息,主要为一下结构: - 节点。审批处理节点,或者说审批步骤。 - 人员选择器。该节点由哪些人来审批或抄送给谁。 - 线条。线条用于连接节点。有了线条串起各个节点,才能形成一个通路。 - 条件处理器。线条配置条件处理器就可以在处理时判断能去到哪些节点。 - 创建工作流实例。设计好流程步骤后。就可以创建相应的审批实例,接着进行审批各项操作。 ### 部署方式 当前流程引擎有自己完整的一套工作方式,并不适合进行拆解源码融合到其它系统,最适合的方式就是以分布式的方式进行部署。通过分布式部署的情况下,与业务系统的对接则通过接口进行。 1. 一般情况下,业务系统表单自己维护一个审批状态,业务系统通过调用流程系统接口发起一个审批。也可以自行管理一套表单和状态,只通过本引擎进行流转。 2. 流程系统成功新建一个流程后,将发起一个审批开始的事件,通过订阅该事件进行回调更新业务表单的审批状态。 3. 其它的诸如新建流程设计、流程审批记录等,可通过接口查询,或者自行实现相应的查询接口。 ## 快速上手 ### 创建流程设计 该步骤是设计我们的流程节点该怎么走。 #### ***简单流程*** - 在流程设计页面,点击新增,输入流程名称,点击确定,即可创建一条流程设计记录,新增成功后自动进入到流程设计页面。 ![新增](/WorkFlowCore/ReadmeImges/流程设计-新增.png ) - 也可以通过点击列表的编辑按钮,进入到设计页面。 ![编辑](/WorkFlowCore/ReadmeImges/流程设计-编辑.png ) 在设计页面,左边是可拖拽的节点,中间的节点布局,右边是属性编辑区。 ![新增](/WorkFlowCore/ReadmeImges/流程设计-设计.png ) - 从左边拖拽节点到中间进行布局设计,开始-普通-结束,这是最简单的流程设计。 ![节点拖拽](/WorkFlowCore/ReadmeImges/流程设计-节点拖拽.gif ) - 节点连线,鼠标放到布局区节点的 左边图标,鼠标会变成 十字 ,点击拖拽,放到目标节点,放开鼠标,就自动连接上了。 ![节点拖拽](/WorkFlowCore/ReadmeImges/流程设计-节点连线.gif) - 点击右上角的 保存 按钮,即可保存流程设计。 ![节点拖拽](/WorkFlowCore/ReadmeImges/流程设计-编辑-保存.png) 这样一个简单的流程就完成了。 #### ***有分支的流程*** - 一个节点可以连接到几个下级节点,这样就构成了多分支流程。 ![多分支连接](/WorkFlowCore/ReadmeImges/流程设计-多分支连接.gif) #### ***有会签的流程*** - 多个节点连线到会签节点,实现会签(等待所有人都处理完才到下一步)。 ![会签连接](/WorkFlowCore/ReadmeImges/流程设计-节点会签.gif) #### ***有子流程的流程*** - 使用子流程节点,可以实现等待子流程节点审批完成后,主流程才会到下一个节点(慎用子流程)。子流程节点 和 子节点节点 是配合使用,一个子流程必须用子节点节点做起始节点,子节点用于标记这里子流程的开始节点。一个子流程可以有多个子节点节点,可以通过条件配置指定走哪个子流程(后面会讲条件配置) ![子流程](/WorkFlowCore/ReadmeImges/流程设计-子流程设计.png) 多个子流程分支的情况(当然,这个不建议,如果需要有多个子流程分支,则应该在外层就指定多个子流程节点,在外层先进行判断): ![子流程](/WorkFlowCore/ReadmeImges/流程设计-多个子流程设计.png) ***子流程的逻辑相对麻烦,驳回可能会有问题,如果有此类需求,优先建议将节点都平铺出来,类似这种:*** ![子流程](/WorkFlowCore/ReadmeImges/流程设计-子流程设计-问题.png) ### 节点属性 点击节点,在右侧可以编辑该节点的属性。 ***注意:配置完的节点信息,一定要点击配置面版右下角的 确定 按钮保存,否则配置信息调整无效。*** #### 名称 节点显示名称 #### 审批人员 审批人员的配置确定这个节点由谁来审批。 一个节点可以增加多个配置项。 可以通过自定义人员选择器来增加人员的选择类型(详见扩展点) - 点击 添加人员,会新增一个人员配置项 ![添加人员](/WorkFlowCore/ReadmeImges/流程设计-节点配置-添加人员.png),其中, - 人员类型。人员类型指定了这个人按什么样的方式或者分类获取。比如,按角色,按职位等 - 人员项。分类明细,指定具体的类型,比如,类型按角色,则这个人员项则选择具体的某个角色。 ![人员选择](/WorkFlowCore/ReadmeImges/流程设计-节点配置-添加人员-人员选择.png) - 参数。这个为额外的配置项,具体作用和用法由人员选择器决定。 - 审批、抄送。审批,则该配置项匹配的人员将可以审批,抄送,则该选项匹配的人员只能查看,不能审批。 - 描述。自定义备注。 #### 等待所有人审批 如果勾选了该选项,当匹配到多个人时,将等到所有人都审批完成才会往下走。(有点类似会签,但是会签的等待上游的所有节点审批,但每个节点都有可能匹配到多个人,特别时按职位匹配时) #### 退回节点 退回节点用于手动指定审批驳回时,驳回到哪个节点上。默认情况下,无需指定退回节点,驳回时将自动回溯到上一级的节点。 - 点击 添加节点 增加一个退回节点配置 ![人员选择](/WorkFlowCore/ReadmeImges/流程设计-节点配置-退回节点.png) - 选择节点。指定驳回时要返回的是哪个节点。由于节点处理没那么智能,这里的节点一定要选择其上游的节点,否则流程可能会有问题。 - 回退条件。配置驳回时,验证是否退回到指定的节点。只有满足条件时,才能退回。默认不指定时,将无条件回退到指定的节点。退回条件配置信息: - 条件类型。该选项指定要用什么处理逻辑来做判断。l条件类型可以通过自定义条件处理器扩展(详见扩展点)。 - 参数。提供参数给处理逻辑解析,不同的条件类型需要的参数可能不一样,除此之外,在后台处理时,还会结合表单数据,流程信息等信息进行解析,具体处理方式以条件类型为准。 比如,条件类型 选择 布尔处理器,参数 传 true 则,标识,这个条件是满足的,但这种方式是写死的。 如果使用 表单条件处理器,参数传 value(请假天数)>3(请假天数 为json表单字段),则匹配审批表单的请假天数 大于 3,才会满足。 **再次强调,使用什么参数格式,得看条件类型的规范,** ### 线条属性 点击连接节点的线条,可以配置线条的属性。线条主要配置的是条件处理,其确定了这条线是否走的通。 ***注意:配置完的节点信息,一定要点击配置面版右下角的 确定 按钮保存,否则配置信息调整无效。*** 点击线条,右侧将切换为线条属性配置。 ![线条配置](/WorkFlowCore/ReadmeImges/流程设计-线条配置.png) #### 线条名称 线条名称 将显示在设计图的线条上,通常,如果该线条配置了条件,可以将条件作为名称。 ![条件名称](/WorkFlowCore/ReadmeImges/流程设计-线条配置-条件名称.png) #### 添加条件 通过配置条件,来决定该连线是否能联通。 在节点审批时,将判断下游节点的连线,哪条线连的通就派发到连接的节点。相当于某些流程设计里的判断节点。’ 这里的思路是直接赋予线条条件,连线能不能打通,就看条件满不满足。 ***对于子流程的线条配置,如果一个子流程节点有多个子流程,则应该只能让其满足一条分支,否则流程会有问题*** 参考 节点配置--退回节点--回退条件。 ***附条件处理器的解析基本流程:*** - 数据来源:审批时,对条件进行判断时,有两个来源,一个是一开始填写,发起后就不能更改的 表单数据,一个是在每一个人审批都可以有自己步骤的表单数据 这里称为审批表单(通常不需要,看实际情况)。他们在条件解析 时是通过不同的字段传入的: ![条件名称](/WorkFlowCore/ReadmeImges/流程设计-线条配置-条件数据来源.png) 而条件解析传入的表达式将作为公式解析,用来判断表单的数据是否满足表达式的公式。 ![条件名称](/WorkFlowCore/ReadmeImges/流程设计-线条配置-条件数据来源-表达式.png) - 数据解析。不同的条件处理器解析不同,这里以 Json条件处理器为例(2023-5-1 新增),该处理器支持通过 "\${属性key}" 的方式来解析变量,比如,表单的数据是这样 :"{days:12}",则通过 \${days} 即可取到 12,如果我们的表达式 写成 "\${days}>10",那这个条件就会解析为 12>10 , 显然就会返回 true。具体怎么解析还得看条件处理器怎么支持。 既然数据是通过 属性key 来获取数据,那我们的表单里也要有这个key才行。 如果表单数据是直接在输入框编辑,就直接指定属性名称,比如, ![条件名称](/WorkFlowCore/ReadmeImges/流程设计-线条配置-条件数据来源-表单key.png) 那如果是通过动态表单配置的表单,则一定要手动指定控件的属性名称: ![条件名称](/WorkFlowCore/ReadmeImges/流程设计-线条配置-条件数据来源-动态表单key.png) ### 流程信息 通过点击编辑面版的右上角 流程信息 可以查看当前设计的 json 信息,还可以直接更新 json 信息以实现编辑,通常需要复制一个流程时,可以通过该功能。 ![条件名称](/WorkFlowCore/ReadmeImges/流程设计-编辑-复制编辑.png) ### 保存 点击右上角的保存按钮保存流程设计。 ![保存](/WorkFlowCore/ReadmeImges/流程设计-编辑-保存.png) - 描述。这个是这个流程的描述 - 版本。一个流程设计可以有多个版本,通过多版本控制,可以实现在业务演变过程中,调整流程,并且保证已经审批中的流程正常。 - 版本描述。针对这个版本的描述。 ### 列表功能 ![列表功能](/WorkFlowCore/ReadmeImges/流程设计-列表功能.png) - 查看。快速查看设计图 - 编辑。进入设计页面编辑设计图。 - 版本切换。一个流程设计随着业务变化会有多个版本,该版本切换确定新建的流程使用哪个版本。 - 模拟。该功能提供流程设计的模拟验证,以确保调整的流程是否正常。 - 点击模拟会进入到流程模拟页面 ![模拟](/WorkFlowCore/ReadmeImges/流程设计-列表功能-模拟.png) 在模拟页面,可以手动编辑json表单数据,然后指定人员进行审批。 - 是否模拟。该勾选项如果不勾选,将当作一条正常的流程发起审批。仅为了测试,默认勾选。 - 审批。模拟流程发起后,可以切换当前用户吗,如果下一个步骤是他审批的,将会看到审批表单。 ![列表功能](/WorkFlowCore/ReadmeImges/流程设计-列表功能-模拟-切换-当前用户.png) 审批可以进行直接审批或者转发 ![列表功能](/WorkFlowCore/ReadmeImges/流程设计-列表功能-模拟-审批.png) ![列表功能](/WorkFlowCore/ReadmeImges/流程设计-列表功能-模拟-转发.png) - 表单数据。该参数适用于每次审批可能会更改表单数据的场景。该场景可能发生在作业流控制,即在某些节点可以编辑数据。 - 批语。 - 其它(在模拟页面没有附件等上传,但实际应用过程,可以在不同的审批不同提交附件文件等) - 转发即转给另一个人处理,转发的人员也是通过人员选择器选择,详细参考节点配置的人员选择。 ### 配置流程表单 *这一步不是必须。如果你的系统有自己的表单,这里则用不上。在这里,是为了让演示环境更完整以及方便,才增加了动态表单模块* *流程表单和流程设计不是必须关联的,在demo平台上的表单设计只是提供一种应用的参考方式。可以但不限于。可以理解为,这里的表单只是为了将表单数据进行可视化,可操作化* #### 表单创建 切换到 流程表单 功能菜单,点击新增,创建一个与指定流程设计版本的表单。 ![创建](/WorkFlowCore/ReadmeImges/流程表单-创建.png) #### 表单编辑 新建的表单需要通过列表页,点击编辑进入到编辑页面,在编辑页面编辑表单。 ![创建](/WorkFlowCore/ReadmeImges/流程表单-编辑.png) 与流程设计类型,表单设计也分三个区域,左侧控件区,中间是布局区,右侧是控件属性区。 通过拖拽左边的控件到中间编辑区进行控件布局。 ![推拽](/WorkFlowCore/ReadmeImges/流程表单-编辑-推拽.png) ![布局](/WorkFlowCore/ReadmeImges/流程表单-编辑-布局.png) #### 控件编辑 选中布局区域的控件,通过其下的功能按钮可以进行简单的控件编辑。 ![控件编辑](/WorkFlowCore/ReadmeImges/流程表单-编辑-布局-控件编辑.png) - 删除。从布局区域移除该控件 - 复制。复制一个同样的控件(会生成新的控件id) - 编辑。点击编辑右侧将会出现该控件的属性面版,可直接编辑属性。 ![控件属性](/WorkFlowCore/ReadmeImges/流程表单-编辑-布局-控件属性.png) 不同的控件类型可编辑的控件属性也有所不同。通常有: - 宽度。宽度将表单的宽度分为24 个栅格,选择 6 个栅格表示该控件占据 1/4 的宽度。12 个栅格标识占据了 1/2 的宽度。 - 字段。自动生成的字段名称。可以手动指定(但不可重复)。通常如果需要在流程中解析字段数据时,应该要手动指定字段名称。 - 字段类型。也是控件类型,切换将更改控件样式和可配置的属性。 - 默认值。指定默认值。 #### 几种特殊的控件和属性 - 单、多选。比如 checkbox,select,radiobox 等,对于这些,特别提供了 选项属性,选项属性是一组数组,指定选择值和名称两个属性填写。 ![控件属性](/WorkFlowCore/ReadmeImges/流程表单-编辑-布局-控件属性-选择项.png) - 表格table。表格需要配置每一列的数据,每一列又可以是不同的数据类型,所以表格的属性相当于嵌套了一层控件 ![控件属性](/WorkFlowCore/ReadmeImges/流程表单-编辑-布局-控件属性-表格.png) #### 预览 点击预览可以查看生成的表单效果。 #### 保存 与流程设计类似,流程表单也可以进行版本管理,一个流程设计可以设计多版表单,但最终数据呈现以激活的版本为准。 ![控件属性](/WorkFlowCore/ReadmeImges/流程表单-保存.png) ### 发起审批 在 流程审批--我发起的-新建 ,即可创建和发起审批。 ## API ### 通用 - 响应格式 ``` { "code": "string", 编号 success|fail "msg": "string", 对应编号的 成功|失败 "data": //接口业务 } ``` - 部分标准实体返回: - 这些参数 顾名思义。其中 id 为该实体的数据库标识,有时也作业业务唯一标识。 ``` "modifiedTime": "2023-01-08T02:43:38.640Z", //更新时间 "modifiedUserId": "string", //更新人id "createdUserId": "string", // "creationTime": "2023-01-08T02:43:38.640Z", "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "deletedUserId": "string", "deletedTime": "2023-01-08T02:43:38.640Z", ``` - 接口为 非标准 restful (method 遵循) ### 流程设计相关 在流程设计服务里,主要涉及设计和版本的管理。 设计内容则为流程的设计信息,包括节点,连线,人员,条件以及前端其它设计代码管理。 版本则为流程的版本管理,服务的设计为,一个流程可以有多个版本,随着业务的调整可以不断的调整流程,通过版本管理,可以保证现有流程与新流程区分开来,使旧流程不受新流程影响。 - #### 创建流程(设计)***/api/WorkFlow/CreateWorkFlow*** 这里的创建很简单,这里实际上是将设计信息的创建统一放到更新操作,一来为了简化新增逻辑,统一在更新处理,二来也是配合前端的交互进行的设计。 - name:流程设计名称 - des :描述 该操作返回流程基本信息(这时候设计信息还是为空) ``` { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", //流程id "workflowNo": "string",//流程编号 "name": "string",//流程名称 "activeVersion": 0, //当前使用版本 "description": "string" //描述 ...... } ``` - #### 流程设计更新 ***/api/WorkFlow/UpdateWorkflow*** 该接口用于保存流程设计信息,当流程版本有多个时,每个版本单独存储,每个版本有自己的设计id。 - **name**:流程设计名称 - **des**: 描述 - **drawingInfo**: 前端设计绘制信息,考虑到有些前端流程框架的绘制数据可能比较复杂,为便于回显,可以直接将绘制信息记录起来,需要回显可直接读取 - **versionDescription**: 版本描述。 - **workflowId**: 流程唯一标识。 由于一个流程可以有多个版本,所以唯一标识 由 流程id 和 版本确定 ``` { versionId: 0 //版本号 id: //流程设计id 为 CreateWorkFlow 接口返回的 id } ``` - **workflowNodes**: 流程节点。系统的设计思路为节点负责人员选择,连线负责条件判断。一个节点除了有基本的节点信息,还包括 用户选择器(userSelector 用于在流程流转过程中,解析审批人员)。在这里,节点信息允许括驳回节点指定,即可根据不同的条件判定驳回场景下,应流转到哪个节点(补充一点,在当前系统,不管是拒绝还是驳回,都会构建一条记录信息,而不是“删除”上一步操作,所以每一步都是“下一步”)。 流程节点数据格式如下: ``` [ { id: 3fa85f64-5717-4562-b3fc-2c963f66afa6 //节点唯一标识,应由前端生成 name: string //节点名称 nodeType: 0 //节点类型 drawingInfo: string //节点绘制信息。预留字段,如果节点的绘制数据较复杂,可以考虑直接保存绘制信息,便于回显 isWaitingAllUser: true //是否等待所有用户(处理)。 userSelectors: //用户选择器,指定通过哪个选择器来解析审批人员 [ { selectorId: string // 选择器id selectorName: string //选择器名称 selections: //选项值 [ { id: string //选项值id name: string //选项名称 } ] parameter: string //参数,指定参数将作为选择器使用(参数是否有用,具体看选择器的实现,在后台解析审批人员时,会传递该参数) description: string //描述 handleType: 0 //处理类型。分为 只读(类似 抄送,只能看但不能处理)和处理。 } ] rejectNodes: //驳回(拒绝)回退的节点配置 [ { conditions: //条件 [ { conditionId: string //条件id conditionName: string //条件名称 parameter: string //参数 description: string // 描述 } ] nodeId: 3fa85f64-5717-4562-b3fc-2c963f66afa6 //退回的节点id nodeName: string // 退回的节点名称 } ] } ] ``` 其中: - **nodeType** 节点类型。系统会自动从“开始”节点解析流程。 其中比较特别的类型是“会签”,使用会签节点的效果与 **isWaitingAllUser** 值为 *true* 的类似,但它是需要连接到它的节点审批通过才会进行下一个节点,它等待的不同来源节点审批完成。有以下枚举类型: ``` /// /// 开始 /// Begin, /// /// 结束 /// End, /// /// 普通 /// Judge, /// /// 会签,所有指向该节点的节点都要审批完成后到达 /// Sign ``` - **isWaitingAllUser** : 一个节点可能会分配到多个人审批,通常同一个节点有其中一个人处理后,即可往下一个节点,但是,如果勾选等待所有用户审批,那这个节点就相当会签节点,所有人都通过才会走下一步。 - **userSelectors** : 用户选择器集合。用户选择器用于在流转到节点时,解析目标审批人员。用户选择器信息主要包括几个信息:哪个选择器,选了哪个选项,额外参数,人员处理方式。 - 用户选择器通过 实现 IUserSelector 接口自定义用户选择 ``` [UserSelector("按用户选择","从所有用户选择")] public class UserSelectorB : IUserSelector { //获取选项值 public List GetSelections() { return UserList.Users.Select(u => new Selection { Id = u.Id, Name = u.Name }).ToList(); } //根据选项和参数获取用户(审批人员) public List GetUsers(SelectorInput input) { var result = new List(); switch (input.SelectionId) { default: result.Add(new User { Id = input.SelectionId, Name = UserList.GetUserById(input.SelectionId).Name }); break; } return result; } } ``` - ***/api/WorkFlow/GetAllUserSelectors*** 前端通过该接口获取所有的用户选择器。默认返回的是 实现了 IUserSelector 接口的类型的基本信息,selectorId 为 类型(在文档这里是 UserSelectorB)全称,selectorName 为 UserSelectorAttribute 的第一个参数: ``` [UserSelector("按用户选择","从所有用户选择")] public class UserSelectorB : IUserSelector ``` - 返回选择器集 ``` [ { "id": "string", //选择器id(类名全称) "name": "string", //选择器名称 UserSelectorAttribute 的第一个参数 "description": "string" //选择器描述 UserSelectorAttribute 的第二个参数 } ] ``` - ***/api/WorkFlow/GetUserSelectionsOfUserSelector*** 通过该接口根据用户选择器id获取该选择器的选项 。 - userSelectorId 选择器id。 - 返回选项集 ``` [ { "id": "string", //选项id "name": "string" 选项名称 } ] ``` - **rejectNodes** :拒绝(驳回、回退、回滚)节点配置。通过该配置,根据条件“回到”指定的节点,在必要情况下可以直接退到指定的节点,默认情况下,不配置时,则按原路返回。该配置主要有两项内容:条件,目标节点 - **nodeId、nodeName** 目标节点id 和名称, - **conditions** 条件处理器的配置集合,可配置多个条件,满足其一则跳转到指定节点。条件处理器信息相对简单,主要包含哪个条件处理器以及额外参数。 - 通过实现 ICondition 接口实现自定义条件处理器。 ``` [Condition("条件处理器A")] public class ConditionA : ICondition { public bool CanAccept(ConditionInput input) { try { //简单的表达式解析 var keyvalue = input.Expression.Split('='); JObject jObject = JObject.Parse(input.WorkTask.FormData); var token = jObject.SelectToken(keyvalue[0]); var value = token.Value(); return value.Equals(keyvalue[1]); } catch (Exception) { return false; } } } ``` - ***/api/WorkFlow/GetAllconditions*** 通过该接口可以获取所有的条件处理器信息。 ``` [Condition("条件处理器A")] public class ConditionA : ICondition ``` - 返回基本的条件处理器信息集 ``` [ { "id": "string", //条件处理器id(类名全称) "name": "string", //条件处理器名称 ConditionAttribute 的第一个参数 "description": "string" //条件处理器描述 ConditionAttribute 的第二个参数 } ] ``` - **workflowLines** : 节点连线信息。该属性记录节点间的连线关系(从哪个节点到哪个节点)以及通行(条件是否满足)条件。 - **name**: 条件名称。只用于显示 - **fromNodeId**: 起始节点 - **toNodeId**: 目标节点 - **drawingInfo**: 绘制信息。也是冗余备用。万一需要存储前端设计信息,也便于回显 - **conditions** :条件选择器信息,用于判断改连线流转是否满足条件,满足才能进行(同 用户选择器 rejectNodes 下的 conditions ),相当于一个“判断”+连线。 - ***/api/WorkFlow/UpdateWorkflowActiveVersion*** 用户切换当前激活(应用)的是哪个版本的流程 - **workflowId** :流程id - **activeVersion** :激活版本(versionId) - ***/api/WorkFlow/GetAllWorkflowVersions*** 通过该接口根据流程id获取所有的流程和版本信息(一个流程有多个版本) - **workflowId** 流程id - 返回流程版本集合 ``` [ { "workflowId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", //流程id "versionNo": 0, //流程版本 "description": "string", //版本描述 "modifiedTime": "2023-01-07T13:40:38.791Z", "creationTime": "2023-01-07T13:40:38.791Z" } ] ``` - #### 获取所有的流程(设计)基本信息列表 ***​/api​/WorkFlow​/GetAllWorkflows*** - 返回基本信息列表 ``` [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", //流程id "workflowNo": "string", //流程编号 "name": "string", //流程名称 "activeVersion": 0, //当前应用版本 "description": "string" //描述 } ] ``` - #### 根据流程id和版本号获取详细的流程设计信息 ***/api/WorkFlow/GetWorkflowVersion*** 。 - **versionId** 版本号 - **id** 工作流id - 返回详细信息 ``` { "code": "string", "msg": "string", "data": { "modifiedTime": "2023-01-07T13:48:42.386Z", "modifiedUserId": "string", "createdUserId": "string", "creationTime": "2023-01-07T13:48:42.386Z", "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "deletedUserId": "string", "deletedTime": "2023-01-07T13:48:42.386Z", "deleted": true, "workflowId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "versionNo": 0, "drawingInfo": "string", "description": "string", "nodeMaps": [], //节点映射。连线和节点的映射信息集合。 "allNodes": [] //所有节点 } } ``` 其中 - **nodeMaps** 和 allNodes 数据差不多。对于 allNodes ,类似 接口 ***/api/WorkFlow/UpdateWorkflow*** 的参数。 nodeMaps 是 lines 和 nodes 关联的信息的明细,相当于把 连线line 里的 fromNodeId toNodeId 替换为 fromNode toNode ,对应 allNode 里相同nodeId的 node 节点: ``` [ { "mapType": 0, "fromNode": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "workflowId": { "versionId": 0, "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }, "name": "string", "nodeType": 0, "drawingInfo": "string", "isWaitingAllUser": true, "userSelectors": [ { "selectorId": "string", "selectorName": "string", "selections": [ { "id": "string", "name": "string" } ], "parameter": "string", "description": "string", "handleType": 0 } ], "rejectNodes": [ { "conditions": [ { "conditionId": "string", "conditionName": "string", "parameter": "string", "description": "string" } ], "nodeId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "nodeName": "string" } ] }, "toNode": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "workflowId": { "versionId": 0, "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }, "name": "string", "nodeType": 0, "drawingInfo": "string", "isWaitingAllUser": true, "userSelectors": [ { "selectorId": "string", "selectorName": "string", "selections": [ { "id": "string", "name": "string" } ], "parameter": "string", "description": "string", "handleType": 0 } ], "rejectNodes": [ { "conditions": [ { "conditionId": "string", "conditionName": "string", "parameter": "string", "description": "string" } ], "nodeId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "nodeName": "string" } ] }, "conditions": [ { "conditionId": "string", "conditionName": "string", "parameter": "string", "description": "string" } ] } ] ``` ### 流程审批相关 - #### 新建审批 ***/api/WorkFlow/CreateWorkTask*** - ``` { "workflowId": { "versionId": 0, "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }, "name": "string", "formData": "string", "entityFullName": "string", "entityKeyValue": "string", "createdUserId": "string" } ``` - ***workflowId** :流程id :原始流程id + 版本号。通过接口 ***/api/WorkFlow/GetAllWorkflows*** 获取所有的流程设计。返回流程设计基本信息。workflowId.versionId 对应 GetAllWorkflows 结果集里的 activeVersion。 即这里要创建审批,只需要知道某个流程的以及当前应用的版本(不需要知道所有版本,原则上发起审批的人不需要知道这些)。 - ***name*** 审批名称(标题)。 - ***formData*** 表单数据。这个参数是核心。表单数据可以是平台的任意格式,相应的 **userSelector** 和 **condition** 在处理过程也会传递表单数据共解析和判断。 - ***entityFullName,entityKeyValue*** 外部系统表单类型全称和唯一标识。这两个参数主要是为了便于外部系统自己的分类和查询。在系统表单处需要查询流程信息时,借助这两个数据可以精确查询。 - ***createdUserId*** 创建用户id。为系统平台的用户id,也可以不传,前提是自定义实现 IWorkflowSession 接口解析当前请求的用户信息。 - **IWorkflowSession** 的抽象在 **WorkFlowCore.Authorization** 。在**WorkFlowCore.Framework** 实现自定义的 session,并在 **WorkFlowCoreFrameworkService** 替换现有的注册: ``` services.Replace(new ServiceDescriptor(typeof(IWorkflowSession),typeof(DefaultSession), ServiceLifetime.Scoped)); ``` - 返回基本流程信息: ``` { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "deleted": true, "workflowId": { "versionId": 0, "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }, "name": "string", "formData": "string", "entityFullName": "string", "entityKeyValue": "string", "workTaskStatus": 0, "isProcessed": true, "isPending": true, "isProcessing": true, "isSimulation": true } ``` 其中有几个的数据: - ***workTaskStatus*** 审批状态 ``` { /// /// 待处理 /// Pending, /// /// 处理中 /// Processing, /// /// 已完成 /// Processed } ``` - ***isProcessed/isPending/isProcessing*** 为 **workTaskStatus** 的常用判断值 - ***isSimulation*** 是否是模拟流程。 该属性不在前端传递,而是通过 **创建模拟流程接口** ***/api/WorkFlow/CreateSimulationWorkTask*** 创建的任务。其参数与 **CreateWorkTask** 一致。 - #### 发起审批 ***/api/WorkFlow/StartWorkTask*** - ``` { "worktaskId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" } ``` - ***worktaskId*** 流程审批id。 - 返回发起审批后的处理待审批记录(每一条记录对应 每一个审批用户): ``` [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "workTaskId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "workStepType": 0, "fromNodeId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "fromNodeName": "string", "nodeId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "nodeName": "string", "handleUser": { "id": "string", "name": "string" }, "isHandled": true, "handleType": 0, "comment": "string", "resourceIds": "string", "isRead": true, "readTime": "2023-01-08T02:43:38.627Z", "handlerTime": "2023-01-08T02:43:38.627Z", "groupId": "string", "preStepGroupId": "string", "formData": "string", "fromForwardStepId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" } ] ``` 其中: - ***workStepType*** 处理类型。与 流程设计(***/api/WorkFlow/UpdateWorkflow*** )中,节点 Node 配置中的 handleType对应: 0 //处理类型。分为 只读(类似 抄送,只能看但不能处理)和处理。 ``` /// /// 处理(正常需要处理的情况) /// Handle, /// /// 只读(抄送情况下) /// ReadOnly, ``` - ***fromNodeId,fromNodeName*** 来源节点。即是从哪个节点流转到当前的(新发起的节点来源节点为 开始节点)。 - ***nodeId,nodeName*** 当前审批节点。 - ***handleUser*** 当前审批人。 - ***isHandled** 是否已经处理(**workStepType** 为”处理“的情况) - ***handleType*** 处理方式: ``` { /// /// 通过 /// Pass, /// /// 拒绝 /// Reject, /// /// 撤回 /// Withdraw, /// /// 转发(转给某人审批) /// Forward, /// /// 未处理(在会签节点其它节点驳回,但是部分未处理的则更新为该状态) /// UnWork } ``` - ***handlerTime** 处理时间 - ***comment*** 批文。 - ***resourceIds*** 附件id。这里设计为只记录附件id。每一次审批都可以根据情况上传附件(附件由平台自己管理附件信息)。通常多个附件英文逗号隔开,但具体什么存和解析,由接入平台定义。 - ***isRead,readTime*** 是否已读,以及已读时间。(目前这两个状态仅在执行”撤回(***/api/WorkFlow/WithdrawProve***)“操作时,才标记已读,设计考虑在用户查看审批明细时,也应该标记已读) - ***groupId*** 分组id。分组id 在业务上没意义,在流程控制上便于”撤回“和”驳回“。*设计上,每一次的节点流转都是一个分组,一个节点可能会根据条件流转到一个或者多个节点上,而这些在同一个节点审批流转的审批就归属为同一组*。 - ***preStepGroupId*** 前一组id - ***formData*** 表单数据。这里审批的表单数据不是发起是的表单数据。下面审批接口(***/api/WorkFlow/PassProve***)会提到。每次审批也都可以有自己的表单数据,当存在这些数据并且 **用户选择器** 和 **条件处理器** 都会应用这些数据进行解析时,也会影响到流程的流转。考虑到的这个场景是应用到每次审批都有可能更新表单的情况(具体在审批过程能否编辑数据,这个得看实现情况)。 - ***comment*** 批文。 - ***fromForwardStepId** 来源步骤id。如果是转发才会有值。即标记是哪个审批记录转发过来的。 - #### 通过审批 ***/api/WorkFlow/PassProve*** - ``` { "comment": "string", "stepId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "resourceIds": "string" } ``` - ***comment*** 批文 - ***stepId*** 待审批记录id - ***resourceIds*** 附件id(前面提到过)。 - 返回发起审批后的处理待审批记录(与 发起审批 ***/api/WorkFlow/StartWorkTask*** 接口返回一致): - #### 驳回审批 ***/api/WorkFlow/RejectProve*** (参考发起审批 ***/api/WorkFlow/StartWorkTask***) - #### 撤回审批 ***/api/WorkFlow/WithdrawProve*** (参考发起审批 ***/api/WorkFlow/ StartWorkTask***) - #### 转发审批 ***/api/WorkFlow/RejectProve*** 转发审批为转发当前审批操作转给另一个人进行审批。与其他审批处理操作相似,但多出一个传参。 ``` { "comment": "string", //批文 "stepId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", //待审批记录id "resourceIds": "string", //附件id "userSelectors": [ { "selectorId": "string", "selectorName": "string", "selections": [ { "id": "string", "name": "string" } ], "parameter": "string", "description": "string", "handleType": 0 } ] } ``` - ***userSelectors*** 用户选择器。此处转发操作也采用用户选择器进行解析,一来统一规范,二来可扩展性也 高。参考流程设计接口 (流程设计更新 ***/api/WorkFlow/UpdateWorkflow*** ) - #### 获取所有审批步骤 ***/api/WorkFlow/GetAllTaskStepsOfWorkTask*** - ***worktaskId*** 审批id。 - 返回该审批流所有的审批记录。(与 发起审批 ***/api/WorkFlow/StartWorkTask*** 接口返回一致)。 - #### 获取所有审批步骤(根据entity 信息) ***/api/WorkFlow/ GetAllTaskStepsOfWorkTaskByEntityInfo*** - ***entityFullName*** 外部分类全称。 - ***entityKeyValue*** 外部表单标识。 - (参考 新建审批 ***/api/WorkFlow/CreateWorkTask*** 里的) - #### 分页获取用户待处理审批 ***/api/WorkFlow/GetUnHandledWorkTasksOfUser*** - ***CurrentPage*** 当前页(默认从1开始) - ***MaxResultCount*** 分页大小 - 返回流程审批集合.(属性参考 **新建审批** ***/api/WorkFlow/CreateWorkTask***) - #### 分页获取用户已处理审批 ***/api/WorkFlow/GetHandledWorkTasksOfUser*** 参考 **分页获取用户待处理审批** ***/api/WorkFlow/GetUnHandledWorkTasksOfUser*** - 这里的已处理包括自己发起的,也包括别人发起经由自己审批的。 - #### 分页根据entity信息获取处理中的审批 ***​/api​/WorkFlow​/ GetAllProcessingWorkTasksByEntityType*** - ***entityFullName*** 外部分类全称。 - ***entityKeyValues*** 外部表单标识,多个之间通过英文逗号隔开。 - 返回流程审批集合.(属性参考 **新建审批** ***/api/WorkFlow/CreateWorkTask***) - #### 分页获取用发起的审批 ***/api/WorkFlow/GetWorkTasksOfCreator*** - ***CurrentPage*** 当前页(默认从1开始) - ***MaxResultCount*** 分页大小 - 返回流程审批集合.(属性参考 **新建审批** ***/api/WorkFlow/CreateWorkTask***) - #### 接口现状 - 目前已实现的接口只能保证基本的流程运作,实际上应用过程可能还有更多的场景没考虑到。由于能力有限,不可能做到面面俱到,特别是对于查询接口,这就需要根据业务自己实现。 ## 扩展点 ### 用户选择器 通过自定义实现 IUserSelector 接口实现所需的用户选择器。 - 实现插件(具体参考 Plugins/UserSelectors/UserSelector_PluginDemo) - 新建自定义类库 - 引入项目依赖 WorkFlowCore,或者手动引入dll (WorkFlowCore.Common.dll,WorkFlowCore.dll) - 实现自定义逻辑。并创建对应的 manifest.json ``` { "Entry": "Condition_PluginDemo.dll", //入口文件dll "Version": "1.0" //版本号(目前没应用到,预留) } ``` - 编译生成。并将生成的文件按如下结构按如下格式复制到 Host发布目录下 Plugins\UserSelectors ![子域间关系](/WorkFlowCore/ReadmeImges/用户选择器插件目录.png ) - 重启(启动) Host 服务 - 目前插件的自定义实现类暂时还无法通过ioc 注入参数。除非插件自己维护 ServiceProvider。 ### 条件处理器 通过自定义实现 ICondition 接口实现所需的用户选择器。 - 实现插件(具体参考 Plugins/Conditions/UserSelector_PluginDemo) - 其他步骤类似用户选择器。只是生成后的插件导入到Host 的路径为 Plugins\Conditions - ### 自定义session 实现 - 自定义实现 IWorkflowSession 接口解析当前请求的用户信息。 - **IWorkflowSession** 的抽象在 **WorkFlowCore.Authorization** 。在**WorkFlowCore.Framework** 实现自定义的 session,并在 **WorkFlowCoreFrameworkService** 替换现有的注册: ``` services.Replace(new ServiceDescriptor(typeof(IWorkflowSession),typeof(DefaultSession), ServiceLifetime.Scoped)); ```