diff --git a/README.md b/README.md index b738b7664387406ac98b5205dac5f0b35a5554b7..3f32806a123f486432908d16a526943f434bfca5 100644 --- a/README.md +++ b/README.md @@ -167,3 +167,749 @@ https://gitee.com/mengtree/workflow-engine/tree/sample/ 3. 其它的诸如新建流程设计、流程审批记录等,可通过接口查询,或者自行实现相应的查询接口。 +## 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)); + ``` + +### 切换数据库读写工具 +- 目前默认使用 EF 进行数据库读写。参考(***项目运行***切换)。 +- 如需要实现其他数据库工具,则需要在 **WorkFlowCore.Framework** 实现 WorkflowCore.IRepositories 下的所有接口。并在**WorkFlowCoreFrameworkService** 替换现有的注册,类似: + ``` + if (conf.OrmType== FrameworkConfigOrmType.Default) + { + services.AddScoped(typeof(IBasicRepository<,>), typeof(BasicRepository<,>)); + services.AddScoped(typeof(IBasicRepository<>), typeof(BasicRepository<>)); + services.AddScoped(typeof(IWorkStepRepository), typeof(WorkStepRepository)); + services.AddScoped(typeof(IWorkTaskRepository), typeof(WorkTaskRepository)); + services.AddScoped(typeof(IUnitOfWork), typeof(UnitOfWork)); + services.AddScoped(typeof(UnitOfWork)); + services.AddScoped(typeof(IUnitOfWorkManager), typeof(UnitOfWorkManager)); + } + else if (conf.OrmType == FrameworkConfigOrmType.EF) + { + services.AddScoped(typeof(IBasicRepository<,>), typeof(BasicRepository4EF<,>)); + services.AddScoped(typeof(IBasicRepository<>), typeof(BasicRepository4EF<>)); + services.AddScoped(typeof(IWorkStepRepository), typeof(WorkStepRepository4EF)); + services.AddScoped(typeof(IWorkTaskRepository), typeof(WorkTaskRepository4EF)); + services.AddScoped(typeof(IUnitOfWork), typeof(UnitOfWork4EF)); + services.AddScoped(typeof(IUnitOfWorkManager), typeof(UnitOfWorkManager4EF)); + } + ``` + + ### 自定义 EventBus + - 实现 **WorkFlowCore.Common.EventBus** 下 ***IEventBus*** 接口,参考 **DefaultEventBus** 实现。并在 **EventBusService** 进行服务的注册扩展。之后在 Setup services 配置载入即可。 + + + + \ No newline at end of file diff --git a/WorkFlowCore/Plugins/Conditions/Condition_PluginDemo/ConditionDemo.cs b/WorkFlowCore/Plugins/Conditions/Condition_PluginDemo/ConditionDemo.cs new file mode 100644 index 0000000000000000000000000000000000000000..89725344b6ffe00d75c0ead0875586a52221306e --- /dev/null +++ b/WorkFlowCore/Plugins/Conditions/Condition_PluginDemo/ConditionDemo.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; +using WorkFlowCore.Conditions; + +namespace Condition_PluginDemo +{ + [Condition("条件处理器插件demo", "这只是一个demo,当传入参数(parameter)为 'true'(包括引号) 时通过")] + public class ConditionDemo : ICondition + { + public bool CanAccept(ConditionInput input) + { + return "true".Equals(input.Expression); + } + } +} diff --git a/WorkFlowCore/Plugins/Conditions/Condition_PluginDemo/Condition_PluginDemo.csproj b/WorkFlowCore/Plugins/Conditions/Condition_PluginDemo/Condition_PluginDemo.csproj new file mode 100644 index 0000000000000000000000000000000000000000..6b5d6145a2ca37287350f07365b67a5ac3a7e968 --- /dev/null +++ b/WorkFlowCore/Plugins/Conditions/Condition_PluginDemo/Condition_PluginDemo.csproj @@ -0,0 +1,28 @@ + + + + netcoreapp3.1 + + + + + libs\WorkFlowCore.dll + + + libs\WorkFlowCore.Common.dll + + + + + + Never + + + Never + + + Always + + + + diff --git a/WorkFlowCore/Plugins/Conditions/Condition_PluginDemo/libs/WorkFlowCore.Common.dll b/WorkFlowCore/Plugins/Conditions/Condition_PluginDemo/libs/WorkFlowCore.Common.dll new file mode 100644 index 0000000000000000000000000000000000000000..bfd62e75139d867f57cb32dbb2d605d6540edba1 Binary files /dev/null and b/WorkFlowCore/Plugins/Conditions/Condition_PluginDemo/libs/WorkFlowCore.Common.dll differ diff --git a/WorkFlowCore/Plugins/Conditions/Condition_PluginDemo/libs/WorkFlowCore.dll b/WorkFlowCore/Plugins/Conditions/Condition_PluginDemo/libs/WorkFlowCore.dll new file mode 100644 index 0000000000000000000000000000000000000000..9bfdd622f58d698ec76425fe747afb0b59bb99e1 Binary files /dev/null and b/WorkFlowCore/Plugins/Conditions/Condition_PluginDemo/libs/WorkFlowCore.dll differ diff --git a/WorkFlowCore/Plugins/Conditions/Condition_PluginDemo/manifest.json b/WorkFlowCore/Plugins/Conditions/Condition_PluginDemo/manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..74868514c333f353eda60c245c4db86a6e62a36e --- /dev/null +++ b/WorkFlowCore/Plugins/Conditions/Condition_PluginDemo/manifest.json @@ -0,0 +1,4 @@ +{ + "Entry": "Condition_PluginDemo.dll", + "Version": "1.0" +} diff --git a/WorkFlowCore/Plugins/UserSelectors/UserSelector_PluginDemo/UserSelectorDemo.cs b/WorkFlowCore/Plugins/UserSelectors/UserSelector_PluginDemo/UserSelectorDemo.cs new file mode 100644 index 0000000000000000000000000000000000000000..4f907240b0921d208c7190aceffa1b2630be29fc --- /dev/null +++ b/WorkFlowCore/Plugins/UserSelectors/UserSelector_PluginDemo/UserSelectorDemo.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Text; +using WorkFlowCore.UserSelectors; + +namespace UserSelector_PluginDemo +{ + [UserSelector("用户选择器插件demo","这是一个插件demo")] + public class UserSelectorDemo : IUserSelector + { + private static List selections = null; + private static Dictionary> selectionUsers = null; + + static UserSelectorDemo() + { + selections = new List(); + selectionUsers = new Dictionary>(); + for (int i = 0; i < 4; i++) + { + selections.Add(new Selection { Id = i.ToString(), Name = $"类型选项{i}" }); + var users = new List(); + for (int j = 0; j < 5; j++) + { + users.Add(new User($"UserSelectorDemo-{i}-{j}", $"选项{i}-用{j}")); + } + selectionUsers.Add(i.ToString(), users); + } + } + + public List GetSelections() + { + return selections; + } + + public List GetUsers(SelectorInput input) + { + try + { + return selectionUsers[input.SelectionId]; + } + catch (Exception) + { + return new List(); + } + } + } +} diff --git a/WorkFlowCore/Plugins/UserSelectors/UserSelector_PluginDemo/UserSelector_PluginDemo.csproj b/WorkFlowCore/Plugins/UserSelectors/UserSelector_PluginDemo/UserSelector_PluginDemo.csproj new file mode 100644 index 0000000000000000000000000000000000000000..f295c94724587307610a4bc8a061c130349c7e9b --- /dev/null +++ b/WorkFlowCore/Plugins/UserSelectors/UserSelector_PluginDemo/UserSelector_PluginDemo.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp3.1 + + + + + + + + + Always + + + + diff --git a/WorkFlowCore/Plugins/UserSelectors/UserSelector_PluginDemo/manifest.json b/WorkFlowCore/Plugins/UserSelectors/UserSelector_PluginDemo/manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..6c978b771b68f4fdf093d58c0e04a54436ae036f --- /dev/null +++ b/WorkFlowCore/Plugins/UserSelectors/UserSelector_PluginDemo/manifest.json @@ -0,0 +1,4 @@ +{ + "Entry": "UserSelector_PluginDemo.dll", + "Version": "1.0" +} diff --git "a/WorkFlowCore/ReadmeImges/\347\224\250\346\210\267\351\200\211\346\213\251\345\231\250\346\217\222\344\273\266\347\233\256\345\275\225.png" "b/WorkFlowCore/ReadmeImges/\347\224\250\346\210\267\351\200\211\346\213\251\345\231\250\346\217\222\344\273\266\347\233\256\345\275\225.png" new file mode 100644 index 0000000000000000000000000000000000000000..a19623551f2c8b66a8043136e5e79fb513467648 Binary files /dev/null and "b/WorkFlowCore/ReadmeImges/\347\224\250\346\210\267\351\200\211\346\213\251\345\231\250\346\217\222\344\273\266\347\233\256\345\275\225.png" differ diff --git a/WorkFlowCore/WorkFlowCore.Common/EventBus/EventBusService.cs b/WorkFlowCore/WorkFlowCore.Common/EventBus/EventBusService.cs index 25dbe32bae0a37654d6c5954fb0c3032a8e9e1dc..30e4b492209a2e88269174092e128213b3352f6f 100644 --- a/WorkFlowCore/WorkFlowCore.Common/EventBus/EventBusService.cs +++ b/WorkFlowCore/WorkFlowCore.Common/EventBus/EventBusService.cs @@ -34,19 +34,27 @@ namespace WorkFlowCore.Common.EventBus return services; } public static IApplicationBuilder InitGlobalEventBus(this IApplicationBuilder app) + { + InitGlobalEventBus(app.ApplicationServices); + + return app; + + } + + public static IServiceProvider InitGlobalEventBus(this IServiceProvider serviceProvider) { //注册普通事件,该事件订阅在单应用有效无法分布式 - EventBusManager.Init(app.ApplicationServices); + EventBusManager.Init(serviceProvider); //注册kafka作为分布式事件 - var kafkaEventBus = app.ApplicationServices.GetService(); - var config = app.ApplicationServices.GetService(); - var configuration = app.ApplicationServices.GetService(); + var kafkaEventBus = serviceProvider.GetService(); + var config = serviceProvider.GetService(); + var configuration = serviceProvider.GetService(); Console.WriteLine("servers:" + configuration["KafkaBootstrapServers"]); - if (kafkaEventBus!=null&&config!=null && config.RegisterAssemblies != null) + if (kafkaEventBus != null && config != null && config.RegisterAssemblies != null) kafkaEventBus.RegistSubscriptions(config.RegisterAssemblies); - return app; + return serviceProvider; } } diff --git a/WorkFlowCore/WorkFlowCore.Common/SimplePluginLoaders/Manifest.cs b/WorkFlowCore/WorkFlowCore.Common/SimplePluginLoaders/Manifest.cs new file mode 100644 index 0000000000000000000000000000000000000000..9e4e8a41ca6ef8cf3ac87e320a024fb20042ae3f --- /dev/null +++ b/WorkFlowCore/WorkFlowCore.Common/SimplePluginLoaders/Manifest.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace WorkFlowCore.Common.SimplePluginLoaders +{ + public class Manifest + { + public string Entry { get; set; } + public string Version { get; set; } + public Assembly Assembly { get; set; } + } +} diff --git a/WorkFlowCore/WorkFlowCore.Common/SimplePluginLoaders/PluginAssemblyLoadContext.cs b/WorkFlowCore/WorkFlowCore.Common/SimplePluginLoaders/PluginAssemblyLoadContext.cs new file mode 100644 index 0000000000000000000000000000000000000000..c59ad9efb9eafcc06322ae83e3a9e18064017156 --- /dev/null +++ b/WorkFlowCore/WorkFlowCore.Common/SimplePluginLoaders/PluginAssemblyLoadContext.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.Loader; +using System.Text; + +namespace WorkFlowCore.Common.SimplePluginLoaders +{ + public class PluginAssemblyLoadContext : AssemblyLoadContext + { + private AssemblyDependencyResolver _resolver; + + public PluginAssemblyLoadContext(string mainAssemblyToLoadPath) : base(isCollectible: true) + { + _resolver = new AssemblyDependencyResolver(mainAssemblyToLoadPath); + } + + protected override Assembly Load(AssemblyName name) + { + string assemblyPath = _resolver.ResolveAssemblyToPath(name); + if (assemblyPath != null) + { + //***这里需要使用 Default 来载入程序集,因为有共享 程序集。否则会出现类型转换问题*** + //https://learn.microsoft.com/zh-cn/dotnet/core/dependency-loading/understanding-assemblyloadcontext#shared-dependencies + return Default.LoadFromAssemblyPath(assemblyPath); + } + return base.Load(name); + } + } +} diff --git a/WorkFlowCore/WorkFlowCore.Common/SimplePluginLoaders/SimplePluginLoader.cs b/WorkFlowCore/WorkFlowCore.Common/SimplePluginLoaders/SimplePluginLoader.cs new file mode 100644 index 0000000000000000000000000000000000000000..f0d4b919cc3c678682477c1330c576b521920817 --- /dev/null +++ b/WorkFlowCore/WorkFlowCore.Common/SimplePluginLoaders/SimplePluginLoader.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Text; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; + +namespace WorkFlowCore.Common.SimplePluginLoaders +{ + public static class SimplePluginLoader + { + /// + /// 载入插件 + /// + /// + /// + /// + /// + /// + public static IServiceCollection LoadPlugins(this IServiceCollection services, string pluginDir, Action> loaded) where TManifest : Manifest + { + if (!Directory.Exists(pluginDir)) return services; + + var plugins = new List(); + + var dirs = Directory.GetDirectories(pluginDir); + foreach (var dir in dirs) + { + var manifestPath = Path.Combine(dir, "manifest.json"); + if (!File.Exists(manifestPath)) continue; + try + { + var manifest = JsonConvert.DeserializeObject(File.ReadAllText(manifestPath)); + if (manifest == null) continue; + + var context = new PluginAssemblyLoadContext(System.IO.Path.Combine(dir, "src", manifest.Entry)); + var EntryLib = manifest.Entry; + var assembly = context.LoadFromAssemblyName(new System.Reflection.AssemblyName(EntryLib.Substring(0, EntryLib.Length - 4))); + manifest.Assembly = assembly; + plugins.Add(manifest); + } + catch (Exception ex) + { + Console.Error.WriteLine($"{ex.Message},{ex}"); + // + } + + } + loaded?.Invoke(plugins); + + return services; + } + + public static IServiceCollection LoadPlugins(this IServiceCollection services, string pluginDir, Action> loaded) + { + if (!Directory.Exists(pluginDir)) Directory.CreateDirectory(pluginDir); + return LoadPlugins(services, pluginDir, loaded); + } + } +} diff --git a/WorkFlowCore/WorkFlowCore.Framework/Repositories/WorkTaskRepository.cs b/WorkFlowCore/WorkFlowCore.Framework/Repositories/WorkTaskRepository.cs index 031f57d8308026a9b05919bc06333328e0b4f2c0..8517ebe07e9015e1c136fa1a0adaddd460e585bf 100644 --- a/WorkFlowCore/WorkFlowCore.Framework/Repositories/WorkTaskRepository.cs +++ b/WorkFlowCore/WorkFlowCore.Framework/Repositories/WorkTaskRepository.cs @@ -93,5 +93,23 @@ namespace WorkFlowCore.Framework.Repositories else result.Items = (await GetListAsync(wt => workTaskIds.Contains(wt.Id))).OrderByDescending(ws => ws.CreationTime).Select(ws => ws.ToWorkTask()).Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList(); return await Task.FromResult(result); } + + public async Task> GetWorkTasksOfCreatorAsync(string userId, int pageIndex = 1, int pageSize = -1) + { + var workTaskIds = (await workStepRepository.GetListAsync(ws => ws.CreatedUserId == userId && ws.IsHandled)).Select(ws => ws.WorkTaskId); + + var workTasks = (await GetListAsync(wt => workTaskIds.Contains(wt.Id) && !wt.IsSimulation)).OrderByDescending(ws => ws.CreationTime); + + var result = new PageResult + { + Total = await GetCountAsync(wt => workTaskIds.Contains(wt.Id) && !wt.IsSimulation) + }; + + if (pageSize < 1) + result.Items = (await Task.FromResult(workTasks.Select(w => w.ToWorkTask()))).ToList(); + else result.Items = (await Task.FromResult(workTasks.Select(w => w.ToWorkTask()).Skip((pageIndex - 1) * pageSize).Take(pageSize))).ToList(); + + return await Task.FromResult(result); + } } } diff --git a/WorkFlowCore/WorkFlowCore.Framework/Repositories4EF/WorkTaskRepository4EF.cs b/WorkFlowCore/WorkFlowCore.Framework/Repositories4EF/WorkTaskRepository4EF.cs index 071650f60113ed242a66587c802ac4251e4067ef..bc02c86de87e194aac00ddabf0dc6ee2686ffe80 100644 --- a/WorkFlowCore/WorkFlowCore.Framework/Repositories4EF/WorkTaskRepository4EF.cs +++ b/WorkFlowCore/WorkFlowCore.Framework/Repositories4EF/WorkTaskRepository4EF.cs @@ -101,5 +101,18 @@ namespace WorkFlowCore.Framework.Repositories4EF else result.Items = worktaskQuery.Skip((pageIndex - 1) * pageSize).Take(pageSize).OrderByDescending(w => w.CreationTime).Select(w => w.ToWorkTask()).ToList(); return await Task.FromResult(result); } + + public async Task> GetWorkTasksOfCreatorAsync(string userId, int pageIndex = 1, int pageSize = -1) + { + var worktaskQuery = workflowDbContext.Set().Where(wt => !wt.Deleted && wt.CreatedUserId == userId && !wt.IsSimulation); + var result = new PageResult + { + Total = worktaskQuery.Count() + }; + if (pageSize < 1) + result.Items = worktaskQuery.Select(w => w.ToWorkTask()).ToList(); + else result.Items = worktaskQuery.Skip((pageIndex - 1) * pageSize).Take(pageSize).OrderByDescending(w => w.CreationTime).Select(w => w.ToWorkTask()).ToList(); + return await Task.FromResult(result); + } } } diff --git a/WorkFlowCore/WorkFlowCore.Framework/WorkFlowCore.Framework.csproj b/WorkFlowCore/WorkFlowCore.Framework/WorkFlowCore.Framework.csproj index a9f729199920ecf760be2e519e226be47d6adbc2..aae58818df1f0ac7915446147b6c3ca90d6b158e 100644 --- a/WorkFlowCore/WorkFlowCore.Framework/WorkFlowCore.Framework.csproj +++ b/WorkFlowCore/WorkFlowCore.Framework/WorkFlowCore.Framework.csproj @@ -6,6 +6,7 @@ + diff --git a/WorkFlowCore/WorkFlowCore.Framework/WorkFlowCoreFrameworkService.cs b/WorkFlowCore/WorkFlowCore.Framework/WorkFlowCoreFrameworkService.cs index 23c5b77cd82fb0cac78dd3684b279eda872c33b3..a8f72cd349bae393f88de95582135c8459538281 100644 --- a/WorkFlowCore/WorkFlowCore.Framework/WorkFlowCoreFrameworkService.cs +++ b/WorkFlowCore/WorkFlowCore.Framework/WorkFlowCoreFrameworkService.cs @@ -1,8 +1,16 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using System; using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; using System.Text; +using WorkFlowCore.Authorization; +using WorkFlowCore.Common.SimplePluginLoaders; using WorkFlowCore.Conditions; +using WorkFlowCore.Framework.Authorization; using WorkFlowCore.Framework.Conditions; using WorkFlowCore.Framework.EventHandlers; using WorkFlowCore.Framework.Repositories; @@ -53,21 +61,36 @@ namespace WorkFlowCore.Framework services.AddScoped(typeof(IUnitOfWorkManager), typeof(UnitOfWorkManager4EF)); } - + services.AddSingleton(); //事件处理 services.AddScoped(); services.AddScoped(); var assembly = typeof(WorkFlowCoreFrameworkService).Assembly; + services.Replace(new ServiceDescriptor(typeof(IWorkflowSession),typeof(DefaultSession), ServiceLifetime.Scoped)); //注册条件和选择器 - UserSelectorManager.RegisterSelector(assembly); - ConditionManager.RegisterCondition(assembly); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + #region 注册条件和选择器 + UserSelectorManager.RegisterSelector(services, assembly); + services.LoadPlugins(Path.Combine(AppContext.BaseDirectory, "Plugins", "UserSelectors"), plugins => + { + UserSelectorManager.RegisterSelector(services, plugins.Select(p => p.Assembly).ToArray()); + + }); + ConditionManager.Registercondition(services, assembly); + + services.LoadPlugins(Path.Combine(AppContext.BaseDirectory, "Plugins", "Conditions"), plugins => + { + ConditionManager.Registercondition(services, plugins.Select(p => p.Assembly).ToArray()); + }); + #endregion + + + //services.AddScoped(); + //services.AddScoped(); + //services.AddScoped(); + //services.AddScoped(); + //services.AddScoped(); } } } diff --git a/WorkFlowCore/WorkFlowCore.Host/Controllers/WorkFlowController.cs b/WorkFlowCore/WorkFlowCore.Host/Controllers/WorkFlowController.cs index fa3be5cc9d95af382e890f60d14545d1cefde5f8..53c48a569d3201e9aff7aaaaf5a0f62ada4244c3 100644 --- a/WorkFlowCore/WorkFlowCore.Host/Controllers/WorkFlowController.cs +++ b/WorkFlowCore/WorkFlowCore.Host/Controllers/WorkFlowController.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using WorkFlowCore.Authorization; using WorkFlowCore.Conditions; using WorkFlowCore.Framework.UserSelectors; using WorkFlowCore.Host.ViewModels; @@ -11,6 +12,7 @@ using WorkFlowCore.IRepositories; using WorkFlowCore.UserSelectors; using WorkFlowCore.Workflows; using WorkFlowCore.WorkTasks; +using Mapster; namespace WorkFlowCore.Host.Controllers { @@ -21,12 +23,13 @@ namespace WorkFlowCore.Host.Controllers private ConditionManager conditionManager; private WorkflowManager workflowManager; private IBasicRepository workflowRepository; - private IBasicRepository worktaskRepository; + private IWorkTaskRepository worktaskRepository; private IBasicRepository versionRepository; private IBasicRepository workStepRepository; + private IWorkflowSession workflowSession; - public WorkFlowController(ConditionManager conditionManager, WorkflowManager workflowManager, IBasicRepository workflowRepository, IBasicRepository worktaskRepository, IBasicRepository versionRepository, IBasicRepository workStepRepository) + public WorkFlowController(ConditionManager conditionManager, WorkflowManager workflowManager, IBasicRepository workflowRepository, IWorkTaskRepository worktaskRepository, IBasicRepository versionRepository, IBasicRepository workStepRepository) { this.conditionManager = conditionManager; this.workflowManager = workflowManager; @@ -115,7 +118,7 @@ namespace WorkFlowCore.Host.Controllers /// /// /// - [HttpGet("DeleteWorkflowVersion")] + [HttpDelete("DeleteWorkflowVersion")] public async Task> GetWorkflowVersion(Guid id) { var result = await workflowManager.DeleteWorkflow(id); @@ -138,7 +141,7 @@ namespace WorkFlowCore.Host.Controllers /// 更新流程 /// /// - /// + /// f [HttpPut("UpdateWorkFlow")] public async Task> UpdateWorkFlow(UpdateWorkFlowInput input) { @@ -160,6 +163,18 @@ namespace WorkFlowCore.Host.Controllers return OutputDto.Succeed(worktask); } + /// + /// 创建流程任务 + /// + /// + /// + [HttpPost("CreateSimulationWorkTask")] + public async Task> CreateSimulationWorkTask(CreateWorkTaskInput input) + { + var worktask = await workflowManager.CreateSimulationWorkTask(input.WorkflowId, input.Name, input.FormData, input.EntityFullName, input.EntityKeyValue, input.CreatedUserId); + return OutputDto.Succeed(worktask); + } + /// /// 获取流程任务 /// @@ -272,8 +287,93 @@ namespace WorkFlowCore.Host.Controllers { return OutputDto.Succeed>(UserList.Users.Select(u=>new UserSelectors.User { Id=u.Id,Name=u.Name}).ToList()); } - - + /// + /// 获取所有审批步骤 + /// + /// + /// + [HttpGet("GetAllTaskStepsOfWorkTaskByEntityInfo")] + public async Task> GetAllTaskStepsOfWorkTaskByEntityInfo(string entityFullName, string entityKeyValue) + { + //获取所有过程输出 + var historySteps = await workflowManager.GetAllTaskStepsOfWorkTaskByEntityInfoAsync(entityFullName, entityKeyValue); + return historySteps; + } + + + + private async Task FillWorktaskInfo(List workTasks) + { + var workflows = await workflowRepository.GetListAsync(w => workTasks.Select(s => s.WorkflowId.Id).Contains(w.Id)); + workTasks.ForEach(i => + { + i.WorkflowId.WorkflowName = workflows.FirstOrDefault(w => w.Id == i.WorkflowId.Id && w.ActiveVersion == i.WorkflowId.VersionId)?.Name; + }); + } + + /// + /// 获取用户待处理的流程 + /// + /// + [HttpGet("GetUnHandledWorkTasksOfUser")] + public async Task> GetUnHandledWorkTasksOfUser([FromQuery] GetUnHandledWorkTasksOfUserInput input) + { + var worktasks = await workflowManager.GetUnHandledWorkTasksOfUserAsync(workflowSession.User.Id, input.CurrentPage, input.MaxResultCount); + var result = new PageResult + { + Items = worktasks.Items.Adapt>(),// mapper.Map, List>(worktasks.Items), + Total = worktasks.Total + }; + await FillWorktaskInfo(result.Items); + + return result; + + } + + /// + /// 获取用户已处理的流程 + /// + /// + [HttpGet("GetHandledWorkTasksOfUser")] + public async Task> GetHandledWorkTasksOfUser([FromQuery] GetHandledWorkTasksOfUserInput input) + { + var worktasks = await workflowManager.GetHandledWorkTasksOfUserAsync(workflowSession.User.Id, input.CurrentPage, input.MaxResultCount); + var result = new PageResult + { + Items = worktasks.Items.Adapt>(),// mapper.Map, List>(worktasks.Items), + Total = worktasks.Total + }; + + await FillWorktaskInfo(result.Items); + + return result; + } + /// + /// 根据实体类型获取所有的处理中的工作流 + /// + /// + /// + [HttpGet("GetAllProcessingWorkTasksByEntityType")] + public async Task> GetAllProcessingWorkTasksByEntityType([FromQuery] GetAllProcessingWorkTasksByEntityTypeInput input) + { + var worktaskInfos = await worktaskRepository.GetListAsync(wt => input.EntityKeyValues.Contains(wt.EntityKeyValue) && !wt.Deleted && wt.EntityFullName == input.EntityFullName); + return worktaskInfos.Select(wt => wt.ToWorkTask()).ToList(); + } + [HttpGet("GetWorkTasksOfCreator")] + public async Task> GetWorkTasksOfCreator([FromQuery] GetWorkTasksOfCreatorInput input) + { + var worktasks = await worktaskRepository.GetWorkTasksOfCreatorAsync(workflowSession.User.Id, input.CurrentPage, input.MaxResultCount); + var result = new PageResult + { + Items = worktasks.Items.Adapt>(), //mapper.Map, List>(worktasks.Items), + Total = worktasks.Total + }; + + await FillWorktaskInfo(result.Items); + + return result; + } + } } diff --git a/WorkFlowCore/WorkFlowCore.Host/ViewModels/WorkFlowCore/GetAllProcessingWorkTasksByEntityTypeInput.cs b/WorkFlowCore/WorkFlowCore.Host/ViewModels/WorkFlowCore/GetAllProcessingWorkTasksByEntityTypeInput.cs new file mode 100644 index 0000000000000000000000000000000000000000..86900b8dddc91b42803fc52190e44ed1ff2a7f9c --- /dev/null +++ b/WorkFlowCore/WorkFlowCore.Host/ViewModels/WorkFlowCore/GetAllProcessingWorkTasksByEntityTypeInput.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace WorkFlowCore.Host.ViewModels.WorkFlowCore +{ + public class GetAllProcessingWorkTasksByEntityTypeInput + { + public string EntityFullName { get; set; } + public string[] EntityKeyValues { get;set; } + } +} diff --git a/WorkFlowCore/WorkFlowCore.Host/ViewModels/WorkFlowCore/GetHandledWorkTasksOfUserInput.cs b/WorkFlowCore/WorkFlowCore.Host/ViewModels/WorkFlowCore/GetHandledWorkTasksOfUserInput.cs new file mode 100644 index 0000000000000000000000000000000000000000..1edbcf404a8e8fc4978aeaadfcecc4810c7dde54 --- /dev/null +++ b/WorkFlowCore/WorkFlowCore.Host/ViewModels/WorkFlowCore/GetHandledWorkTasksOfUserInput.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace WorkFlowCore.Host.ViewModels.WorkFlowCore +{ + public class GetHandledWorkTasksOfUserInput + { + public int CurrentPage { get; set; } = 1; + public int MaxResultCount { get; set; } = -1; + } + +} diff --git a/WorkFlowCore/WorkFlowCore.Host/ViewModels/WorkFlowCore/GetUnHandlerWorkTasksOfUserInput.cs b/WorkFlowCore/WorkFlowCore.Host/ViewModels/WorkFlowCore/GetUnHandlerWorkTasksOfUserInput.cs new file mode 100644 index 0000000000000000000000000000000000000000..7571e42409f84e52bcdf465f76c8c6f1825ae60f --- /dev/null +++ b/WorkFlowCore/WorkFlowCore.Host/ViewModels/WorkFlowCore/GetUnHandlerWorkTasksOfUserInput.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace WorkFlowCore.Host.ViewModels.WorkFlowCore +{ + public class GetUnHandledWorkTasksOfUserInput + { + public int CurrentPage { get; set; } = 1; + public int MaxResultCount { get; set; } = -1; + } + +} diff --git a/WorkFlowCore/WorkFlowCore.Host/ViewModels/WorkFlowCore/GetWorkTasksOfCreatorInput.cs b/WorkFlowCore/WorkFlowCore.Host/ViewModels/WorkFlowCore/GetWorkTasksOfCreatorInput.cs new file mode 100644 index 0000000000000000000000000000000000000000..eb559da687e3cd6ba1cbdf2e7cabf13ebfe89e4b --- /dev/null +++ b/WorkFlowCore/WorkFlowCore.Host/ViewModels/WorkFlowCore/GetWorkTasksOfCreatorInput.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace WorkFlowCore.Host.ViewModels.WorkFlowCore +{ + public class GetWorkTasksOfCreatorInput + { + public int CurrentPage { get; set; } = 1; + public int MaxResultCount { get; set; } = -1; + } + +} diff --git a/WorkFlowCore/WorkFlowCore.Host/ViewModels/WorkFlowCore/WorkTaskDto.cs b/WorkFlowCore/WorkFlowCore.Host/ViewModels/WorkFlowCore/WorkTaskDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..6df6e7af33874e6e758daee17fe578894b63df25 --- /dev/null +++ b/WorkFlowCore/WorkFlowCore.Host/ViewModels/WorkFlowCore/WorkTaskDto.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; +using WorkFlowCore.IRepositories; +using WorkFlowCore.Workflows; +using WorkFlowCore.WorkTasks; + +namespace WorkFlowCore.Host.ViewModels.WorkFlowCore +{ + /// + /// 工作流dto + /// + public class WorkTaskDto: WithBaseInfoEntity + { + /// + /// 流程id + /// + public WorkflowIdDto WorkflowId { get; set; } + /// + /// 任务名称 + /// + public string Name { get; set; } + + /// + /// 表单数据(json) + /// + public string FormData { get; set; } + /// + /// 实体全称 + /// + public string EntityFullName { get; set; } + /// + /// 实体主键值 + /// + public string EntityKeyValue { get; set; } + /// + /// 审批状态 + /// + public WorkTaskStatus WorkTaskStatus { get; set; } = WorkTaskStatus.Pending; + + public bool IsProcessed { get => WorkTaskStatus == WorkTaskStatus.Processed; } + public bool IsPending { get => WorkTaskStatus == WorkTaskStatus.Pending; } + public bool IsProcessing { get => WorkTaskStatus == WorkTaskStatus.Processing; } + public bool IsSimulation { get; set; } + } +} diff --git a/WorkFlowCore/WorkFlowCore.Host/ViewModels/WorkFlowCore/WorkflowIdDto.cs b/WorkFlowCore/WorkFlowCore.Host/ViewModels/WorkFlowCore/WorkflowIdDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..eedcbefcf91896afea1fe9b5dbafc9b865adaf57 --- /dev/null +++ b/WorkFlowCore/WorkFlowCore.Host/ViewModels/WorkFlowCore/WorkflowIdDto.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; +using WorkFlowCore.Workflows; + +namespace WorkFlowCore.Host.ViewModels.WorkFlowCore +{ + public class WorkflowIdDto: WorkflowId + { + /// + /// 流程名称 + /// + public string WorkflowName { get; set; } + } +} diff --git a/WorkFlowCore/WorkFlowCore.Host/WorkFlowCore.Host.csproj b/WorkFlowCore/WorkFlowCore.Host/WorkFlowCore.Host.csproj index 9c9fb5dac9021baf4cd21f9433f7653ed03d0c95..f5b6934874c780bdf4ba1ba5f05fe4e0d0ddf0cb 100644 --- a/WorkFlowCore/WorkFlowCore.Host/WorkFlowCore.Host.csproj +++ b/WorkFlowCore/WorkFlowCore.Host/WorkFlowCore.Host.csproj @@ -15,6 +15,8 @@ + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/WorkFlowCore/WorkFlowCore.Host/WorkFlowCore.Host.xml b/WorkFlowCore/WorkFlowCore.Host/WorkFlowCore.Host.xml index ddbc8fbba4e083c5203d26bae83019db604f72cf..896c91c19714e547fdc2b26d28004de893c84e58 100644 --- a/WorkFlowCore/WorkFlowCore.Host/WorkFlowCore.Host.xml +++ b/WorkFlowCore/WorkFlowCore.Host/WorkFlowCore.Host.xml @@ -69,7 +69,7 @@ 更新流程 - + f @@ -78,6 +78,13 @@ + + + 创建流程任务 + + + + 获取流程任务 @@ -139,6 +146,32 @@ + + + 获取所有审批步骤 + + + + + + + 获取用户待处理的流程 + + + + + + 获取用户已处理的流程 + + + + + + 根据实体类型获取所有的处理中的工作流 + + + + 流程id @@ -234,6 +267,11 @@ 流程连线 + + + 流程名称 + + 绘制信息,前端绘制所需信息 @@ -269,5 +307,40 @@ 拒绝节点 + + + 工作流dto + + + + + 流程id + + + + + 任务名称 + + + + + 表单数据(json) + + + + + 实体全称 + + + + + 实体主键值 + + + + + 审批状态 + + diff --git a/WorkFlowCore/WorkFlowCore.Host/appsettings.Development.json b/WorkFlowCore/WorkFlowCore.Host/appsettings.Development.json index b58cdf3c21c122c76319378af36480362036fee5..678228f8bab6813952ae0db9bf01aa82bb8f2770 100644 --- a/WorkFlowCore/WorkFlowCore.Host/appsettings.Development.json +++ b/WorkFlowCore/WorkFlowCore.Host/appsettings.Development.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "Default": "Database=Workflow;Data Source=81.71.14.205;Port=3308;UserId=root;Password=ShiHuiDai123!;Charset=utf8;TreatTinyAsBoolean=false;Allow User Variables=True", + "Default": "Database=WorkflowCore;Data Source=39.101.74.14;Port=3308;UserId=root;Password=ShiHuiDai123!;Charset=utf8;TreatTinyAsBoolean=false;Allow User Variables=True", "DefaultVersion": "8.0.26" }, "Logging": { diff --git a/WorkFlowCore/WorkFlowCore.Test/WorkFlowCore.Test.csproj b/WorkFlowCore/WorkFlowCore.Test/WorkFlowCore.Test.csproj index d518e33520471501937e81580efdd2658e6dc1a3..4d6037e44f559b55dfd9a5da5758b6e245583c45 100644 --- a/WorkFlowCore/WorkFlowCore.Test/WorkFlowCore.Test.csproj +++ b/WorkFlowCore/WorkFlowCore.Test/WorkFlowCore.Test.csproj @@ -7,6 +7,7 @@ + diff --git a/WorkFlowCore/WorkFlowCore.Test/Workflow/Workflow_Test.cs b/WorkFlowCore/WorkFlowCore.Test/Workflow/Workflow_Test.cs index 2e32235cde7b36a4d00e31cb9316092a87539426..c27856715f612e9a22857c70f78abdaf3ba35057 100644 --- a/WorkFlowCore/WorkFlowCore.Test/Workflow/Workflow_Test.cs +++ b/WorkFlowCore/WorkFlowCore.Test/Workflow/Workflow_Test.cs @@ -1,10 +1,12 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using NUnit.Framework; using System; using System.Collections.Generic; using System.Linq; using System.Text; +using WorkFlowCore.Common.EventBus; using WorkFlowCore.Conditions; using WorkFlowCore.Framework; using WorkFlowCore.Framework.Repositories; @@ -37,12 +39,20 @@ namespace WorkFlowCore.Test.Workflow services.AddScoped(); services.AddScoped(); + var configurationBuilder = new ConfigurationBuilder(); + var configuration = configurationBuilder.Build(); + services.AddSingleton(configuration); + + services.AddDefautEventBus(typeof(WorkFlowCoreFrameworkService).Assembly); + + BasicRepository.ClearData(); BasicRepository.ClearData(); BasicRepository.ClearData(); BasicRepository.ClearData(); serviceProvider = services.BuildServiceProvider(); + serviceProvider.InitGlobalEventBus(); } [Test] diff --git a/WorkFlowCore/WorkFlowCore.sln b/WorkFlowCore/WorkFlowCore.sln index 716539896243e2e45a6a613ee77d0af92b11d0e9..59c4e831c6b7e40ff2d7dca974b9bb2204aa8d4b 100644 --- a/WorkFlowCore/WorkFlowCore.sln +++ b/WorkFlowCore/WorkFlowCore.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30413.136 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32516.85 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkFlowCore", "WorkFlowCore\WorkFlowCore.csproj", "{8B678BB1-F787-448D-8714-4D34E165B4DF}" EndProject @@ -11,7 +11,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkFlowCore.Test", "WorkFl EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkFlowCore.Host", "WorkFlowCore.Host\WorkFlowCore.Host.csproj", "{1C0D8C51-D49C-4C1D-9C66-1522546360E0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkFlowCore.Common", "WorkFlowCore.Common\WorkFlowCore.Common.csproj", "{8F14A61F-C202-4ED1-A167-B426EE428DDA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkFlowCore.Common", "WorkFlowCore.Common\WorkFlowCore.Common.csproj", "{8F14A61F-C202-4ED1-A167-B426EE428DDA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{3D75B3EF-C8FF-42E0-A3B0-A0C63FDC6110}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UserSelectors", "UserSelectors", "{F371EB9A-F50A-4FF3-BBC4-1C9C50AF4D78}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Conditions", "Conditions", "{A81DC2DC-4670-40CE-AFA8-708A9E05E93D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UserSelector_PluginDemo", "Plugins\UserSelectors\UserSelector_PluginDemo\UserSelector_PluginDemo.csproj", "{D018E18A-B871-4C25-8A79-44EFF4A76228}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Condition_PluginDemo", "Plugins\Conditions\Condition_PluginDemo\Condition_PluginDemo.csproj", "{83CEABEA-4230-4C5E-AA80-D3EC302B7D19}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -39,10 +49,24 @@ Global {8F14A61F-C202-4ED1-A167-B426EE428DDA}.Debug|Any CPU.Build.0 = Debug|Any CPU {8F14A61F-C202-4ED1-A167-B426EE428DDA}.Release|Any CPU.ActiveCfg = Release|Any CPU {8F14A61F-C202-4ED1-A167-B426EE428DDA}.Release|Any CPU.Build.0 = Release|Any CPU + {D018E18A-B871-4C25-8A79-44EFF4A76228}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D018E18A-B871-4C25-8A79-44EFF4A76228}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D018E18A-B871-4C25-8A79-44EFF4A76228}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D018E18A-B871-4C25-8A79-44EFF4A76228}.Release|Any CPU.Build.0 = Release|Any CPU + {83CEABEA-4230-4C5E-AA80-D3EC302B7D19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {83CEABEA-4230-4C5E-AA80-D3EC302B7D19}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83CEABEA-4230-4C5E-AA80-D3EC302B7D19}.Release|Any CPU.ActiveCfg = Release|Any CPU + {83CEABEA-4230-4C5E-AA80-D3EC302B7D19}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {F371EB9A-F50A-4FF3-BBC4-1C9C50AF4D78} = {3D75B3EF-C8FF-42E0-A3B0-A0C63FDC6110} + {A81DC2DC-4670-40CE-AFA8-708A9E05E93D} = {3D75B3EF-C8FF-42E0-A3B0-A0C63FDC6110} + {D018E18A-B871-4C25-8A79-44EFF4A76228} = {F371EB9A-F50A-4FF3-BBC4-1C9C50AF4D78} + {83CEABEA-4230-4C5E-AA80-D3EC302B7D19} = {A81DC2DC-4670-40CE-AFA8-708A9E05E93D} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2B47C6B7-D14F-4E7A-AC89-493A7F10350B} EndGlobalSection diff --git a/WorkFlowCore/WorkFlowCore/Conditions/ConditionManager.cs b/WorkFlowCore/WorkFlowCore/Conditions/ConditionManager.cs index df1c17c277f3c60667a4c022d02247ed736d15e3..7aa1e8721282b78620ae61807930ad7a671d69c5 100644 --- a/WorkFlowCore/WorkFlowCore/Conditions/ConditionManager.cs +++ b/WorkFlowCore/WorkFlowCore/Conditions/ConditionManager.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Extensions.DependencyInjection; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -101,5 +102,66 @@ namespace WorkFlowCore.Conditions } } } + + /// + /// 注册条件 + /// + /// + /// + /// + /// + public static void Registercondition(string conditionId, string conditionName, Type conditionType, string description) + { + lock (objLock) + { + if (AllConditions.Where(s => s.Id == conditionId).Any()) + return; + //throw new Exception($"相同的转换器id[{conditionId}]已存在"); + } + + AllConditions.Add(new Condition(conditionId, conditionName, conditionType, description)); + } + + /// + /// 从 程序集注册 + /// + /// + public static void Registercondition(params Assembly[] assemblies) + { + foreach (var assembly in assemblies) + { + var types = assembly.GetTypes().Where(t => t.GetCustomAttribute() != null); + + foreach (var type in types) + { + var attr = type.GetCustomAttribute(); + var conditionId = type.FullName; + var conditionName = attr.Name ?? type.FullName; + Registercondition(conditionId, conditionName, type, attr.Description); + } + } + } + + /// + /// 从 程序集注册 + /// + /// + /// + public static void Registercondition(IServiceCollection services, params Assembly[] assemblies) + { + foreach (var assembly in assemblies) + { + var types = assembly.GetTypes().Where(t => t.GetCustomAttribute() != null); + + foreach (var type in types) + { + var attr = type.GetCustomAttribute(); + var conditionId = type.FullName; + var conditionName = attr.Name ?? type.FullName; + Registercondition(conditionId, conditionName, type, attr.Description); + services.AddScoped(type); + } + } + } } } diff --git a/WorkFlowCore/WorkFlowCore/IRepositories/IWorkTaskRepository.cs b/WorkFlowCore/WorkFlowCore/IRepositories/IWorkTaskRepository.cs index 5e84b689dbe483ebec9749c0660a1903db1c73ca..1f005c5f05d66160f21e12f9265f0b6b8b19d365 100644 --- a/WorkFlowCore/WorkFlowCore/IRepositories/IWorkTaskRepository.cs +++ b/WorkFlowCore/WorkFlowCore/IRepositories/IWorkTaskRepository.cs @@ -13,5 +13,13 @@ namespace WorkFlowCore.IRepositories Task> GetUnHandledWorkTasksOfUserAsync(string userId, int pageIndex = 1, int pageSize = -1); Task> GetHandledWorkTasksOfUserAsync(string userId, int pageIndex = 1, int pageSize = -1); Task> GetTasksOfStartUserAsync(string userId, int pageIndex = 1, int pageSize = -1); + /// + /// 获取用户发起的流程 + /// + /// + /// + /// + /// + Task> GetWorkTasksOfCreatorAsync(string userId, int pageIndex = 1, int pageSize = -1); } } diff --git a/WorkFlowCore/WorkFlowCore/UserSelectors/UserSelectorManager.cs b/WorkFlowCore/WorkFlowCore/UserSelectors/UserSelectorManager.cs index a21bdb14df9abc72f7b9489cd32760b1289a7aa3..22e3e0af1eff2d7ccc6bcfa6a9ce35e9b5f89392 100644 --- a/WorkFlowCore/WorkFlowCore/UserSelectors/UserSelectorManager.cs +++ b/WorkFlowCore/WorkFlowCore/UserSelectors/UserSelectorManager.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Extensions.DependencyInjection; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -74,6 +75,47 @@ namespace WorkFlowCore.UserSelectors } } + /// + /// 从 程序集注册 + /// + /// + /// + public static void RegisterSelector(IServiceCollection services, params Assembly[] assemblies) + { + foreach (var assembly in assemblies) + { + var types = assembly.GetTypes().Where(t => t.GetCustomAttribute() != null); + + foreach (var type in types) + { + var attr = type.GetCustomAttribute(); + var selectorId = type.FullName; + var selectorName = attr.Name ?? type.FullName; + RegisterSelector(selectorId, selectorName, type, attr.Description); + services.AddScoped(type); + } + } + } + + /// + /// 从 程序集注册 + /// + /// + /// + public static void RegisterSelector(IServiceCollection services, params Type[] types) + { + var _types = types.Where(t => t.GetCustomAttribute() != null); + + foreach (var type in _types) + { + var attr = type.GetCustomAttribute(); + var selectorId = type.FullName; + var selectorName = attr.Name ?? type.FullName; + RegisterSelector(selectorId, selectorName, type, attr.Description); + services.AddScoped(type); + } + } + /// /// 获取选择器 diff --git a/WorkFlowCore/WorkFlowCore/WorkflowManager.cs b/WorkFlowCore/WorkFlowCore/WorkflowManager.cs index 63bcf4c80ce69d68d818307f777220ed5a4c30f0..fbc53e7e871978ecefa7dbbbf5053eae3e900849 100644 --- a/WorkFlowCore/WorkFlowCore/WorkflowManager.cs +++ b/WorkFlowCore/WorkFlowCore/WorkflowManager.cs @@ -1099,6 +1099,7 @@ namespace WorkFlowCore return userSelector.GetSelections(); } + /// /// 删除流程 /// 同时删除审批记录 @@ -1107,10 +1108,24 @@ namespace WorkFlowCore /// public async Task DeleteWorkTask(Guid worktaskId) { - await workStepRepository.DeleteManyAsync(ws => ws.WorkTaskId == worktaskId); - await workTaskRepository.DeleteAsync(worktaskId); + var worktaskInfo = await workTaskRepository.GetAsync(worktaskId); + + if (worktaskInfo == null) + throw new Exception($"查无此项:{worktaskId}"); + + var worktask = worktaskInfo.ToWorkTask(); + + if (!worktask.IsPending) + throw new Exception("非待审批状态无法删除"); + + using (var unitOfWork = unitOfWorkManager.Begin()) + { + await workStepRepository.DeleteManyAsync(ws => ws.WorkTaskId == worktaskId); + await workTaskRepository.DeleteAsync(worktaskId); + } } + #endregion } } diff --git a/WorkFlowCore/WorkFlowCore/Workflows/RejectNode.cs b/WorkFlowCore/WorkFlowCore/Workflows/RejectNode.cs index 7cdd308dc58a2244227e3d130eda6360bfe964b6..9a984c5df0927c6c54ec4d01ba5f94d57a95891f 100644 --- a/WorkFlowCore/WorkFlowCore/Workflows/RejectNode.cs +++ b/WorkFlowCore/WorkFlowCore/Workflows/RejectNode.cs @@ -7,7 +7,7 @@ namespace WorkFlowCore.Workflows public class RejectNode { public List Conditions { get; set; } - public Guid NodeId; - public Guid NodeName; + public Guid NodeId { get; set; } + public string NodeName { get; set; } } }