Commit 367e3b9880eb9e3e0f7f427ffe8c6e6f4e186f1e

Authored by 芋道源码
Committed by Gitee
2 parents d94e5779 a1933223

!1286 2.4.2:工作流的更新

Merge pull request !1286 from 芋道源码/feature/bpm
Showing 57 changed files with 2142 additions and 569 deletions
README.md
@@ -149,22 +149,45 @@ @@ -149,22 +149,45 @@
149 149
150 ### 工作流程 150 ### 工作流程
151 151
152 -| | 功能 | 描述 |  
153 -|----|-------|-----------------------------------------|  
154 -| 🚀 | 流程模型 | 配置工作流的流程模型,支持 BPMN 和仿钉钉/飞书设计器 |  
155 -| 🚀 | 流程表单 | 拖动表单元素生成相应的工作流表单,覆盖 Element UI 所有的表单组件 |  
156 -| 🚀 | 用户分组 | 自定义用户分组,可用于工作流的审批分组 |  
157 -| 🚀 | 我的流程 | 查看我发起的工作流程,支持新建、取消流程等操作,高亮流程图、审批时间线 |  
158 -| 🚀 | 待办任务 | 查看自己【未】审批的工作任务,支持通过、不通过、转派、委派、退回、加减签等操作 |  
159 -| 🚀 | 已办任务 | 查看自己【已】审批的工作任务,支持流程预测,展示未来审批人信息 |  
160 -| 🚀 | OA 请假 | 作为业务自定义接入工作流的使用示例,只需创建请求对应的工作流程,即可进行审批 |  
161 -  
162 ![功能图](/.image/common/bpm-feature.png) 152 ![功能图](/.image/common/bpm-feature.png)
163 153
  154 +基于 Flowable 构建,可支持信创(国产)数据库,满足中国特色流程操作:
  155 +
164 | BPMN 设计器 | 钉钉/飞书设计器 | 156 | BPMN 设计器 | 钉钉/飞书设计器 |
165 |------------------------------|--------------------------------| 157 |------------------------------|--------------------------------|
166 | ![](/.image/工作流设计器-bpmn.jpg) | ![](/.image/工作流设计器-simple.jpg) | 158 | ![](/.image/工作流设计器-bpmn.jpg) | ![](/.image/工作流设计器-simple.jpg) |
167 159
  160 +> 历经头部企业生产验证,工作流引擎须标配仿钉钉/飞书 + BPMN 双设计器!!!
  161 +>
  162 +> 前者支持轻量配置简单流程,后者实现复杂场景深度编排
  163 +
  164 +| 功能列表 | 功能描述 | 是否完成 |
  165 +|------------|-------------------------------------------------------------------------------------|------|
  166 +| SIMPLE 设计器 | 仿钉钉/飞书设计器,支持拖拽搭建表单流程,10 分钟快速完成审批流程配置 | ✅ |
  167 +| BPMN 设计器 | 基于 BPMN 标准开发,适配复杂业务场景,满足多层级审批及流程自动化需求 | ✅ |
  168 +| 会签 | 同一个审批节点设置多个人(如 A、B、C 三人,三人会同时收到待办任务),需全部同意之后,审批才可到下一审批节点 | ✅ |
  169 +| 或签 | 同一个审批节点设置多个人,任意一个人处理后,就能进入下一个节点 | ✅ |
  170 +| 依次审批 | (顺序会签)同一个审批节点设置多个人(如 A、B、C 三人),三人按顺序依次收到待办,即 A 先审批,A 提交后 B 才能审批,需全部同意之后,审批才可到下一审批节点 | ✅ |
  171 +| 抄送 | 将审批结果通知给抄送人,同一个审批默认排重,不重复抄送给同一人 | ✅ |
  172 +| 驳回 | (退回)将审批重置发送给某节点,重新审批。可驳回至发起人、上一节点、任意节点 | ✅ |
  173 +| 转办 | A 转给其 B 审批,B 审批后,进入下一节点 | ✅ |
  174 +| 委派 | A 转给其 B 审批,B 审批后,转给 A,A 继续审批后进入下一节点 | ✅ |
  175 +| 加签 | 允许当前审批人根据需要,自行增加当前节点的审批人,支持向前、向后加签 | ✅ |
  176 +| 减签 | (取消加签)在当前审批人操作之前,减少审批人 | ✅ |
  177 +| 撤销 | (取消流程)流程发起人,可以对流程进行撤销处理 | ✅ |
  178 +| 终止 | 系统管理员,在任意节点终止流程实例 | ✅ |
  179 +| 表单权限 | 支持拖拉拽配置表单,每个审批节点可配置只读、编辑、隐藏权限 | ✅ |
  180 +| 超时审批 | 配置超时审批时间,超时后自动触发审批通过、不通过、驳回等操作 | ✅ |
  181 +| 自动提醒 | 配置提醒时间,到达时间后自动触发短信、邮箱、站内信等通知提醒,支持自定义重复提醒频次 | ✅ |
  182 +| 父子流程 | 主流程设置子流程节点,子流程节点会自动触发子流程。子流程结束后,主流程才会执行(继续往下下执行),支持同步子流程、异步子流程 | ✅ |
  183 +| 条件分支 | (排它分支)用于在流程中实现决策,即根据条件选择一个分支执行 | ✅ |
  184 +| 并行分支 | 允许将流程分成多条分支,不进行条件判断,所有分支都会执行 | ✅ |
  185 +| 包容分支 | (条件分支 + 并行分支的结合体)允许基于条件选择多条分支执行,但如果没有任何一个分支满足条件,则可以选择默认分支 | ✅ |
  186 +| 路由分支 | 根据条件选择一个分支执行(重定向到指定配置节点),也可以选择默认分支执行(继续往下执行) | ✅ |
  187 +| 触发节点 | 执行到该节点,触发 HTTP 请求、HTTP 回调、更新数据、删除数据等 | ✅ |
  188 +| 延迟节点 | 执行到该节点,审批等待一段时间再执行,支持固定时长、固定日期等 | ✅ |
  189 +| 拓展设置 | 流程前置/后置通知,节点(任务)前置、后置通知,流程报表,自动审批去重,自定流程编号、标题、摘要,流程报表等 | ✅ |
  190 +
168 ### 支付系统 191 ### 支付系统
169 192
170 | | 功能 | 描述 | 193 | | 功能 | 描述 |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApi.java
1 package cn.iocoder.yudao.module.bpm.api.task; 1 package cn.iocoder.yudao.module.bpm.api.task;
2 2
3 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; 3 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
4 -  
5 import jakarta.validation.Valid; 4 import jakarta.validation.Valid;
6 5
7 /** 6 /**
@@ -20,4 +19,6 @@ public interface BpmProcessInstanceApi { @@ -20,4 +19,6 @@ public interface BpmProcessInstanceApi {
20 */ 19 */
21 String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO reqDTO); 20 String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO reqDTO);
22 21
  22 +
  23 +
23 } 24 }
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessTaskApi.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.api.task;
  2 +
  3 +import jakarta.validation.constraints.NotEmpty;
  4 +
  5 +/**
  6 + * 流程任务 Api 接口
  7 + *
  8 + * @author jason
  9 + */
  10 +public interface BpmProcessTaskApi {
  11 +
  12 + /**
  13 + * 触发流程任务的执行
  14 + *
  15 + * @param processInstanceId 流程实例编号
  16 + * @param taskDefineKey 任务 Key
  17 + */
  18 + void triggerTask(@NotEmpty(message = "流程实例的编号不能为空") String processInstanceId,
  19 + @NotEmpty(message = "任务 Key 不能为空") String taskDefineKey);
  20 +
  21 +}
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
@@ -24,6 +24,7 @@ public interface ErrorCodeConstants { @@ -24,6 +24,7 @@ public interface ErrorCodeConstants {
24 ErrorCode MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS = new ErrorCode(1_009_002_005, "部署流程失败,原因:BPMN 流程图中,没有开始事件"); 24 ErrorCode MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS = new ErrorCode(1_009_002_005, "部署流程失败,原因:BPMN 流程图中,没有开始事件");
25 ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在"); 25 ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在");
26 ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程({})的管理员"); 26 ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程({})的管理员");
  27 + ErrorCode MODEL_DEPLOY_FAIL_FIRST_USER_TASK_CANDIDATE_STRATEGY_ERROR = new ErrorCode(1_009_002_008, "部署流程失败,原因:首个任务({})的审批人不能是【审批人自选】");
27 28
28 // ========== 流程定义 1-009-003-000 ========== 29 // ========== 流程定义 1-009-003-000 ==========
29 ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图"); 30 ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图");
@@ -39,6 +40,8 @@ public interface ErrorCodeConstants { @@ -39,6 +40,8 @@ public interface ErrorCodeConstants {
39 ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "任务({})的候选人({})不存在"); 40 ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "任务({})的候选人({})不存在");
40 ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程"); 41 ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程");
41 ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_005, "流程取消失败,该流程不允许取消"); 42 ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_005, "流程取消失败,该流程不允许取消");
  43 + ErrorCode PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR = new ErrorCode(1_009_004_006, "流程 Http 触发器请求调用失败");
  44 + ErrorCode PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_007, "下一个任务({})的审批人未配置");
42 45
43 // ========== 流程任务 1-009-005-000 ========== 46 // ========== 流程任务 1-009-005-000 ==========
44 ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你"); 47 ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你");
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventTypeEnum.java
@@ -14,7 +14,8 @@ import lombok.Getter; @@ -14,7 +14,8 @@ import lombok.Getter;
14 public enum BpmBoundaryEventTypeEnum { 14 public enum BpmBoundaryEventTypeEnum {
15 15
16 USER_TASK_TIMEOUT(1, "用户任务超时"), 16 USER_TASK_TIMEOUT(1, "用户任务超时"),
17 - DELAY_TIMER_TIMEOUT(2, "延迟器超时"); 17 + DELAY_TIMER_TIMEOUT(2, "延迟器超时"),
  18 + CHILD_PROCESS_TIMEOUT(3, "子流程超时");
18 19
19 private final Integer type; 20 private final Integer type;
20 private final String name; 21 private final String name;
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmChildProcessMultiInstanceSourceTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.enums.definition;
  2 +
  3 +import cn.hutool.core.util.ArrayUtil;
  4 +import cn.iocoder.yudao.framework.common.core.ArrayValuable;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.Getter;
  7 +
  8 +import java.util.Arrays;
  9 +
  10 +/**
  11 + * BPM 子流程多实例来源类型枚举
  12 + *
  13 + * @author Lesan
  14 + */
  15 +@Getter
  16 +@AllArgsConstructor
  17 +public enum BpmChildProcessMultiInstanceSourceTypeEnum implements ArrayValuable<Integer> {
  18 +
  19 + FIXED_QUANTITY(1, "固定数量"),
  20 + NUMBER_FORM(2, "数字表单"),
  21 + MULTIPLE_FORM(3, "多选表单");
  22 +
  23 + private final Integer type;
  24 + private final String name;
  25 +
  26 + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmChildProcessMultiInstanceSourceTypeEnum::getType).toArray(Integer[]::new);
  27 +
  28 + public static BpmChildProcessMultiInstanceSourceTypeEnum typeOf(Integer type) {
  29 + return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
  30 + }
  31 +
  32 + @Override
  33 + public Integer[] array() {
  34 + return ARRAYS;
  35 + }
  36 +
  37 +}
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmChildProcessStartUserEmptyTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.enums.definition;
  2 +
  3 +import cn.hutool.core.util.ArrayUtil;
  4 +import cn.iocoder.yudao.framework.common.core.ArrayValuable;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.Getter;
  7 +
  8 +import java.util.Arrays;
  9 +
  10 +/**
  11 + * BPM 当子流程发起人为空时类型枚举
  12 + *
  13 + * @author Lesan
  14 + */
  15 +@Getter
  16 +@AllArgsConstructor
  17 +public enum BpmChildProcessStartUserEmptyTypeEnum implements ArrayValuable<Integer> {
  18 +
  19 + MAIN_PROCESS_START_USER(1, "同主流程发起人"),
  20 + CHILD_PROCESS_ADMIN(2, "子流程管理员"),
  21 + MAIN_PROCESS_ADMIN(3, "主流程管理员");
  22 +
  23 + private final Integer type;
  24 + private final String name;
  25 +
  26 + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmChildProcessStartUserEmptyTypeEnum::getType).toArray(Integer[]::new);
  27 +
  28 + public static BpmChildProcessStartUserEmptyTypeEnum typeOf(Integer type) {
  29 + return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
  30 + }
  31 +
  32 + @Override
  33 + public Integer[] array() {
  34 + return ARRAYS;
  35 + }
  36 +}
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmChildProcessStartUserTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.enums.definition;
  2 +
  3 +import cn.hutool.core.util.ArrayUtil;
  4 +import cn.iocoder.yudao.framework.common.core.ArrayValuable;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.Getter;
  7 +
  8 +import java.util.Arrays;
  9 +
  10 +/**
  11 + * BPM 子流程发起人类型枚举
  12 + *
  13 + * @author Lesan
  14 + */
  15 +@Getter
  16 +@AllArgsConstructor
  17 +public enum BpmChildProcessStartUserTypeEnum implements ArrayValuable<Integer> {
  18 +
  19 + MAIN_PROCESS_START_USER(1, "同主流程发起人"),
  20 + FROM_FORM(2, "表单");
  21 +
  22 + private final Integer type;
  23 + private final String name;
  24 +
  25 + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmChildProcessStartUserTypeEnum::getType).toArray(Integer[]::new);
  26 +
  27 + public static BpmChildProcessStartUserTypeEnum typeOf(Integer type) {
  28 + return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
  29 + }
  30 +
  31 + @Override
  32 + public Integer[] array() {
  33 + return ARRAYS;
  34 + }
  35 +}
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeTypeEnum.java
@@ -25,10 +25,13 @@ public enum BpmSimpleModelNodeTypeEnum implements ArrayValuable&lt;Integer&gt; { @@ -25,10 +25,13 @@ public enum BpmSimpleModelNodeTypeEnum implements ArrayValuable&lt;Integer&gt; {
25 START_USER_NODE(10, "发起人", "userTask"), // 发起人节点。前端的开始节点,Id 固定 25 START_USER_NODE(10, "发起人", "userTask"), // 发起人节点。前端的开始节点,Id 固定
26 APPROVE_NODE(11, "审批人", "userTask"), 26 APPROVE_NODE(11, "审批人", "userTask"),
27 COPY_NODE(12, "抄送人", "serviceTask"), 27 COPY_NODE(12, "抄送人", "serviceTask"),
  28 + TRANSACTOR_NODE(13, "办理人", "userTask"),
28 29
29 DELAY_TIMER_NODE(14, "延迟器", "receiveTask"), 30 DELAY_TIMER_NODE(14, "延迟器", "receiveTask"),
30 TRIGGER_NODE(15, "触发器", "serviceTask"), 31 TRIGGER_NODE(15, "触发器", "serviceTask"),
31 32
  33 + CHILD_PROCESS(20, "子流程", "callActivity"),
  34 +
32 // 50 ~ 条件分支 35 // 50 ~ 条件分支
33 CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式 36 CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式
34 CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"), 37 CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"),
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTriggerTypeEnum.java
@@ -16,8 +16,12 @@ import java.util.Arrays; @@ -16,8 +16,12 @@ import java.util.Arrays;
16 @AllArgsConstructor 16 @AllArgsConstructor
17 public enum BpmTriggerTypeEnum implements ArrayValuable<Integer> { 17 public enum BpmTriggerTypeEnum implements ArrayValuable<Integer> {
18 18
19 - HTTP_REQUEST(1, "发起 HTTP 请求"),  
20 - UPDATE_NORMAL_FORM(2, "更新流程表单"); // TODO @jason:FORM_UPDATE 19 + HTTP_REQUEST(1, "发起 HTTP 请求"), // BPM => 业务,流程继续执行,无需等待业务
  20 + HTTP_CALLBACK(2, "接收 HTTP 回调"), // BPM => 业务 => BPM,流程卡主,等待业务回调
  21 +
  22 + FORM_UPDATE(10, "更新流程表单数据"),
  23 + FORM_DELETE(11, "删除流程表单数据"),
  24 + ;
21 25
22 /** 26 /**
23 * 触发器执行动作类型 27 * 触发器执行动作类型
@@ -39,5 +43,4 @@ public enum BpmTriggerTypeEnum implements ArrayValuable&lt;Integer&gt; { @@ -39,5 +43,4 @@ public enum BpmTriggerTypeEnum implements ArrayValuable&lt;Integer&gt; {
39 public static BpmTriggerTypeEnum typeOf(Integer type) { 43 public static BpmTriggerTypeEnum typeOf(Integer type) {
40 return ArrayUtil.firstMatch(item -> item.getType().equals(type), values()); 44 return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
41 } 45 }
42 -  
43 } 46 }
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java
@@ -26,6 +26,7 @@ public enum BpmReasonEnum { @@ -26,6 +26,7 @@ public enum BpmReasonEnum {
26 TIMEOUT_REJECT("审批超时,系统自动不通过"), 26 TIMEOUT_REJECT("审批超时,系统自动不通过"),
27 ASSIGN_START_USER_APPROVE("审批人与提交人为同一人时,自动通过"), 27 ASSIGN_START_USER_APPROVE("审批人与提交人为同一人时,自动通过"),
28 ASSIGN_START_USER_APPROVE_WHEN_SKIP("审批人与提交人为同一人时,自动通过"), 28 ASSIGN_START_USER_APPROVE_WHEN_SKIP("审批人与提交人为同一人时,自动通过"),
  29 + ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE("发起人节点首次自动通过"), // 目前仅“子流程”使用
29 ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND("审批人与提交人为同一人时,找不到部门负责人,自动通过"), 30 ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND("审批人与提交人为同一人时,找不到部门负责人,自动通过"),
30 ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"), 31 ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"),
31 ASSIGN_EMPTY_APPROVE("审批人为空,自动通过"), 32 ASSIGN_EMPTY_APPROVE("审批人为空,自动通过"),
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApiImpl.java
@@ -2,11 +2,10 @@ package cn.iocoder.yudao.module.bpm.api.task; @@ -2,11 +2,10 @@ package cn.iocoder.yudao.module.bpm.api.task;
2 2
3 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; 3 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
4 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; 4 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
5 -import org.springframework.stereotype.Service;  
6 -import org.springframework.validation.annotation.Validated;  
7 -  
8 import jakarta.annotation.Resource; 5 import jakarta.annotation.Resource;
9 import jakarta.validation.Valid; 6 import jakarta.validation.Valid;
  7 +import org.springframework.stereotype.Service;
  8 +import org.springframework.validation.annotation.Validated;
10 9
11 /** 10 /**
12 * Flowable 流程实例 Api 实现类 11 * Flowable 流程实例 Api 实现类
@@ -25,4 +24,5 @@ public class BpmProcessInstanceApiImpl implements BpmProcessInstanceApi { @@ -25,4 +24,5 @@ public class BpmProcessInstanceApiImpl implements BpmProcessInstanceApi {
25 public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO reqDTO) { 24 public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO reqDTO) {
26 return processInstanceService.createProcessInstance(userId, reqDTO); 25 return processInstanceService.createProcessInstance(userId, reqDTO);
27 } 26 }
  27 +
28 } 28 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessTaskApiImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.api.task;
  2 +
  3 +import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
  4 +import jakarta.annotation.Resource;
  5 +import org.springframework.stereotype.Service;
  6 +import org.springframework.validation.annotation.Validated;
  7 +
  8 +/**
  9 + * 流程任务 Api 实现类
  10 + *
  11 + * @author jason
  12 + */
  13 +@Service
  14 +@Validated
  15 +public class BpmProcessTaskApiImpl implements BpmProcessTaskApi {
  16 +
  17 + @Resource
  18 + private BpmTaskService bpmTaskService;
  19 +
  20 + @Override
  21 + public void triggerTask(String processInstanceId, String taskDefineKey) {
  22 + bpmTaskService.triggerTask(processInstanceId, taskDefineKey);
  23 + }
  24 +
  25 +}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java
@@ -57,7 +57,7 @@ public class BpmModelController { @@ -57,7 +57,7 @@ public class BpmModelController {
57 @GetMapping("/list") 57 @GetMapping("/list")
58 @Operation(summary = "获得模型分页") 58 @Operation(summary = "获得模型分页")
59 @Parameter(name = "name", description = "模型名称", example = "芋艿") 59 @Parameter(name = "name", description = "模型名称", example = "芋艿")
60 - public CommonResult<List<BpmModelRespVO>> getModelPage(@RequestParam(value = "name", required = false) String name) { 60 + public CommonResult<List<BpmModelRespVO>> getModelList(@RequestParam(value = "name", required = false) String name) {
61 List<Model> list = modelService.getModelList(name); 61 List<Model> list = modelService.getModelList(name);
62 if (CollUtil.isEmpty(list)) { 62 if (CollUtil.isEmpty(list)) {
63 return success(Collections.emptyList()); 63 return success(Collections.emptyList());
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java
@@ -17,6 +17,7 @@ import io.swagger.v3.oas.annotations.Parameter; @@ -17,6 +17,7 @@ import io.swagger.v3.oas.annotations.Parameter;
17 import io.swagger.v3.oas.annotations.tags.Tag; 17 import io.swagger.v3.oas.annotations.tags.Tag;
18 import jakarta.annotation.Resource; 18 import jakarta.annotation.Resource;
19 import org.flowable.bpmn.model.BpmnModel; 19 import org.flowable.bpmn.model.BpmnModel;
  20 +import org.flowable.common.engine.impl.db.SuspensionState;
20 import org.flowable.engine.repository.Deployment; 21 import org.flowable.engine.repository.Deployment;
21 import org.flowable.engine.repository.ProcessDefinition; 22 import org.flowable.engine.repository.ProcessDefinition;
22 import org.springframework.security.access.prepost.PreAuthorize; 23 import org.springframework.security.access.prepost.PreAuthorize;
@@ -31,6 +32,7 @@ import java.util.List; @@ -31,6 +32,7 @@ import java.util.List;
31 import java.util.Map; 32 import java.util.Map;
32 33
33 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 34 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  35 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
34 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 36 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
35 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 37 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
36 38
@@ -99,6 +101,17 @@ public class BpmProcessDefinitionController { @@ -99,6 +101,17 @@ public class BpmProcessDefinitionController {
99 list, null, processDefinitionMap, null, null)); 101 list, null, processDefinitionMap, null, null));
100 } 102 }
101 103
  104 + @GetMapping("/simple-list")
  105 + @Operation(summary = "获得流程定义精简列表", description = "只包含未挂起的流程,主要用于前端的下拉选项")
  106 + public CommonResult<List<BpmProcessDefinitionRespVO>> getSimpleProcessDefinitionList() {
  107 + // 只查询未挂起的流程
  108 + List<ProcessDefinition> list = processDefinitionService.getProcessDefinitionListBySuspensionState(
  109 + SuspensionState.ACTIVE.getStateCode());
  110 + // 拼接 VO 返回,只返回 id、name、key
  111 + return success(convertList(list, definition -> new BpmProcessDefinitionRespVO()
  112 + .setId(definition.getId()).setName(definition.getName()).setKey(definition.getKey())));
  113 + }
  114 +
102 @GetMapping ("/get") 115 @GetMapping ("/get")
103 @Operation(summary = "获得流程定义") 116 @Operation(summary = "获得流程定义")
104 @Parameter(name = "id", description = "流程编号", required = true, example = "1024") 117 @Parameter(name = "id", description = "流程编号", required = true, example = "1024")
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model;
2 2
3 import cn.iocoder.yudao.framework.common.core.KeyValue; 3 import cn.iocoder.yudao.framework.common.core.KeyValue;
4 import cn.iocoder.yudao.framework.common.validation.InEnum; 4 import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
5 import cn.iocoder.yudao.module.bpm.enums.definition.BpmAutoApproveTypeEnum; 6 import cn.iocoder.yudao.module.bpm.enums.definition.BpmAutoApproveTypeEnum;
6 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; 7 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
7 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; 8 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum;
@@ -27,8 +28,7 @@ import java.util.List; @@ -27,8 +28,7 @@ import java.util.List;
27 @Data 28 @Data
28 public class BpmModelMetaInfoVO { 29 public class BpmModelMetaInfoVO {
29 30
30 - @Schema(description = "流程图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg")  
31 - @NotEmpty(message = "流程图标不能为空") 31 + @Schema(description = "流程图标", example = "https://www.iocoder.cn/yudao.jpg")
32 @URL(message = "流程图标格式不正确") 32 @URL(message = "流程图标格式不正确")
33 private String icon; 33 private String icon;
34 34
@@ -46,6 +46,7 @@ public class BpmModelMetaInfoVO { @@ -46,6 +46,7 @@ public class BpmModelMetaInfoVO {
46 private Integer formType; 46 private Integer formType;
47 @Schema(description = "表单编号", example = "1024") 47 @Schema(description = "表单编号", example = "1024")
48 private Long formId; // formType 为 NORMAL 使用,必须非空 48 private Long formId; // formType 为 NORMAL 使用,必须非空
  49 +
49 @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/create") 50 @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/create")
50 private String formCustomCreatePath; // 表单类型为 CUSTOM 时,必须非空 51 private String formCustomCreatePath; // 表单类型为 CUSTOM 时,必须非空
51 @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/view") 52 @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/view")
@@ -81,6 +82,14 @@ public class BpmModelMetaInfoVO { @@ -81,6 +82,14 @@ public class BpmModelMetaInfoVO {
81 @Schema(description = "摘要设置", example = "{}") 82 @Schema(description = "摘要设置", example = "{}")
82 private SummarySetting summarySetting; 83 private SummarySetting summarySetting;
83 84
  85 + // TODO @lesan:processBeforeTriggerSetting;要不叫这个?主要考虑,notify 留给后续的站内信、短信、邮件这种 notify 通知哈。
  86 + @Schema(description = "流程前置通知设置", example = "{}")
  87 + private HttpRequestSetting PreProcessNotifySetting;
  88 +
  89 + // TODO @lesan:processAfterTriggerSetting
  90 + @Schema(description = "流程后置通知设置", example = "{}")
  91 + private HttpRequestSetting PostProcessNotifySetting;
  92 +
84 @Schema(description = "流程 ID 规则") 93 @Schema(description = "流程 ID 规则")
85 @Data 94 @Data
86 @Valid 95 @Valid
@@ -133,4 +142,32 @@ public class BpmModelMetaInfoVO { @@ -133,4 +142,32 @@ public class BpmModelMetaInfoVO {
133 142
134 } 143 }
135 144
  145 + @Schema(description = "http 请求通知设置", example = "{}")
  146 + @Data
  147 + public static class HttpRequestSetting {
  148 +
  149 + @Schema(description = "请求路径", example = "http://127.0.0.1")
  150 + @NotEmpty(message = "请求 URL 不能为空")
  151 + @URL(message = "请求 URL 格式不正确")
  152 + private String url;
  153 +
  154 + @Schema(description = "请求头参数设置", example = "[]")
  155 + @Valid
  156 + private List<BpmSimpleModelNodeVO.HttpRequestParam> header;
  157 +
  158 + @Schema(description = "请求头参数设置", example = "[]")
  159 + @Valid
  160 + private List<BpmSimpleModelNodeVO.HttpRequestParam> body;
  161 +
  162 + /**
  163 + * 请求返回处理设置,用于修改流程表单值
  164 + * <p>
  165 + * key:表示要修改的流程表单字段名(name)
  166 + * value:接口返回的字段名
  167 + */
  168 + @Schema(description = "请求返回处理设置", example = "[]")
  169 + private List<KeyValue<String, String>> response;
  170 +
  171 + }
  172 +
136 } 173 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java
@@ -25,7 +25,7 @@ public class BpmModelRespVO extends BpmModelMetaInfoVO { @@ -25,7 +25,7 @@ public class BpmModelRespVO extends BpmModelMetaInfoVO {
25 @Schema(description = "流程图标", example = "https://www.iocoder.cn/yudao.jpg") 25 @Schema(description = "流程图标", example = "https://www.iocoder.cn/yudao.jpg")
26 private String icon; 26 private String icon;
27 27
28 - @Schema(description = "流程分类编码", example = "1") 28 + @Schema(description = "流程分类编号", example = "1")
29 private String category; 29 private String category;
30 @Schema(description = "流程分类名字", example = "请假") 30 @Schema(description = "流程分类名字", example = "请假")
31 private String categoryName; 31 private String categoryName;
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
@@ -4,16 +4,19 @@ import cn.iocoder.yudao.framework.common.core.KeyValue; @@ -4,16 +4,19 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
4 import cn.iocoder.yudao.framework.common.validation.InEnum; 4 import cn.iocoder.yudao.framework.common.validation.InEnum;
5 import cn.iocoder.yudao.module.bpm.enums.definition.*; 5 import cn.iocoder.yudao.module.bpm.enums.definition.*;
6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
  7 +import com.fasterxml.jackson.annotation.JsonIgnore;
7 import com.fasterxml.jackson.annotation.JsonInclude; 8 import com.fasterxml.jackson.annotation.JsonInclude;
8 import io.swagger.v3.oas.annotations.media.Schema; 9 import io.swagger.v3.oas.annotations.media.Schema;
9 import jakarta.validation.Valid; 10 import jakarta.validation.Valid;
10 import jakarta.validation.constraints.NotEmpty; 11 import jakarta.validation.constraints.NotEmpty;
11 import jakarta.validation.constraints.NotNull; 12 import jakarta.validation.constraints.NotNull;
12 import lombok.Data; 13 import lombok.Data;
  14 +import org.flowable.bpmn.model.IOParameter;
13 import org.hibernate.validator.constraints.URL; 15 import org.hibernate.validator.constraints.URL;
14 16
15 import java.util.List; 17 import java.util.List;
16 import java.util.Map; 18 import java.util.Map;
  19 +import java.util.Set;
17 20
18 @Schema(description = "管理后台 - 仿钉钉流程设计模型节点 VO") 21 @Schema(description = "管理后台 - 仿钉钉流程设计模型节点 VO")
19 @Data 22 @Data
@@ -114,7 +117,8 @@ public class BpmSimpleModelNodeVO { @@ -114,7 +117,8 @@ public class BpmSimpleModelNodeVO {
114 @Schema(description = "路由分支组", example = "[]") 117 @Schema(description = "路由分支组", example = "[]")
115 private List<RouterSetting> routerGroups; 118 private List<RouterSetting> routerGroups;
116 119
117 - @Schema(description = "路由分支默认分支 ID", example = "Flow_xxx", hidden = true) // 由后端生成,所以 hidden = true 120 + @Schema(description = "路由分支默认分支 ID", example = "Flow_xxx", hidden = true) // 由后端生成(不从前端传递),所以 hidden = true
  121 + @JsonIgnore
118 private String routerDefaultFlowId; // 仅用于路由分支节点 BpmSimpleModelNodeType.ROUTER_BRANCH_NODE 122 private String routerDefaultFlowId; // 仅用于路由分支节点 BpmSimpleModelNodeType.ROUTER_BRANCH_NODE
119 123
120 /** 124 /**
@@ -122,6 +126,15 @@ public class BpmSimpleModelNodeVO { @@ -122,6 +126,15 @@ public class BpmSimpleModelNodeVO {
122 */ 126 */
123 private TriggerSetting triggerSetting; 127 private TriggerSetting triggerSetting;
124 128
  129 + @Schema(description = "附加节点 Id", example = "UserTask_xxx", hidden = true) // 由后端生成(不从前端传递),所以 hidden = true
  130 + @JsonIgnore
  131 + private String attachNodeId; // 目前用于触发器节点(HTTP 回调)。需要 UserTask 和 ReceiveTask(附加节点) 来完成
  132 +
  133 + /**
  134 + * 子流程设置
  135 + */
  136 + private ChildProcessSetting childProcessSetting;
  137 +
125 @Schema(description = "任务监听器") 138 @Schema(description = "任务监听器")
126 @Valid 139 @Valid
127 @Data 140 @Data
@@ -345,12 +358,10 @@ public class BpmSimpleModelNodeVO { @@ -345,12 +358,10 @@ public class BpmSimpleModelNodeVO {
345 @Valid 358 @Valid
346 private HttpRequestTriggerSetting httpRequestSetting; 359 private HttpRequestTriggerSetting httpRequestSetting;
347 360
348 - // TODO @jason:这个要不直接叫 formSetting,更好理解一点哈  
349 - // TODO @jason:如果搞成 List<NormalFormTriggerSetting>,是不是可以做条件组了?微信讨论哈  
350 /** 361 /**
351 * 流程表单触发器设置 362 * 流程表单触发器设置
352 */ 363 */
353 - private NormalFormTriggerSetting normalFormSetting; 364 + private List<FormTriggerSetting> formSettings;
354 365
355 @Schema(description = "http 请求触发器设置", example = "{}") 366 @Schema(description = "http 请求触发器设置", example = "{}")
356 @Data 367 @Data
@@ -369,7 +380,6 @@ public class BpmSimpleModelNodeVO { @@ -369,7 +380,6 @@ public class BpmSimpleModelNodeVO {
369 @Valid 380 @Valid
370 private List<HttpRequestParam> body; 381 private List<HttpRequestParam> body;
371 382
372 - // TODO @json:可能未来看情况,搞个 HttpResponseParam;得看看有没别的业务需要,抽象统一  
373 /** 383 /**
374 * 请求返回处理设置,用于修改流程表单值 384 * 请求返回处理设置,用于修改流程表单值
375 * <p> 385 * <p>
@@ -379,15 +389,137 @@ public class BpmSimpleModelNodeVO { @@ -379,15 +389,137 @@ public class BpmSimpleModelNodeVO {
379 @Schema(description = "请求返回处理设置", example = "[]") 389 @Schema(description = "请求返回处理设置", example = "[]")
380 private List<KeyValue<String, String>> response; 390 private List<KeyValue<String, String>> response;
381 391
  392 + /**
  393 + * Http 回调请求,需要指定回调任务 Key,用于回调执行
  394 + */
  395 + @Schema(description = "回调任务 Key", example = "xxx", hidden = true)
  396 + private String callbackTaskDefineKey;
  397 +
382 } 398 }
383 399
384 @Schema(description = "流程表单触发器设置", example = "{}") 400 @Schema(description = "流程表单触发器设置", example = "{}")
385 @Data 401 @Data
386 - public static class NormalFormTriggerSetting { 402 + public static class FormTriggerSetting {
387 403
388 - @Schema(description = "修改的表单字段", example = "userName") 404 + @Schema(description = "条件类型", example = "1")
  405 + @InEnum(BpmSimpleModeConditionTypeEnum.class)
  406 + private Integer conditionType;
  407 +
  408 + @Schema(description = "条件表达式", example = "${day>3}")
  409 + private String conditionExpression;
  410 +
  411 + @Schema(description = "条件组", example = "{}")
  412 + private ConditionGroups conditionGroups;
  413 +
  414 + @Schema(description = "修改的表单字段", example = "{}")
389 private Map<String, Object> updateFormFields; 415 private Map<String, Object> updateFormFields;
390 416
  417 + @Schema(description = "删除表单字段", example = "[]")
  418 + private Set<String> deleteFields;
  419 + }
  420 + }
  421 +
  422 + @Schema(description = "子流程节点配置")
  423 + @Data
  424 + @Valid
  425 + public static class ChildProcessSetting {
  426 +
  427 + @Schema(description = "被调用流程", requiredMode = Schema.RequiredMode.REQUIRED, example = "xxx")
  428 + @NotEmpty(message = "被调用流程不能为空")
  429 + private String calledProcessDefinitionKey;
  430 +
  431 + @Schema(description = "被调用流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "xxx")
  432 + @NotEmpty(message = "被调用流程名称不能为空")
  433 + private String calledProcessDefinitionName;
  434 +
  435 + @Schema(description = "是否异步", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  436 + @NotNull(message = "是否异步不能为空")
  437 + private Boolean async;
  438 +
  439 + @Schema(description = "输入参数(主->子)", example = "[]")
  440 + private List<IOParameter> inVariables;
  441 +
  442 + @Schema(description = "输出参数(子->主)", example = "[]")
  443 + private List<IOParameter> outVariables;
  444 +
  445 + @Schema(description = "是否自动跳过子流程发起节点", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  446 + @NotNull(message = "是否自动跳过子流程发起节点不能为空")
  447 + private Boolean skipStartUserNode;
  448 +
  449 + @Schema(description = "子流程发起人配置", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}")
  450 + @NotNull(message = "子流程发起人配置不能为空")
  451 + private StartUserSetting startUserSetting;
  452 +
  453 + @Schema(description = "超时设置", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}")
  454 + private TimeoutSetting timeoutSetting;
  455 +
  456 + @Schema(description = "多实例设置", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}")
  457 + private MultiInstanceSetting multiInstanceSetting;
  458 +
  459 + @Schema(description = "子流程发起人配置")
  460 + @Data
  461 + @Valid
  462 + public static class StartUserSetting {
  463 +
  464 + @Schema(description = "子流程发起人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  465 + @NotNull(message = "子流程发起人类型")
  466 + @InEnum(BpmChildProcessStartUserTypeEnum.class)
  467 + private Integer type;
  468 +
  469 + @Schema(description = "表单", example = "xxx")
  470 + private String formField;
  471 +
  472 + @Schema(description = "当子流程发起人为空时类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  473 + @NotNull(message = "当子流程发起人为空时类型不能为空")
  474 + @InEnum(BpmChildProcessStartUserEmptyTypeEnum.class)
  475 + private Integer emptyType;
  476 +
  477 + }
  478 +
  479 + @Schema(description = "超时设置")
  480 + @Data
  481 + @Valid
  482 + public static class TimeoutSetting {
  483 +
  484 + @Schema(description = "是否开启超时设置", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  485 + @NotNull(message = "是否开启超时设置不能为空")
  486 + private Boolean enable;
  487 +
  488 + @Schema(description = "时间类型", example = "1")
  489 + @InEnum(BpmDelayTimerTypeEnum.class)
  490 + private Integer type;
  491 +
  492 + @Schema(description = "时间表达式", example = "PT1H,2025-01-01T00:00:00")
  493 + private String timeExpression;
  494 +
  495 + }
  496 +
  497 + @Schema(description = "多实例设置")
  498 + @Data
  499 + @Valid
  500 + public static class MultiInstanceSetting {
  501 +
  502 + @Schema(description = "是否开启多实例", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  503 + @NotNull(message = "是否开启多实例不能为空")
  504 + private Boolean enable;
  505 +
  506 + @Schema(description = "是否串行", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  507 + @NotNull(message = "是否串行不能为空")
  508 + private Boolean sequential;
  509 +
  510 + @Schema(description = "完成比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
  511 + @NotNull(message = "完成比例不能为空")
  512 + private Integer approveRatio;
  513 +
  514 + @Schema(description = "多实例来源类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  515 + @NotNull(message = "多实例来源类型不能为空")
  516 + @InEnum(BpmChildProcessMultiInstanceSourceTypeEnum.class)
  517 + private Integer sourceType;
  518 +
  519 + @Schema(description = "多实例来源", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  520 + @NotNull(message = "多实例来源不能为空")
  521 + private String source;
  522 +
391 } 523 }
392 524
393 } 525 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java
1 package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process; 1 package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process;
2 2
  3 +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
3 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 5 import lombok.Data;
5 6
@@ -8,7 +9,7 @@ import java.util.List; @@ -8,7 +9,7 @@ import java.util.List;
8 9
9 @Schema(description = "管理后台 - 流程定义 Response VO") 10 @Schema(description = "管理后台 - 流程定义 Response VO")
10 @Data 11 @Data
11 -public class BpmProcessDefinitionRespVO { 12 +public class BpmProcessDefinitionRespVO extends BpmModelMetaInfoVO {
12 13
13 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") 14 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
14 private String id; 15 private String id;
@@ -19,15 +20,9 @@ public class BpmProcessDefinitionRespVO { @@ -19,15 +20,9 @@ public class BpmProcessDefinitionRespVO {
19 @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") 20 @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
20 private String name; 21 private String name;
21 22
22 - @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao") 23 + @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "youdao")
23 private String key; 24 private String key;
24 25
25 - @Schema(description = "流程图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg")  
26 - private String icon;  
27 -  
28 - @Schema(description = "流程描述", example = "我是描述")  
29 - private String description;  
30 -  
31 @Schema(description = "流程分类", example = "1") 26 @Schema(description = "流程分类", example = "1")
32 private String category; 27 private String category;
33 @Schema(description = "流程分类名字", example = "请假") 28 @Schema(description = "流程分类名字", example = "请假")
@@ -36,22 +31,15 @@ public class BpmProcessDefinitionRespVO { @@ -36,22 +31,15 @@ public class BpmProcessDefinitionRespVO {
36 @Schema(description = "流程模型的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") 31 @Schema(description = "流程模型的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
37 private Integer modelType; // 参见 BpmModelTypeEnum 枚举类 32 private Integer modelType; // 参见 BpmModelTypeEnum 枚举类
38 33
39 - @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1")  
40 - private Integer formType;  
41 - @Schema(description = "表单编号-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", example = "1024")  
42 - private Long formId;  
43 - @Schema(description = "表单名字", example = "请假表单")  
44 - private String formName; 34 + @Schema(description = "流程模型的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "ABC")
  35 + private String modelId;
  36 +
45 @Schema(description = "表单的配置-JSON 字符串。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", requiredMode = Schema.RequiredMode.REQUIRED) 37 @Schema(description = "表单的配置-JSON 字符串。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", requiredMode = Schema.RequiredMode.REQUIRED)
46 private String formConf; 38 private String formConf;
47 @Schema(description = "表单项的数组-JSON 字符串的数组。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", requiredMode = Schema.RequiredMode.REQUIRED) 39 @Schema(description = "表单项的数组-JSON 字符串的数组。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", requiredMode = Schema.RequiredMode.REQUIRED)
48 private List<String> formFields; 40 private List<String> formFields;
49 - @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空",  
50 - example = "/bpm/oa/leave/create")  
51 - private String formCustomCreatePath;  
52 - @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空",  
53 - example = "/bpm/oa/leave/view")  
54 - private String formCustomViewPath; 41 + @Schema(description = "表单名字", example = "请假表单")
  42 + private String formName;
55 43
56 @Schema(description = "中断状态-参见 SuspensionState 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 44 @Schema(description = "中断状态-参见 SuspensionState 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
57 private Integer suspensionState; // 参见 SuspensionState 枚举 45 private Integer suspensionState; // 参见 SuspensionState 枚举
@@ -67,7 +55,7 @@ public class BpmProcessDefinitionRespVO { @@ -67,7 +55,7 @@ public class BpmProcessDefinitionRespVO {
67 55
68 @Schema(description = "流程定义排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") 56 @Schema(description = "流程定义排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
69 private Long sort; 57 private Long sort;
70 - 58 +
71 @Schema(description = "BPMN UserTask 用户任务") 59 @Schema(description = "BPMN UserTask 用户任务")
72 @Data 60 @Data
73 public static class UserTask { 61 public static class UserTask {
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
1 package cn.iocoder.yudao.module.bpm.controller.admin.task; 1 package cn.iocoder.yudao.module.bpm.controller.admin.task;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.StrUtil;
4 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
6 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 8 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
7 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; 9 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
8 import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; 10 import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert;
@@ -30,10 +32,10 @@ import org.springframework.web.bind.annotation.*; @@ -30,10 +32,10 @@ import org.springframework.web.bind.annotation.*;
30 32
31 import java.util.List; 33 import java.util.List;
32 import java.util.Map; 34 import java.util.Map;
  35 +import java.util.Set;
33 36
34 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 37 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
35 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;  
36 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 38 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
37 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 39 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
38 40
39 @Tag(name = "管理后台 - 流程实例") // 流程实例,通过流程定义创建的一次“申请” 41 @Tag(name = "管理后台 - 流程实例") // 流程实例,通过流程定义创建的一次“申请”
@@ -76,8 +78,14 @@ public class BpmProcessInstanceController { @@ -76,8 +78,14 @@ public class BpmProcessInstanceController {
76 convertSet(processDefinitionMap.values(), ProcessDefinition::getCategory)); 78 convertSet(processDefinitionMap.values(), ProcessDefinition::getCategory));
77 Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap( 79 Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap(
78 convertSet(pageResult.getList(), HistoricProcessInstance::getProcessDefinitionId)); 80 convertSet(pageResult.getList(), HistoricProcessInstance::getProcessDefinitionId));
  81 + Set<Long> userIds = convertSet(pageResult.getList(), processInstance -> NumberUtils.parseLong(processInstance.getStartUserId()));
  82 + userIds.addAll(convertSetByFlatMap(taskMap.values(),
  83 + tasks -> tasks.stream().map(Task::getAssignee).filter(StrUtil::isNotBlank).map(Long::parseLong)));
  84 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
  85 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
  86 + convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
79 return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstancePage(pageResult, 87 return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstancePage(pageResult,
80 - processDefinitionMap, categoryMap, taskMap, null, null, processDefinitionInfoMap)); 88 + processDefinitionMap, categoryMap, taskMap, userMap, deptMap, processDefinitionInfoMap));
81 } 89 }
82 90
83 @GetMapping("/manager-page") 91 @GetMapping("/manager-page")
@@ -140,6 +148,7 @@ public class BpmProcessInstanceController { @@ -140,6 +148,7 @@ public class BpmProcessInstanceController {
140 processDefinition, processDefinitionInfo, startUser, dept)); 148 processDefinition, processDefinitionInfo, startUser, dept));
141 } 149 }
142 150
  151 + // TODO @lesan:【子流程】子流程如果取消,主流程应该是通过、还是不通过哈?还是禁用掉子流程的取消?
143 @DeleteMapping("/cancel-by-start-user") 152 @DeleteMapping("/cancel-by-start-user")
144 @Operation(summary = "用户取消流程实例", description = "取消发起的流程") 153 @Operation(summary = "用户取消流程实例", description = "取消发起的流程")
145 @PreAuthorize("@ss.hasPermission('bpm:process-instance:cancel')") 154 @PreAuthorize("@ss.hasPermission('bpm:process-instance:cancel')")
@@ -162,10 +171,25 @@ public class BpmProcessInstanceController { @@ -162,10 +171,25 @@ public class BpmProcessInstanceController {
162 @Operation(summary = "获得审批详情") 171 @Operation(summary = "获得审批详情")
163 @Parameter(name = "id", description = "流程实例的编号", required = true) 172 @Parameter(name = "id", description = "流程实例的编号", required = true)
164 @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") 173 @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
  174 + @SuppressWarnings("unchecked")
165 public CommonResult<BpmApprovalDetailRespVO> getApprovalDetail(@Valid BpmApprovalDetailReqVO reqVO) { 175 public CommonResult<BpmApprovalDetailRespVO> getApprovalDetail(@Valid BpmApprovalDetailReqVO reqVO) {
  176 + if (StrUtil.isNotEmpty(reqVO.getProcessVariablesStr())) {
  177 + reqVO.setProcessVariables(JsonUtils.parseObject(reqVO.getProcessVariablesStr(), Map.class));
  178 + }
166 return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO)); 179 return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO));
167 } 180 }
168 181
  182 + @GetMapping("/get-next-approval-nodes")
  183 + @Operation(summary = "获取下一个执行的流程节点")
  184 + @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
  185 + @SuppressWarnings("unchecked")
  186 + public CommonResult<List<BpmApprovalDetailRespVO.ActivityNode>> getNextApprovalNodes(@Valid BpmApprovalDetailReqVO reqVO) {
  187 + if (StrUtil.isNotEmpty(reqVO.getProcessVariablesStr())) {
  188 + reqVO.setProcessVariables(JsonUtils.parseObject(reqVO.getProcessVariablesStr(), Map.class));
  189 + }
  190 + return success(processInstanceService.getNextApprovalNodes(getLoginUserId(), reqVO));
  191 + }
  192 +
169 @GetMapping("/get-bpmn-model-view") 193 @GetMapping("/get-bpmn-model-view")
170 @Operation(summary = "获取流程实例的 BPMN 模型视图", description = "在【流程详细】界面中,进行调用") 194 @Operation(summary = "获取流程实例的 BPMN 模型视图", description = "在【流程详细】界面中,进行调用")
171 @Parameter(name = "id", description = "流程实例的编号", required = true) 195 @Parameter(name = "id", description = "流程实例的编号", required = true)
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java
@@ -18,6 +18,9 @@ public class BpmApprovalDetailReqVO { @@ -18,6 +18,9 @@ public class BpmApprovalDetailReqVO {
18 @Schema(description = "流程变量") 18 @Schema(description = "流程变量")
19 private Map<String, Object> processVariables; // 使用场景:同 processDefinitionId,用于流程预测 19 private Map<String, Object> processVariables; // 使用场景:同 processDefinitionId,用于流程预测
20 20
  21 + @Schema(description = "流程变量")
  22 + private String processVariablesStr; // 解决 GET 无法传递对象的问题,最终转换成 processVariables 变量
  23 +
21 @Schema(description = "流程实例的编号", example = "1024") 24 @Schema(description = "流程实例的编号", example = "1024")
22 private String processInstanceId; // 使用场景:流程已发起时候传流程实例 ID 25 private String processInstanceId; // 使用场景:流程已发起时候传流程实例 ID
23 26
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance;
3 import cn.iocoder.yudao.framework.common.core.KeyValue; 3 import cn.iocoder.yudao.framework.common.core.KeyValue;
4 import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; 4 import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
5 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; 5 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
  6 +import com.fasterxml.jackson.annotation.JsonIgnore;
6 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
7 import lombok.Data; 8 import lombok.Data;
8 9
@@ -73,6 +74,13 @@ public class BpmProcessInstanceRespVO { @@ -73,6 +74,13 @@ public class BpmProcessInstanceRespVO {
73 @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") 74 @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
74 private String name; 75 private String name;
75 76
  77 + @Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048")
  78 + @JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser
  79 + private Long assignee;
  80 +
  81 + @Schema(description = "任务分配人", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048")
  82 + private UserSimpleBaseVO assigneeUser;
  83 +
76 } 84 }
77 85
78 } 86 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java
@@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema; @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
4 import jakarta.validation.constraints.NotEmpty; 4 import jakarta.validation.constraints.NotEmpty;
5 import lombok.Data; 5 import lombok.Data;
6 6
  7 +import java.util.List;
7 import java.util.Map; 8 import java.util.Map;
8 9
9 @Schema(description = "管理后台 - 通过流程任务的 Request VO") 10 @Schema(description = "管理后台 - 通过流程任务的 Request VO")
@@ -23,4 +24,7 @@ public class BpmTaskApproveReqVO { @@ -23,4 +24,7 @@ public class BpmTaskApproveReqVO {
23 @Schema(description = "变量实例(动态表单)", requiredMode = Schema.RequiredMode.REQUIRED) 24 @Schema(description = "变量实例(动态表单)", requiredMode = Schema.RequiredMode.REQUIRED)
24 private Map<String, Object> variables; 25 private Map<String, Object> variables;
25 26
  27 + @Schema(description = "下一个节点审批人", example = "{nodeId:[1, 2]}")
  28 + private Map<String, List<Long>> nextAssignees; // 为什么是 Map,而不是 List 呢?因为下一个节点可能是多个,例如说并行网关的情况
  29 +
26 } 30 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java
@@ -18,6 +18,9 @@ public class BpmTaskPageReqVO extends PageParam { @@ -18,6 +18,9 @@ public class BpmTaskPageReqVO extends PageParam {
18 @Schema(description = "流程分类", example = "1") 18 @Schema(description = "流程分类", example = "1")
19 private String category; 19 private String category;
20 20
  21 + @Schema(description = "流程定义的标识", example = "2048")
  22 + private String processDefinitionKey; // 精准匹配
  23 +
21 @Schema(description = "创建时间") 24 @Schema(description = "创建时间")
22 @DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) 25 @DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
23 private LocalDateTime[] createTime; 26 private LocalDateTime[] createTime;
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java
@@ -85,6 +85,9 @@ public class BpmTaskRespVO { @@ -85,6 +85,9 @@ public class BpmTaskRespVO {
85 @Schema(description = "是否填写审批意见", example = "false") 85 @Schema(description = "是否填写审批意见", example = "false")
86 private Boolean reasonRequire; 86 private Boolean reasonRequire;
87 87
  88 + @Schema(description = "节点类型", example = "10")
  89 + private Integer nodeType; // 参见 BpmSimpleModelNodeTypeEnum 枚举。
  90 +
88 @Data 91 @Data
89 @Schema(description = "流程实例") 92 @Schema(description = "流程实例")
90 public static class ProcessInstance { 93 public static class ProcessInstance {
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java
@@ -76,6 +76,15 @@ public interface BpmProcessInstanceConvert { @@ -76,6 +76,15 @@ public interface BpmProcessInstanceConvert {
76 respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); 76 respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
77 MapUtils.findAndThen(deptMap, startUser.getDeptId(), dept -> respVO.getStartUser().setDeptName(dept.getName())); 77 MapUtils.findAndThen(deptMap, startUser.getDeptId(), dept -> respVO.getStartUser().setDeptName(dept.getName()));
78 } 78 }
  79 + if (CollUtil.isNotEmpty(respVO.getTasks())) {
  80 + respVO.getTasks().forEach(task -> {
  81 + AdminUserRespDTO assigneeUser = userMap.get(task.getAssignee());
  82 + if (assigneeUser!= null) {
  83 + task.setAssigneeUser(BeanUtils.toBean(assigneeUser, UserSimpleBaseVO.class));
  84 + MapUtils.findAndThen(deptMap, assigneeUser.getDeptId(), dept -> task.getAssigneeUser().setDeptName(dept.getName()));
  85 + }
  86 + });
  87 + }
79 } 88 }
80 // 摘要 89 // 摘要
81 respVO.setSummary(FlowableUtils.getSummary(processDefinitionInfoMap.get(respVO.getProcessDefinitionId()), 90 respVO.setSummary(FlowableUtils.getSummary(processDefinitionInfoMap.get(respVO.getProcessDefinitionId()),
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.bpm.dal.dataobject.definition; @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.bpm.dal.dataobject.definition;
2 2
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; 4 import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
5 -import cn.iocoder.yudao.framework.mybatis.core.type.StringListTypeHandler;  
6 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; 5 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
7 import cn.iocoder.yudao.module.bpm.enums.definition.BpmAutoApproveTypeEnum; 6 import cn.iocoder.yudao.module.bpm.enums.definition.BpmAutoApproveTypeEnum;
8 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; 7 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
@@ -61,6 +60,14 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { @@ -61,6 +60,14 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
61 private Integer modelType; 60 private Integer modelType;
62 61
63 /** 62 /**
  63 + * 流程分类的编码
  64 + *
  65 + * 关联 {@link BpmCategoryDO#getCode()}
  66 + *
  67 + * 为什么要存储?原因是,{@link ProcessDefinition#getCategory()} 无法设置
  68 + */
  69 + private String category;
  70 + /**
64 * 图标 71 * 图标
65 */ 72 */
66 private String icon; 73 private String icon;
@@ -149,7 +156,7 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { @@ -149,7 +156,7 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
149 * 156 *
150 * 关联 {@link AdminUserRespDTO#getId()} 字段的数组 157 * 关联 {@link AdminUserRespDTO#getId()} 字段的数组
151 */ 158 */
152 - @TableField(typeHandler = StringListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤 159 + @TableField(typeHandler = LongListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤
153 private List<Long> managerUserIds; 160 private List<Long> managerUserIds;
154 161
155 /** 162 /**
@@ -175,11 +182,22 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { @@ -175,11 +182,22 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
175 */ 182 */
176 @TableField(typeHandler = JacksonTypeHandler.class) 183 @TableField(typeHandler = JacksonTypeHandler.class)
177 private BpmModelMetaInfoVO.TitleSetting titleSetting; 184 private BpmModelMetaInfoVO.TitleSetting titleSetting;
178 -  
179 /** 185 /**
180 * 摘要设置 186 * 摘要设置
181 */ 187 */
182 @TableField(typeHandler = JacksonTypeHandler.class) 188 @TableField(typeHandler = JacksonTypeHandler.class)
183 private BpmModelMetaInfoVO.SummarySetting summarySetting; 189 private BpmModelMetaInfoVO.SummarySetting summarySetting;
184 190
  191 + // TODO @lesan:processBeforeTriggerSetting;要不叫这个?主要考虑,notify 留给后续的站内信、短信、邮件这种 notify 通知哈。
  192 + /**
  193 + * 流程前置通知设置
  194 + */
  195 + @TableField(typeHandler = JacksonTypeHandler.class, exist = false) // TODO @芋艿:临时注释 exist,因为要合并 master-jdk17
  196 + private BpmModelMetaInfoVO.HttpRequestSetting PreProcessNotifySetting;
  197 + /**
  198 + * 流程后置通知设置
  199 + */
  200 + @TableField(typeHandler = JacksonTypeHandler.class, exist = false) // TODO @芋艿:临时注释 exist,因为要合并 master-jdk17
  201 + private BpmModelMetaInfoVO.HttpRequestSetting PostProcessNotifySetting;
  202 +
185 } 203 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior; 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
  5 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmChildProcessMultiInstanceSourceTypeEnum;
4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
  7 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; 8 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
6 -import cn.iocoder.yudao.framework.common.util.collection.SetUtils;  
7 import lombok.Setter; 9 import lombok.Setter;
8 import org.flowable.bpmn.model.Activity; 10 import org.flowable.bpmn.model.Activity;
  11 +import org.flowable.bpmn.model.CallActivity;
  12 +import org.flowable.bpmn.model.FlowElement;
  13 +import org.flowable.bpmn.model.UserTask;
9 import org.flowable.engine.delegate.DelegateExecution; 14 import org.flowable.engine.delegate.DelegateExecution;
10 import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior; 15 import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
11 import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior; 16 import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
12 17
  18 +import java.util.List;
13 import java.util.Set; 19 import java.util.Set;
14 20
15 /** 21 /**
@@ -42,27 +48,44 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav @@ -42,27 +48,44 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
42 */ 48 */
43 @Override 49 @Override
44 protected int resolveNrOfInstances(DelegateExecution execution) { 50 protected int resolveNrOfInstances(DelegateExecution execution) {
45 - // 第一步,设置 collectionVariable 和 CollectionVariable  
46 - // 从 execution.getVariable() 读取所有任务处理人的 key  
47 - super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的  
48 - super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());  
49 - // 从 execution.getVariable() 读取当前所有任务处理的人的 key  
50 - super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId()); 51 + // 情况一:UserTask 节点
  52 + if (execution.getCurrentFlowElement() instanceof UserTask) {
  53 + // 第一步,设置 collectionVariable 和 CollectionVariable
  54 + // 从 execution.getVariable() 读取所有任务处理人的 key
  55 + super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的
  56 + super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
  57 + // 从 execution.getVariable() 读取当前所有任务处理的人的 key
  58 + super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
51 59
52 - // 第二步,获取任务的所有处理人  
53 - @SuppressWarnings("unchecked")  
54 - Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);  
55 - if (assigneeUserIds == null) {  
56 - assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);  
57 - if (CollUtil.isEmpty(assigneeUserIds)) {  
58 - // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!  
59 - // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务  
60 - // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时  
61 - assigneeUserIds = SetUtils.asSet((Long) null); 60 + // 第二步,获取任务的所有处理人
  61 + @SuppressWarnings("unchecked")
  62 + Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
  63 + if (assigneeUserIds == null) {
  64 + assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
  65 + if (CollUtil.isEmpty(assigneeUserIds)) {
  66 + // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
  67 + // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
  68 + // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
  69 + assigneeUserIds = SetUtils.asSet((Long) null);
  70 + }
  71 + execution.setVariableLocal(super.collectionVariable, assigneeUserIds);
62 } 72 }
63 - execution.setVariableLocal(super.collectionVariable, assigneeUserIds); 73 + return assigneeUserIds.size();
64 } 74 }
65 - return assigneeUserIds.size(); 75 +
  76 + // 情况二:CallActivity 节点
  77 + if (execution.getCurrentFlowElement() instanceof CallActivity) {
  78 + FlowElement flowElement = execution.getCurrentFlowElement();
  79 + Integer sourceType = BpmnModelUtils.parseMultiInstanceSourceType(flowElement);
  80 + if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM.getType())) {
  81 + return execution.getVariable(super.collectionExpression.getExpressionText(), Integer.class);
  82 + }
  83 + if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM.getType())) {
  84 + return execution.getVariable(super.collectionExpression.getExpressionText(), List.class).size();
  85 + }
  86 + }
  87 +
  88 + return super.resolveNrOfInstances(execution);
66 } 89 }
67 90
68 } 91 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java
@@ -2,14 +2,20 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior; @@ -2,14 +2,20 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.iocoder.yudao.framework.common.util.collection.SetUtils; 4 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
  5 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmChildProcessMultiInstanceSourceTypeEnum;
5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
  7 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; 8 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
7 import lombok.Setter; 9 import lombok.Setter;
8 import org.flowable.bpmn.model.Activity; 10 import org.flowable.bpmn.model.Activity;
  11 +import org.flowable.bpmn.model.CallActivity;
  12 +import org.flowable.bpmn.model.FlowElement;
  13 +import org.flowable.bpmn.model.UserTask;
9 import org.flowable.engine.delegate.DelegateExecution; 14 import org.flowable.engine.delegate.DelegateExecution;
10 import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior; 15 import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
11 import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior; 16 import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
12 17
  18 +import java.util.List;
13 import java.util.Set; 19 import java.util.Set;
14 20
15 /** 21 /**
@@ -35,28 +41,45 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB @@ -35,28 +41,45 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB
35 */ 41 */
36 @Override 42 @Override
37 protected int resolveNrOfInstances(DelegateExecution execution) { 43 protected int resolveNrOfInstances(DelegateExecution execution) {
38 - // 第一步,设置 collectionVariable 和 CollectionVariable  
39 - // 从 execution.getVariable() 读取所有任务处理人的 key  
40 - super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的  
41 - super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());  
42 - // 从 execution.getVariable() 读取当前所有任务处理的人的 key  
43 - super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());  
44 -  
45 - // 第二步,获取任务的所有处理人  
46 - // 不使用 execution.getVariable 原因:目前依次审批任务回退后 collectionVariable 变量没有清理, 如果重新进入该任务不会重新分配审批人  
47 - @SuppressWarnings("unchecked")  
48 - Set<Long> assigneeUserIds = (Set<Long>) execution.getVariableLocal(super.collectionVariable, Set.class);  
49 - if (assigneeUserIds == null) {  
50 - assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);  
51 - if (CollUtil.isEmpty(assigneeUserIds)) {  
52 - // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!  
53 - // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务  
54 - // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时  
55 - assigneeUserIds = SetUtils.asSet((Long) null); 44 + // 情况一:UserTask 节点
  45 + if (execution.getCurrentFlowElement() instanceof UserTask) {
  46 + // 第一步,设置 collectionVariable 和 CollectionVariable
  47 + // 从 execution.getVariable() 读取所有任务处理人的 key
  48 + super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的
  49 + super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
  50 + // 从 execution.getVariable() 读取当前所有任务处理的人的 key
  51 + super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
  52 +
  53 + // 第二步,获取任务的所有处理人
  54 + // 不使用 execution.getVariable 原因:目前依次审批任务回退后 collectionVariable 变量没有清理, 如果重新进入该任务不会重新分配审批人
  55 + @SuppressWarnings("unchecked")
  56 + Set<Long> assigneeUserIds = (Set<Long>) execution.getVariableLocal(super.collectionVariable, Set.class);
  57 + if (assigneeUserIds == null) {
  58 + assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
  59 + if (CollUtil.isEmpty(assigneeUserIds)) {
  60 + // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
  61 + // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
  62 + // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
  63 + assigneeUserIds = SetUtils.asSet((Long) null);
  64 + }
  65 + execution.setVariableLocal(super.collectionVariable, assigneeUserIds);
  66 + }
  67 + return assigneeUserIds.size();
  68 + }
  69 +
  70 + // 情况二:CallActivity 节点
  71 + if (execution.getCurrentFlowElement() instanceof CallActivity) {
  72 + FlowElement flowElement = execution.getCurrentFlowElement();
  73 + Integer sourceType = BpmnModelUtils.parseMultiInstanceSourceType(flowElement);
  74 + if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM.getType())) {
  75 + return execution.getVariable(super.collectionExpression.getExpressionText(), Integer.class);
  76 + }
  77 + if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM.getType())) {
  78 + return execution.getVariable(super.collectionExpression.getExpressionText(), List.class).size();
56 } 79 }
57 - execution.setVariableLocal(super.collectionVariable, assigneeUserIds);  
58 } 80 }
59 - return assigneeUserIds.size(); 81 +
  82 + return super.resolveNrOfInstances(execution);
60 } 83 }
61 84
62 } 85 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java
@@ -19,6 +19,7 @@ import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; @@ -19,6 +19,7 @@ import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
19 import com.google.common.annotations.VisibleForTesting; 19 import com.google.common.annotations.VisibleForTesting;
20 import lombok.extern.slf4j.Slf4j; 20 import lombok.extern.slf4j.Slf4j;
21 import org.flowable.bpmn.model.BpmnModel; 21 import org.flowable.bpmn.model.BpmnModel;
  22 +import org.flowable.bpmn.model.CallActivity;
22 import org.flowable.bpmn.model.FlowElement; 23 import org.flowable.bpmn.model.FlowElement;
23 import org.flowable.bpmn.model.UserTask; 24 import org.flowable.bpmn.model.UserTask;
24 import org.flowable.engine.delegate.DelegateExecution; 25 import org.flowable.engine.delegate.DelegateExecution;
@@ -129,8 +130,12 @@ public class BpmTaskCandidateInvoker { @@ -129,8 +130,12 @@ public class BpmTaskCandidateInvoker {
129 130
130 public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, 131 public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId,
131 Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { 132 Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
132 - // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过 133 + // 如果是 CallActivity 子流程,不进行计算候选人
133 FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId); 134 FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId);
  135 + if (flowElement instanceof CallActivity) {
  136 + return new HashSet<>();
  137 + }
  138 + // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过
134 Integer approveType = BpmnModelUtils.parseApproveType(flowElement); 139 Integer approveType = BpmnModelUtils.parseApproveType(flowElement);
135 if (ObjectUtils.equalsAny(approveType, 140 if (ObjectUtils.equalsAny(approveType,
136 BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(), 141 BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(),
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateApproveUserSelectStrategy.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.lang.Assert;
  5 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy;
  6 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
  7 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
  8 +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
  9 +import com.google.common.collect.Sets;
  10 +import jakarta.annotation.Resource;
  11 +import org.flowable.bpmn.model.BpmnModel;
  12 +import org.flowable.engine.delegate.DelegateExecution;
  13 +import org.flowable.engine.runtime.ProcessInstance;
  14 +import org.springframework.context.annotation.Lazy;
  15 +import org.springframework.stereotype.Component;
  16 +
  17 +import java.util.LinkedHashSet;
  18 +import java.util.List;
  19 +import java.util.Map;
  20 +
  21 +/**
  22 + * 审批人自选 {@link BpmTaskCandidateUserStrategy} 实现类
  23 + * 审批人在审批时选择下一个节点的审批人
  24 + *
  25 + * @author smallNorthLee
  26 + */
  27 +@Component
  28 +public class BpmTaskCandidateApproveUserSelectStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
  29 +
  30 + @Resource
  31 + @Lazy // 延迟加载,避免循环依赖
  32 + private BpmProcessInstanceService processInstanceService;
  33 +
  34 + @Override
  35 + public BpmTaskCandidateStrategyEnum getStrategy() {
  36 + return BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT;
  37 + }
  38 +
  39 + @Override
  40 + public void validateParam(String param) {}
  41 +
  42 + @Override
  43 + public boolean isParamRequired() {
  44 + return false;
  45 + }
  46 +
  47 + @Override
  48 + public LinkedHashSet<Long> calculateUsersByTask(DelegateExecution execution, String param) {
  49 + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
  50 + Assert.notNull(processInstance, "流程实例({})不能为空", execution.getProcessInstanceId());
  51 + Map<String, List<Long>> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processInstance);
  52 + Assert.notNull(approveUserSelectAssignees, "流程实例({}) 的下一个执行节点审批人不能为空",
  53 + execution.getProcessInstanceId());
  54 + if (approveUserSelectAssignees == null) {
  55 + return Sets.newLinkedHashSet();
  56 + }
  57 + // 获得审批人
  58 + List<Long> assignees = approveUserSelectAssignees.get(execution.getCurrentActivityId());
  59 + return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet();
  60 + }
  61 +
  62 + @Override
  63 + public LinkedHashSet<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
  64 + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
  65 + if (processVariables == null) {
  66 + return Sets.newLinkedHashSet();
  67 + }
  68 + // 流程预测时会使用,允许审批人为空,如果为空前端会弹出提示选择下一个节点审批人,避免流程无法进行,审批时会真正校验节点是否配置审批人
  69 + Map<String, List<Long>> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processVariables);
  70 + if (approveUserSelectAssignees == null) {
  71 + return Sets.newLinkedHashSet();
  72 + }
  73 + // 获得审批人
  74 + List<Long> assignees = approveUserSelectAssignees.get(activityId);
  75 + return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet();
  76 + }
  77 +
  78 +}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java
@@ -2,24 +2,21 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.d @@ -2,24 +2,21 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.d
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
5 -import cn.hutool.core.util.ObjectUtil;  
6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy; 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy;
7 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
8 -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;  
9 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; 7 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
10 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; 8 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
11 import com.google.common.collect.Sets; 9 import com.google.common.collect.Sets;
12 import jakarta.annotation.Resource; 10 import jakarta.annotation.Resource;
13 import org.flowable.bpmn.model.BpmnModel; 11 import org.flowable.bpmn.model.BpmnModel;
14 -import org.flowable.bpmn.model.ServiceTask;  
15 -import org.flowable.bpmn.model.Task;  
16 -import org.flowable.bpmn.model.UserTask;  
17 import org.flowable.engine.delegate.DelegateExecution; 12 import org.flowable.engine.delegate.DelegateExecution;
18 import org.flowable.engine.runtime.ProcessInstance; 13 import org.flowable.engine.runtime.ProcessInstance;
19 import org.springframework.context.annotation.Lazy; 14 import org.springframework.context.annotation.Lazy;
20 import org.springframework.stereotype.Component; 15 import org.springframework.stereotype.Component;
21 16
22 -import java.util.*; 17 +import java.util.LinkedHashSet;
  18 +import java.util.List;
  19 +import java.util.Map;
23 20
24 /** 21 /**
25 * 发起人自选 {@link BpmTaskCandidateUserStrategy} 实现类 22 * 发起人自选 {@link BpmTaskCandidateUserStrategy} 实现类
@@ -55,7 +52,7 @@ public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCand @@ -55,7 +52,7 @@ public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCand
55 execution.getProcessInstanceId()); 52 execution.getProcessInstanceId());
56 // 获得审批人 53 // 获得审批人
57 List<Long> assignees = startUserSelectAssignees.get(execution.getCurrentActivityId()); 54 List<Long> assignees = startUserSelectAssignees.get(execution.getCurrentActivityId());
58 - return new LinkedHashSet<>(assignees); 55 + return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet();
59 } 56 }
60 57
61 @Override 58 @Override
@@ -70,28 +67,7 @@ public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCand @@ -70,28 +67,7 @@ public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCand
70 } 67 }
71 // 获得审批人 68 // 获得审批人
72 List<Long> assignees = startUserSelectAssignees.get(activityId); 69 List<Long> assignees = startUserSelectAssignees.get(activityId);
73 - return new LinkedHashSet<>(assignees);  
74 - }  
75 -  
76 - /**  
77 - * 获得发起人自选审批人或抄送人的 Task 列表  
78 - *  
79 - * @param bpmnModel BPMN 模型  
80 - * @return Task 列表  
81 - */  
82 - public static List<Task> getStartUserSelectTaskList(BpmnModel bpmnModel) {  
83 - if (bpmnModel == null) {  
84 - return Collections.emptyList();  
85 - }  
86 - List<Task> tasks = new ArrayList<>();  
87 - tasks.addAll(BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class));  
88 - tasks.addAll(BpmnModelUtils.getBpmnModelElements(bpmnModel, ServiceTask.class));  
89 - if (CollUtil.isEmpty(tasks)) {  
90 - return Collections.emptyList();  
91 - }  
92 - tasks.removeIf(task -> ObjectUtil.notEqual(BpmnModelUtils.parseCandidateStrategy(task),  
93 - BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy()));  
94 - return tasks; 70 + return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet();
95 } 71 }
96 72
97 } 73 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java
@@ -24,7 +24,8 @@ public enum BpmTaskCandidateStrategyEnum implements ArrayValuable&lt;Integer&gt; { @@ -24,7 +24,8 @@ public enum BpmTaskCandidateStrategyEnum implements ArrayValuable&lt;Integer&gt; {
24 MULTI_DEPT_LEADER_MULTI(23, "连续多级部门的负责人"), 24 MULTI_DEPT_LEADER_MULTI(23, "连续多级部门的负责人"),
25 POST(22, "岗位"), 25 POST(22, "岗位"),
26 USER(30, "用户"), 26 USER(30, "用户"),
27 - START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时选择此节点的审批人 27 + APPROVE_USER_SELECT(34, "审批人自身"), // 当前审批人,可在审批时,选择下一个节点的审批人
  28 + START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时,选择此节点的审批人
28 START_USER(36, "发起人自己"), // 申请人自己, 一般紧挨开始节点,常用于发起人信息审核场景 29 START_USER(36, "发起人自己"), // 申请人自己, 一般紧挨开始节点,常用于发起人信息审核场景
29 START_USER_DEPT_LEADER(37, "发起人部门负责人"), 30 START_USER_DEPT_LEADER(37, "发起人部门负责人"),
30 START_USER_DEPT_LEADER_MULTI(38, "发起人连续多级部门的负责人"), 31 START_USER_DEPT_LEADER_MULTI(38, "发起人连续多级部门的负责人"),
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums; 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums;
2 2
  3 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum;
  4 +
3 /** 5 /**
4 * BPMN XML 常量信息 6 * BPMN XML 常量信息
5 * 7 *
@@ -67,6 +69,11 @@ public interface BpmnModelConstants { @@ -67,6 +69,11 @@ public interface BpmnModelConstants {
67 String USER_TASK_APPROVE_METHOD = "approveMethod"; 69 String USER_TASK_APPROVE_METHOD = "approveMethod";
68 70
69 /** 71 /**
  72 + * BPMN Child Process 的扩展属性,用于标记多实例来源类型
  73 + */
  74 + String CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE = "childProcessMultiInstanceSourceType";
  75 +
  76 + /**
70 * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限 77 * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限
71 */ 78 */
72 String FORM_FIELD_PERMISSION_ELEMENT = "fieldsPermission"; 79 String FORM_FIELD_PERMISSION_ELEMENT = "fieldsPermission";
@@ -129,4 +136,11 @@ public interface BpmnModelConstants { @@ -129,4 +136,11 @@ public interface BpmnModelConstants {
129 */ 136 */
130 String REASON_REQUIRE = "reasonRequire"; 137 String REASON_REQUIRE = "reasonRequire";
131 138
  139 + /**
  140 + * 节点类型
  141 + *
  142 + * 目前只有 {@link BpmModelTypeEnum#SIMPLE} 的 UserTask 节点会设置该属性,用于区分是审批节点、还是办理节点
  143 + */
  144 + String NODE_TYPE = "nodeType";
  145 +
132 } 146 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java
@@ -27,9 +27,17 @@ public class BpmnVariableConstants { @@ -27,9 +27,17 @@ public class BpmnVariableConstants {
27 * 流程实例的变量 - 发起用户选择的审批人 Map 27 * 流程实例的变量 - 发起用户选择的审批人 Map
28 * 28 *
29 * @see ProcessInstance#getProcessVariables() 29 * @see ProcessInstance#getProcessVariables()
  30 + * @see BpmTaskCandidateStrategyEnum#START_USER_SELECT
30 */ 31 */
31 public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES"; 32 public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES";
32 /** 33 /**
  34 + * 流程实例的变量 - 审批人选择的审批人 Map
  35 + *
  36 + * @see ProcessInstance#getProcessVariables()
  37 + * @see BpmTaskCandidateStrategyEnum#APPROVE_USER_SELECT
  38 + */
  39 + public static final String PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES = "PROCESS_APPROVE_USER_SELECT_ASSIGNEES";
  40 + /**
33 * 流程实例的变量 - 发起用户 ID 41 * 流程实例的变量 - 发起用户 ID
34 * 42 *
35 * @see ProcessInstance#getProcessVariables() 43 * @see ProcessInstance#getProcessVariables()
@@ -52,6 +60,13 @@ public class BpmnVariableConstants { @@ -52,6 +60,13 @@ public class BpmnVariableConstants {
52 public static final String PROCESS_INSTANCE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED"; 60 public static final String PROCESS_INSTANCE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED";
53 61
54 /** 62 /**
  63 + * 流程实例的变量 - 用于判断流程是否需要跳过发起人节点
  64 + *
  65 + * @see ProcessInstance#getProcessVariables()
  66 + */
  67 + public static final String PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE = "PROCESS_SKIP_START_USER_NODE";
  68 +
  69 + /**
55 * 流程实例的变量 - 流程开始时间 70 * 流程实例的变量 - 流程开始时间
56 * 71 *
57 * 【非存储变量】用于部分需要 format 的场景,例如说:流程实例的自定义标题 72 * 【非存储变量】用于部分需要 format 的场景,例如说:流程实例的自定义标题
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java
@@ -22,6 +22,7 @@ import java.util.Set; @@ -22,6 +22,7 @@ import java.util.Set;
22 public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEventListener { 22 public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEventListener {
23 23
24 public static final Set<FlowableEngineEventType> PROCESS_INSTANCE_EVENTS = ImmutableSet.<FlowableEngineEventType>builder() 24 public static final Set<FlowableEngineEventType> PROCESS_INSTANCE_EVENTS = ImmutableSet.<FlowableEngineEventType>builder()
  25 + .add(FlowableEngineEventType.PROCESS_CREATED)
25 .add(FlowableEngineEventType.PROCESS_COMPLETED) 26 .add(FlowableEngineEventType.PROCESS_COMPLETED)
26 .add(FlowableEngineEventType.PROCESS_CANCELLED) 27 .add(FlowableEngineEventType.PROCESS_CANCELLED)
27 .build(); 28 .build();
@@ -35,6 +36,11 @@ public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEvent @@ -35,6 +36,11 @@ public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEvent
35 } 36 }
36 37
37 @Override 38 @Override
  39 + protected void processCreated(FlowableEngineEntityEvent event) {
  40 + processInstanceService.processProcessInstanceCreated((ProcessInstance)event.getEntity());
  41 + }
  42 +
  43 + @Override
38 protected void processCompleted(FlowableEngineEntityEvent event) { 44 protected void processCompleted(FlowableEngineEntityEvent event) {
39 processInstanceService.processProcessInstanceCompleted((ProcessInstance)event.getEntity()); 45 processInstanceService.processProcessInstanceCompleted((ProcessInstance)event.getEntity());
40 } 46 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
@@ -109,7 +109,11 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { @@ -109,7 +109,11 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
109 // 2.2 延迟器超时处理 109 // 2.2 延迟器超时处理
110 } else if (ObjectUtil.equal(bpmTimerBoundaryEventType, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT)) { 110 } else if (ObjectUtil.equal(bpmTimerBoundaryEventType, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT)) {
111 String taskKey = boundaryEvent.getAttachedToRefId(); 111 String taskKey = boundaryEvent.getAttachedToRefId();
112 - taskService.processDelayTimerTimeout(event.getProcessInstanceId(), taskKey); 112 + taskService.triggerTask(event.getProcessInstanceId(), taskKey);
  113 + // 2.3 子流程超时处理
  114 + } else if (ObjectUtil.equal(bpmTimerBoundaryEventType, BpmBoundaryEventTypeEnum.CHILD_PROCESS_TIMEOUT)) {
  115 + String taskKey = boundaryEvent.getAttachedToRefId();
  116 + taskService.processChildProcessTimeout(event.getProcessInstanceId(), taskKey);
113 } 117 }
114 } 118 }
115 119
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmHttpRequestUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.framework.common.core.KeyValue;
  6 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  7 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  8 +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
  9 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmHttpRequestParamTypeEnum;
  10 +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
  11 +import com.fasterxml.jackson.core.type.TypeReference;
  12 +import lombok.extern.slf4j.Slf4j;
  13 +import org.flowable.engine.runtime.ProcessInstance;
  14 +import org.springframework.http.HttpEntity;
  15 +import org.springframework.http.HttpMethod;
  16 +import org.springframework.http.ResponseEntity;
  17 +import org.springframework.util.LinkedMultiValueMap;
  18 +import org.springframework.util.MultiValueMap;
  19 +import org.springframework.web.client.RestClientException;
  20 +import org.springframework.web.client.RestTemplate;
  21 +
  22 +import java.util.HashMap;
  23 +import java.util.List;
  24 +import java.util.Map;
  25 +
  26 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  27 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
  28 +import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR;
  29 +
  30 +/**
  31 + * 工作流发起 HTTP 请求工具类
  32 + *
  33 + * @author 芋道源码
  34 + */
  35 +@Slf4j
  36 +public class BpmHttpRequestUtils {
  37 +
  38 + public static void executeBpmHttpRequest(ProcessInstance processInstance,
  39 + String url,
  40 + List<BpmSimpleModelNodeVO.HttpRequestParam> headerParams,
  41 + List<BpmSimpleModelNodeVO.HttpRequestParam> bodyParams,
  42 + Boolean handleResponse,
  43 + List<KeyValue<String, String>> response,
  44 + // TODO @lesan:RestTemplate 直接通过 springUtil 获取好咧;
  45 + RestTemplate restTemplate,
  46 + // TODO @lesan:processInstanceService 直接通过 springUtil 获取好咧;
  47 + BpmProcessInstanceService processInstanceService) {
  48 +
  49 + // 1.1 设置请求头
  50 + MultiValueMap<String, String> headers = buildHttpHeaders(processInstance, headerParams);
  51 + // 1.2 设置请求体
  52 + MultiValueMap<String, String> body = buildHttpBody(processInstance, bodyParams);
  53 +
  54 + // 2. 发起请求
  55 + ResponseEntity<String> responseEntity = sendHttpRequest(url, headers, body, restTemplate);
  56 +
  57 + // 3. 处理返回
  58 + // TODO @lesan:可以用 if return,让括号小点
  59 + if (Boolean.TRUE.equals(handleResponse)) {
  60 + // 3.1 判断是否需要解析返回值
  61 + if (responseEntity == null
  62 + || StrUtil.isEmpty(responseEntity.getBody())
  63 + || !responseEntity.getStatusCode().is2xxSuccessful()
  64 + || CollUtil.isEmpty(response)) {
  65 + return;
  66 + }
  67 + // 3.2 解析返回值, 返回值必须符合 CommonResult 规范。
  68 + CommonResult<Map<String, Object>> respResult = JsonUtils.parseObjectQuietly(responseEntity.getBody(),
  69 + new TypeReference<>() {});
  70 + if (respResult == null || !respResult.isSuccess()) {
  71 + return;
  72 + }
  73 + // 3.3 获取需要更新的流程变量
  74 + Map<String, Object> updateVariables = getNeedUpdatedVariablesFromResponse(respResult.getData(), response);
  75 + // 3.4 更新流程变量
  76 + if (CollUtil.isNotEmpty(updateVariables)) {
  77 + processInstanceService.updateProcessInstanceVariables(processInstance.getId(), updateVariables);
  78 + }
  79 + }
  80 + }
  81 +
  82 + public static ResponseEntity<String> sendHttpRequest(String url,
  83 + MultiValueMap<String, String> headers,
  84 + MultiValueMap<String, String> body,
  85 + RestTemplate restTemplate) {
  86 + HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(body, headers);
  87 + ResponseEntity<String> responseEntity;
  88 + try {
  89 + responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
  90 + log.info("[sendHttpRequest][HTTP 触发器,请求头:{},请求体:{},响应结果:{}]", headers, body, responseEntity);
  91 + } catch (RestClientException e) {
  92 + log.error("[sendHttpRequest][HTTP 触发器,请求头:{},请求体:{},请求出错:{}]", headers, body, e.getMessage());
  93 + throw exception(PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR);
  94 + }
  95 + return responseEntity;
  96 + }
  97 +
  98 + public static MultiValueMap<String, String> buildHttpHeaders(ProcessInstance processInstance,
  99 + List<BpmSimpleModelNodeVO.HttpRequestParam> headerSettings) {
  100 + Map<String, Object> processVariables = processInstance.getProcessVariables();
  101 + MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
  102 + headers.add(HEADER_TENANT_ID, processInstance.getTenantId());
  103 + addHttpRequestParam(headers, headerSettings, processVariables);
  104 + return headers;
  105 + }
  106 +
  107 + public static MultiValueMap<String, String> buildHttpBody(ProcessInstance processInstance,
  108 + List<BpmSimpleModelNodeVO.HttpRequestParam> bodySettings) {
  109 + Map<String, Object> processVariables = processInstance.getProcessVariables();
  110 + MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
  111 + addHttpRequestParam(body, bodySettings, processVariables);
  112 + body.add("processInstanceId", processInstance.getId());
  113 + return body;
  114 + }
  115 +
  116 + /**
  117 + * 从请求返回值获取需要更新的流程变量
  118 + *
  119 + * @param result 请求返回结果
  120 + * @param responseSettings 返回设置
  121 + * @return 需要更新的流程变量
  122 + */
  123 + public static Map<String, Object> getNeedUpdatedVariablesFromResponse(Map<String, Object> result,
  124 + List<KeyValue<String, String>> responseSettings) {
  125 + Map<String, Object> updateVariables = new HashMap<>();
  126 + if (CollUtil.isEmpty(result)) {
  127 + return updateVariables;
  128 + }
  129 + responseSettings.forEach(responseSetting -> {
  130 + if (StrUtil.isNotEmpty(responseSetting.getKey()) && result.containsKey(responseSetting.getValue())) {
  131 + updateVariables.put(responseSetting.getKey(), result.get(responseSetting.getValue()));
  132 + }
  133 + });
  134 + return updateVariables;
  135 + }
  136 +
  137 + /**
  138 + * 添加 HTTP 请求参数。请求头或者请求体
  139 + *
  140 + * @param params HTTP 请求参数
  141 + * @param paramSettings HTTP 请求参数设置
  142 + * @param processVariables 流程变量
  143 + */
  144 + public static void addHttpRequestParam(MultiValueMap<String, String> params,
  145 + List<BpmSimpleModelNodeVO.HttpRequestParam> paramSettings,
  146 + Map<String, Object> processVariables) {
  147 + if (CollUtil.isEmpty(paramSettings)) {
  148 + return;
  149 + }
  150 + paramSettings.forEach(item -> {
  151 + if (item.getType().equals(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType())) {
  152 + params.add(item.getKey(), item.getValue());
  153 + } else if (item.getType().equals(BpmHttpRequestParamTypeEnum.FROM_FORM.getType())) {
  154 + params.add(item.getKey(), processVariables.get(item.getValue()).toString());
  155 + }
  156 + });
  157 + }
  158 +
  159 +}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
@@ -159,6 +159,17 @@ public class BpmnModelUtils { @@ -159,6 +159,17 @@ public class BpmnModelUtils {
159 } 159 }
160 160
161 /** 161 /**
  162 + * 解析子流程多实例来源类型
  163 + *
  164 + * @see BpmChildProcessMultiInstanceSourceTypeEnum
  165 + * @param element 任务节点
  166 + * @return 多实例来源类型
  167 + */
  168 + public static Integer parseMultiInstanceSourceType(FlowElement element) {
  169 + return NumberUtils.parseInt(parseExtensionElement(element, BpmnModelConstants.CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE));
  170 + }
  171 +
  172 + /**
162 * 添加任务拒绝处理元素 173 * 添加任务拒绝处理元素
163 * 174 *
164 * @param rejectHandler 任务拒绝处理 175 * @param rejectHandler 任务拒绝处理
@@ -410,6 +421,26 @@ public class BpmnModelUtils { @@ -410,6 +421,26 @@ public class BpmnModelUtils {
410 return parseExtensionElement(flowElement, TRIGGER_PARAM); 421 return parseExtensionElement(flowElement, TRIGGER_PARAM);
411 } 422 }
412 423
  424 + /**
  425 + * 给节点添加节点类型
  426 + *
  427 + * @param nodeType 节点类型
  428 + * @param flowElement 节点
  429 + */
  430 + public static void addNodeType(Integer nodeType, FlowElement flowElement) {
  431 + addExtensionElement(flowElement, BpmnModelConstants.NODE_TYPE, nodeType);
  432 + }
  433 +
  434 + /**
  435 + * 解析节点类型
  436 + *
  437 + * @param flowElement 节点
  438 + * @return 节点类型
  439 + */
  440 + public static Integer parseNodeType(FlowElement flowElement) {
  441 + return NumberUtils.parseInt(parseExtensionElement(flowElement, BpmnModelConstants.NODE_TYPE));
  442 + }
  443 +
413 // ========== BPM 简单查找相关的方法 ========== 444 // ========== BPM 简单查找相关的方法 ==========
414 445
415 /** 446 /**
@@ -777,71 +808,214 @@ public class BpmnModelUtils { @@ -777,71 +808,214 @@ public class BpmnModelUtils {
777 // 情况:ExclusiveGateway 排它,只有一个满足条件的。如果没有,就走默认的 808 // 情况:ExclusiveGateway 排它,只有一个满足条件的。如果没有,就走默认的
778 if (currentElement instanceof ExclusiveGateway) { 809 if (currentElement instanceof ExclusiveGateway) {
779 // 查找满足条件的 SequenceFlow 路径 810 // 查找满足条件的 SequenceFlow 路径
780 - Gateway gateway = (Gateway) currentElement;  
781 - SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),  
782 - flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())  
783 - && evalConditionExpress(variables, flow.getConditionExpression()));  
784 - if (matchSequenceFlow == null) {  
785 - matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),  
786 - flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));  
787 - // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的  
788 - if (matchSequenceFlow == null && gateway.getOutgoingFlows().size() == 1) {  
789 - matchSequenceFlow = gateway.getOutgoingFlows().get(0);  
790 - }  
791 - } 811 + SequenceFlow matchSequenceFlow = findMatchSequenceFlowByExclusiveGateway((Gateway) currentElement, variables);
792 // 遍历满足条件的 SequenceFlow 路径 812 // 遍历满足条件的 SequenceFlow 路径
793 if (matchSequenceFlow != null) { 813 if (matchSequenceFlow != null) {
794 simulateNextFlowElements(matchSequenceFlow.getTargetFlowElement(), variables, resultElements, visitElements); 814 simulateNextFlowElements(matchSequenceFlow.getTargetFlowElement(), variables, resultElements, visitElements);
795 } 815 }
796 - return;  
797 } 816 }
798 -  
799 // 情况:InclusiveGateway 包容,多个满足条件的。如果没有,就走默认的 817 // 情况:InclusiveGateway 包容,多个满足条件的。如果没有,就走默认的
800 - if (currentElement instanceof InclusiveGateway) { 818 + else if (currentElement instanceof InclusiveGateway) {
801 // 查找满足条件的 SequenceFlow 路径 819 // 查找满足条件的 SequenceFlow 路径
802 - Gateway gateway = (Gateway) currentElement;  
803 - Collection<SequenceFlow> matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),  
804 - flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())  
805 - && evalConditionExpress(variables, flow.getConditionExpression()));  
806 - if (CollUtil.isEmpty(matchSequenceFlows)) {  
807 - matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),  
808 - flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));  
809 - // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的  
810 - if (CollUtil.isEmpty(matchSequenceFlows) && gateway.getOutgoingFlows().size() == 1) {  
811 - matchSequenceFlows = gateway.getOutgoingFlows();  
812 - }  
813 - } 820 + Collection<SequenceFlow> matchSequenceFlows = findMatchSequenceFlowsByInclusiveGateway((Gateway) currentElement, variables);
814 // 遍历满足条件的 SequenceFlow 路径 821 // 遍历满足条件的 SequenceFlow 路径
815 matchSequenceFlows.forEach( 822 matchSequenceFlows.forEach(
816 flow -> simulateNextFlowElements(flow.getTargetFlowElement(), variables, resultElements, visitElements)); 823 flow -> simulateNextFlowElements(flow.getTargetFlowElement(), variables, resultElements, visitElements));
817 } 824 }
818 -  
819 // 情况:ParallelGateway 并行,都满足,都走 825 // 情况:ParallelGateway 并行,都满足,都走
820 - if (currentElement instanceof ParallelGateway) { 826 + else if (currentElement instanceof ParallelGateway) {
821 Gateway gateway = (Gateway) currentElement; 827 Gateway gateway = (Gateway) currentElement;
822 // 遍历子节点 828 // 遍历子节点
823 gateway.getOutgoingFlows().forEach( 829 gateway.getOutgoingFlows().forEach(
824 nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements)); 830 nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements));
825 - return;  
826 } 831 }
827 } 832 }
828 833
829 /** 834 /**
  835 + * 根据当前节点,获取下一个节点
  836 + *
  837 + * @param currentElement 当前节点
  838 + * @param bpmnModel BPMN模型
  839 + * @param variables 流程变量
  840 + */
  841 + @SuppressWarnings("PatternVariableCanBeUsed")
  842 + public static List<FlowNode> getNextFlowNodes(FlowElement currentElement, BpmnModel bpmnModel,
  843 + Map<String, Object> variables){
  844 + List<FlowNode> nextFlowNodes = new ArrayList<>(); // 下一个执行的流程节点集合
  845 + FlowNode currentNode = (FlowNode) currentElement; // 当前执行节点的基本属性
  846 + List<SequenceFlow> outgoingFlows = currentNode.getOutgoingFlows(); // 当前节点的关联节点
  847 + if (CollUtil.isEmpty(outgoingFlows)) {
  848 + log.warn("[getNextFlowNodes][当前节点({}) 的 outgoingFlows 为空]", currentNode.getId());
  849 + return nextFlowNodes;
  850 + }
  851 +
  852 + // 遍历每个出口流
  853 + for (SequenceFlow outgoingFlow : outgoingFlows) {
  854 + // 获取目标节点的基本属性
  855 + FlowElement targetElement = bpmnModel.getFlowElement(outgoingFlow.getTargetRef());
  856 + if (targetElement == null) {
  857 + continue;
  858 + }
  859 + // 如果是结束节点,直接返回
  860 + if (targetElement instanceof EndEvent) {
  861 + break;
  862 + }
  863 + // 情况一:处理不同类型的网关
  864 + if (targetElement instanceof Gateway) {
  865 + Gateway gateway = (Gateway) targetElement;
  866 + if (gateway instanceof ExclusiveGateway) {
  867 + handleExclusiveGateway(gateway, bpmnModel, variables, nextFlowNodes);
  868 + } else if (gateway instanceof InclusiveGateway) {
  869 + handleInclusiveGateway(gateway, bpmnModel, variables, nextFlowNodes);
  870 + } else if (gateway instanceof ParallelGateway) {
  871 + handleParallelGateway(gateway, bpmnModel, variables, nextFlowNodes);
  872 + }
  873 + } else {
  874 + // 情况二:如果不是网关,直接添加到下一个节点列表
  875 + nextFlowNodes.add((FlowNode) targetElement);
  876 + }
  877 + }
  878 + return nextFlowNodes;
  879 + }
  880 +
  881 + /**
  882 + * 处理排它网关
  883 + *
  884 + * @param gateway 排他网关
  885 + * @param bpmnModel BPMN模型
  886 + * @param variables 流程变量
  887 + * @param nextFlowNodes 下一个执行的流程节点集合
  888 + */
  889 + private static void handleExclusiveGateway(Gateway gateway, BpmnModel bpmnModel,
  890 + Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
  891 + // 查找满足条件的 SequenceFlow 路径
  892 + SequenceFlow matchSequenceFlow = findMatchSequenceFlowByExclusiveGateway(gateway, variables);
  893 + // 遍历满足条件的 SequenceFlow 路径
  894 + if (matchSequenceFlow != null) {
  895 + FlowElement targetElement = bpmnModel.getFlowElement(matchSequenceFlow.getTargetRef());
  896 + if (targetElement instanceof FlowNode) {
  897 + nextFlowNodes.add((FlowNode) targetElement);
  898 + }
  899 + }
  900 + }
  901 +
  902 + /**
  903 + * 处理排它网关(Exclusive Gateway),选择符合条件的路径
  904 + *
  905 + * @param gateway 排他网关
  906 + * @param variables 流程变量
  907 + * @return 符合条件的路径
  908 + */
  909 + private static SequenceFlow findMatchSequenceFlowByExclusiveGateway(Gateway gateway, Map<String, Object> variables) {
  910 + // TODO 表单无可编辑字段时variables为空,流程走向会出现问题,比如流程审批过程中无需要修改的字段值,
  911 + // TODO @小北:是不是还是保证,编辑的时候,如果计算下一个节点,还是 variables 是完整体?而不是空的!!!(可以微信讨论下)
  912 + SequenceFlow matchSequenceFlow;
  913 + if (CollUtil.isNotEmpty(variables)) {
  914 + matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
  915 + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
  916 + && (evalConditionExpress(variables, flow.getConditionExpression())));
  917 + } else {
  918 + matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
  919 + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()));
  920 + }
  921 + if (matchSequenceFlow == null) {
  922 + matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
  923 + flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
  924 + // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的
  925 + if (matchSequenceFlow == null && gateway.getOutgoingFlows().size() == 1) {
  926 + matchSequenceFlow = gateway.getOutgoingFlows().get(0);
  927 + }
  928 + }
  929 + return matchSequenceFlow;
  930 + }
  931 +
  932 + /**
  933 + * 处理包容网关
  934 + *
  935 + * @param gateway 排他网关
  936 + * @param bpmnModel BPMN模型
  937 + * @param variables 流程变量
  938 + * @param nextFlowNodes 下一个执行的流程节点集合
  939 + */
  940 + private static void handleInclusiveGateway(Gateway gateway, BpmnModel bpmnModel,
  941 + Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
  942 + // 查找满足条件的 SequenceFlow 路径集合
  943 + Collection<SequenceFlow> matchSequenceFlows = findMatchSequenceFlowsByInclusiveGateway(gateway, variables);
  944 + // 遍历满足条件的 SequenceFlow 路径,获取目标节点
  945 + matchSequenceFlows.forEach(flow -> {
  946 + FlowElement targetElement = bpmnModel.getFlowElement(flow.getTargetRef());
  947 + if (targetElement instanceof FlowNode) {
  948 + nextFlowNodes.add((FlowNode) targetElement);
  949 + }
  950 + });
  951 + }
  952 +
  953 + /**
  954 + * 处理排它网关(Inclusive Gateway),选择符合条件的路径
  955 + *
  956 + * @param gateway 排他网关
  957 + * @param variables 流程变量
  958 + * @return 符合条件的路径
  959 + */
  960 + private static Collection<SequenceFlow> findMatchSequenceFlowsByInclusiveGateway(Gateway gateway, Map<String, Object> variables) {
  961 + // 查找满足条件的 SequenceFlow 路径
  962 + Collection<SequenceFlow> matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
  963 + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
  964 + && evalConditionExpress(variables, flow.getConditionExpression()));
  965 + if (CollUtil.isEmpty(matchSequenceFlows)) {
  966 + matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
  967 + flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
  968 + // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的
  969 + if (CollUtil.isEmpty(matchSequenceFlows) && gateway.getOutgoingFlows().size() == 1) {
  970 + matchSequenceFlows = gateway.getOutgoingFlows();
  971 + }
  972 + }
  973 + return matchSequenceFlows;
  974 + }
  975 +
  976 +
  977 + /**
  978 + * 处理并行网关
  979 + *
  980 + * @param gateway 排他网关
  981 + * @param bpmnModel BPMN模型
  982 + * @param variables 流程变量
  983 + * @param nextFlowNodes 下一个执行的流程节点集合
  984 + */
  985 + private static void handleParallelGateway(Gateway gateway, BpmnModel bpmnModel,
  986 + Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
  987 + // 并行网关,遍历所有出口路径,获取目标节点
  988 + gateway.getOutgoingFlows().forEach(flow -> {
  989 + FlowElement targetElement = bpmnModel.getFlowElement(flow.getTargetRef());
  990 + if (targetElement instanceof FlowNode) {
  991 + nextFlowNodes.add((FlowNode) targetElement);
  992 + }
  993 + });
  994 + }
  995 +
  996 + /**
830 * 计算条件表达式是否为 true 满足条件 997 * 计算条件表达式是否为 true 满足条件
831 * 998 *
832 * @param variables 流程实例 999 * @param variables 流程实例
833 - * @param express 条件表达式 1000 + * @param expression 条件表达式
834 * @return 是否满足条件 1001 * @return 是否满足条件
835 */ 1002 */
836 - public static boolean evalConditionExpress(Map<String, Object> variables, String express) {  
837 - if (express == null) { 1003 + public static boolean evalConditionExpress(Map<String, Object> variables, String expression) {
  1004 + if (expression == null) {
838 return Boolean.FALSE; 1005 return Boolean.FALSE;
839 } 1006 }
  1007 + // 如果 variables 为空,则创建一个的原因?可能 expression 的计算,不依赖于 variables
  1008 + if (variables == null) {
  1009 + variables = new HashMap<>();
  1010 + }
  1011 +
  1012 + // 执行计算
840 try { 1013 try {
841 - Object result = FlowableUtils.getExpressionValue(variables, express); 1014 + Object result = FlowableUtils.getExpressionValue(variables, expression);
842 return Boolean.TRUE.equals(result); 1015 return Boolean.TRUE.equals(result);
843 } catch (FlowableException ex) { 1016 } catch (FlowableException ex) {
844 - log.error("[evalConditionExpress][条件表达式({}) 变量({}) 解析报错]", express, variables, ex); 1017 + // 为什么使用 info 日志?原因是,expression 如果从 variables 取不到值,会报错。实际这种情况下,可以忽略
  1018 + log.info("[evalConditionExpress][条件表达式({}) 变量({}) 解析报错]", expression, variables, ex);
845 return Boolean.FALSE; 1019 return Boolean.FALSE;
846 } 1020 }
847 } 1021 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java
1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
2 2
  3 +import cn.hutool.core.map.MapUtil;
3 import cn.hutool.core.util.ObjectUtil; 4 import cn.hutool.core.util.ObjectUtil;
4 import cn.hutool.extra.spring.SpringUtil; 5 import cn.hutool.extra.spring.SpringUtil;
5 import cn.iocoder.yudao.framework.common.core.KeyValue; 6 import cn.iocoder.yudao.framework.common.core.KeyValue;
@@ -24,7 +25,10 @@ import org.flowable.engine.impl.util.CommandContextUtil; @@ -24,7 +25,10 @@ import org.flowable.engine.impl.util.CommandContextUtil;
24 import org.flowable.engine.runtime.ProcessInstance; 25 import org.flowable.engine.runtime.ProcessInstance;
25 import org.flowable.task.api.TaskInfo; 26 import org.flowable.task.api.TaskInfo;
26 27
27 -import java.util.*; 28 +import java.util.HashMap;
  29 +import java.util.List;
  30 +import java.util.Map;
  31 +import java.util.Objects;
28 import java.util.concurrent.Callable; 32 import java.util.concurrent.Callable;
29 import java.util.stream.Collectors; 33 import java.util.stream.Collectors;
30 34
@@ -190,13 +194,38 @@ public class FlowableUtils { @@ -190,13 +194,38 @@ public class FlowableUtils {
190 @SuppressWarnings("unchecked") 194 @SuppressWarnings("unchecked")
191 public static Map<String, List<Long>> getStartUserSelectAssignees(Map<String, Object> processVariables) { 195 public static Map<String, List<Long>> getStartUserSelectAssignees(Map<String, Object> processVariables) {
192 if (processVariables == null) { 196 if (processVariables == null) {
193 - return null; 197 + return new HashMap<>();
194 } 198 }
195 return (Map<String, List<Long>>) processVariables.get( 199 return (Map<String, List<Long>>) processVariables.get(
196 BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES); 200 BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES);
197 } 201 }
198 202
199 /** 203 /**
  204 + * 获得流程实例的审批用户选择的下一个节点的审批人 Map
  205 + *
  206 + * @param processInstance 流程实例
  207 + * @return 审批用户选择的下一个节点的审批人Map
  208 + */
  209 + public static Map<String, List<Long>> getApproveUserSelectAssignees(ProcessInstance processInstance) {
  210 + return processInstance != null ? getApproveUserSelectAssignees(processInstance.getProcessVariables()) : null;
  211 + }
  212 +
  213 + /**
  214 + * 获得流程实例的审批用户选择的下一个节点的审批人 Map
  215 + *
  216 + * @param processVariables 流程变量
  217 + * @return 审批用户选择的下一个节点的审批人Map Map
  218 + */
  219 + @SuppressWarnings("unchecked")
  220 + public static Map<String, List<Long>> getApproveUserSelectAssignees(Map<String, Object> processVariables) {
  221 + if (processVariables == null) {
  222 + return new HashMap<>();
  223 + }
  224 + return (Map<String, List<Long>>) processVariables.get(
  225 + BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES);
  226 + }
  227 +
  228 + /**
200 * 获得流程实例的摘要 229 * 获得流程实例的摘要
201 * 230 *
202 * 仅有 {@link BpmModelFormTypeEnum#getType()} 表单,才有摘要。 231 * 仅有 {@link BpmModelFormTypeEnum#getType()} 表单,才有摘要。
@@ -240,7 +269,7 @@ public class FlowableUtils { @@ -240,7 +269,7 @@ public class FlowableUtils {
240 return formFieldsMap.entrySet().stream() 269 return formFieldsMap.entrySet().stream()
241 .limit(3) 270 .limit(3)
242 .map(entry -> new KeyValue<>(entry.getValue().getTitle(), 271 .map(entry -> new KeyValue<>(entry.getValue().getTitle(),
243 - processVariables.getOrDefault(entry.getValue().getField(), "").toString())) 272 + MapUtil.getStr(processVariables, entry.getValue().getField(), "")))
244 .collect(Collectors.toList()); 273 .collect(Collectors.toList());
245 } 274 }
246 275
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
@@ -5,25 +5,27 @@ import cn.hutool.core.lang.Assert; @@ -5,25 +5,27 @@ import cn.hutool.core.lang.Assert;
5 import cn.hutool.core.map.MapUtil; 5 import cn.hutool.core.map.MapUtil;
6 import cn.hutool.core.util.*; 6 import cn.hutool.core.util.*;
7 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 7 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  8 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
8 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; 9 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
9 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.ConditionGroups; 10 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.ConditionGroups;
10 import cn.iocoder.yudao.module.bpm.enums.definition.*; 11 import cn.iocoder.yudao.module.bpm.enums.definition.*;
11 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 12 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
12 -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; 13 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
13 import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate; 14 import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate;
14 import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmTriggerTaskDelegate; 15 import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmTriggerTaskDelegate;
  16 +import cn.iocoder.yudao.module.bpm.service.task.listener.BpmCallActivityListener;
  17 +import cn.iocoder.yudao.module.bpm.service.task.listener.BpmUserTaskListener;
15 import org.flowable.bpmn.BpmnAutoLayout; 18 import org.flowable.bpmn.BpmnAutoLayout;
16 import org.flowable.bpmn.constants.BpmnXMLConstants; 19 import org.flowable.bpmn.constants.BpmnXMLConstants;
17 import org.flowable.bpmn.model.Process; 20 import org.flowable.bpmn.model.Process;
18 import org.flowable.bpmn.model.*; 21 import org.flowable.bpmn.model.*;
  22 +import org.flowable.engine.delegate.ExecutionListener;
19 import org.flowable.engine.delegate.TaskListener; 23 import org.flowable.engine.delegate.TaskListener;
20 -import org.springframework.util.MultiValueMap;  
21 24
22 import java.util.*; 25 import java.util.*;
23 26
24 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; 27 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
25 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*; 28 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*;
26 -import static cn.iocoder.yudao.module.bpm.service.task.listener.BpmUserTaskListener.DELEGATE_EXPRESSION;  
27 import static java.util.Arrays.asList; 29 import static java.util.Arrays.asList;
28 30
29 /** 31 /**
@@ -40,9 +42,10 @@ public class SimpleModelUtils { @@ -40,9 +42,10 @@ public class SimpleModelUtils {
40 42
41 static { 43 static {
42 List<NodeConvert> converts = asList(new StartNodeConvert(), new EndNodeConvert(), 44 List<NodeConvert> converts = asList(new StartNodeConvert(), new EndNodeConvert(),
43 - new StartUserNodeConvert(), new ApproveNodeConvert(), new CopyNodeConvert(), 45 + new StartUserNodeConvert(), new ApproveNodeConvert(), new CopyNodeConvert(), new TransactorNodeConvert(),
44 new DelayTimerNodeConvert(), new TriggerNodeConvert(), 46 new DelayTimerNodeConvert(), new TriggerNodeConvert(),
45 - new ConditionBranchNodeConvert(), new ParallelBranchNodeConvert(), new InclusiveBranchNodeConvert(), new RouteBranchNodeConvert()); 47 + new ConditionBranchNodeConvert(), new ParallelBranchNodeConvert(), new InclusiveBranchNodeConvert(), new RouteBranchNodeConvert(),
  48 + new ChildProcessConvert());
46 converts.forEach(convert -> NODE_CONVERTS.put(convert.getType(), convert)); 49 converts.forEach(convert -> NODE_CONVERTS.put(convert.getType(), convert));
47 } 50 }
48 51
@@ -78,7 +81,7 @@ public class SimpleModelUtils { @@ -78,7 +81,7 @@ public class SimpleModelUtils {
78 traverseNodeToBuildFlowNode(startNode, process); 81 traverseNodeToBuildFlowNode(startNode, process);
79 82
80 // 3. 构建并添加节点之间的连线 Sequence Flow 83 // 3. 构建并添加节点之间的连线 Sequence Flow
81 - EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); 84 + EndEvent endEvent = getEndEvent(bpmnModel);
82 traverseNodeToBuildSequenceFlow(process, startNode, endEvent.getId()); 85 traverseNodeToBuildSequenceFlow(process, startNode, endEvent.getId());
83 86
84 // 4. 自动布局 87 // 4. 自动布局
@@ -164,8 +167,16 @@ public class SimpleModelUtils { @@ -164,8 +167,16 @@ public class SimpleModelUtils {
164 // 情况一:有“子”节点,则建立连线 167 // 情况一:有“子”节点,则建立连线
165 // 情况二:没有“子节点”,则直接跟 targetNodeId 建立连线。例如说,结束节点、条件分支(分支节点的孩子节点或聚合节点)的最后一个节点 168 // 情况二:没有“子节点”,则直接跟 targetNodeId 建立连线。例如说,结束节点、条件分支(分支节点的孩子节点或聚合节点)的最后一个节点
166 String finalTargetNodeId = isChildNodeValid ? childNode.getId() : targetNodeId; 169 String finalTargetNodeId = isChildNodeValid ? childNode.getId() : targetNodeId;
167 - SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), finalTargetNodeId);  
168 - process.addFlowElement(sequenceFlow); 170 +
  171 + // 如果没有附加节点:则直接建立连线
  172 + if (StrUtil.isEmpty(node.getAttachNodeId())) {
  173 + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), finalTargetNodeId);
  174 + process.addFlowElement(sequenceFlow);
  175 + } else {
  176 + // 如果有附加节点:需要先建立和附加节点的连线,再建立附加节点和目标节点的连线。例如说,触发器节点(HTTP 回调)
  177 + List<SequenceFlow> sequenceFlows = buildAttachNodeSequenceFlow(node.getId(), node.getAttachNodeId(), finalTargetNodeId);
  178 + sequenceFlows.forEach(process::addFlowElement);
  179 + }
169 180
170 // 因为有子节点,递归调用后续子节点 181 // 因为有子节点,递归调用后续子节点
171 if (isChildNodeValid) { 182 if (isChildNodeValid) {
@@ -174,6 +185,19 @@ public class SimpleModelUtils { @@ -174,6 +185,19 @@ public class SimpleModelUtils {
174 } 185 }
175 186
176 /** 187 /**
  188 + * 构建有附加节点的连线
  189 + *
  190 + * @param nodeId 当前节点 ID
  191 + * @param attachNodeId 附属节点 ID
  192 + * @param targetNodeId 目标节点 ID
  193 + */
  194 + private static List<SequenceFlow> buildAttachNodeSequenceFlow(String nodeId, String attachNodeId, String targetNodeId) {
  195 + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(nodeId, attachNodeId, null, null, null);
  196 + SequenceFlow attachSequenceFlow = buildBpmnSequenceFlow(attachNodeId, targetNodeId, null, null, null);
  197 + return CollUtil.newArrayList(sequenceFlow, attachSequenceFlow);
  198 + }
  199 +
  200 + /**
177 * 遍历条件节点,构建 SequenceFlow 元素 201 * 遍历条件节点,构建 SequenceFlow 元素
178 * 202 *
179 * @param process Bpmn 流程 203 * @param process Bpmn 流程
@@ -337,7 +361,7 @@ public class SimpleModelUtils { @@ -337,7 +361,7 @@ public class SimpleModelUtils {
337 userTask.setName(node.getName()); 361 userTask.setName(node.getName());
338 362
339 // 人工审批 363 // 人工审批
340 - addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE, BpmUserTaskApproveTypeEnum.USER.getType()); 364 + addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, BpmUserTaskApproveTypeEnum.USER.getType());
341 // 候选人策略为发起人自己 365 // 候选人策略为发起人自己
342 addCandidateElements(BpmTaskCandidateStrategyEnum.START_USER.getStrategy(), null, userTask); 366 addCandidateElements(BpmTaskCandidateStrategyEnum.START_USER.getStrategy(), null, userTask);
343 // 添加表单字段权限属性元素 367 // 添加表单字段权限属性元素
@@ -388,24 +412,17 @@ public class SimpleModelUtils { @@ -388,24 +412,17 @@ public class SimpleModelUtils {
388 */ 412 */
389 private BoundaryEvent buildUserTaskTimeoutBoundaryEvent(UserTask userTask, 413 private BoundaryEvent buildUserTaskTimeoutBoundaryEvent(UserTask userTask,
390 BpmSimpleModelNodeVO.TimeoutHandler timeoutHandler) { 414 BpmSimpleModelNodeVO.TimeoutHandler timeoutHandler) {
391 - // 1.1 定时器边界事件  
392 - BoundaryEvent boundaryEvent = new BoundaryEvent();  
393 - boundaryEvent.setId("Event-" + IdUtil.fastUUID());  
394 - boundaryEvent.setCancelActivity(false); // 设置关联的任务为不会被中断  
395 - boundaryEvent.setAttachedToRef(userTask);  
396 - // 1.2 定义超时时间、最大提醒次数  
397 - TimerEventDefinition eventDefinition = new TimerEventDefinition();  
398 - eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration()); 415 + // 1. 创建 Timeout Boundary Event
  416 + String timeCycle = null;
399 if (Objects.equals(BpmUserTaskTimeoutHandlerTypeEnum.REMINDER.getType(), timeoutHandler.getType()) && 417 if (Objects.equals(BpmUserTaskTimeoutHandlerTypeEnum.REMINDER.getType(), timeoutHandler.getType()) &&
400 timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) { 418 timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) {
401 - eventDefinition.setTimeCycle(String.format("R%d/%s",  
402 - timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration())); 419 + timeCycle = String.format("R%d/%s",
  420 + timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration());
403 } 421 }
404 - boundaryEvent.addEventDefinition(eventDefinition); 422 + BoundaryEvent boundaryEvent = buildTimeoutBoundaryEvent(userTask, BpmBoundaryEventTypeEnum.USER_TASK_TIMEOUT.getType(),
  423 + timeoutHandler.getTimeDuration(), timeCycle, null);
405 424
406 - // 2.1 添加定时器边界事件类型  
407 - addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, BpmBoundaryEventTypeEnum.USER_TASK_TIMEOUT.getType());  
408 - // 2.2 添加超时执行动作元素 425 + // 2 添加超时执行动作元素
409 addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_TYPE, timeoutHandler.getType()); 426 addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_TYPE, timeoutHandler.getType());
410 return boundaryEvent; 427 return boundaryEvent;
411 } 428 }
@@ -445,6 +462,8 @@ public class SimpleModelUtils { @@ -445,6 +462,8 @@ public class SimpleModelUtils {
445 addSignEnable(node.getSignEnable(), userTask); 462 addSignEnable(node.getSignEnable(), userTask);
446 // 审批意见 463 // 审批意见
447 addReasonRequire(node.getReasonRequire(), userTask); 464 addReasonRequire(node.getReasonRequire(), userTask);
  465 + // 节点类型
  466 + addNodeType(node.getType(), userTask);
448 return userTask; 467 return userTask;
449 } 468 }
450 469
@@ -455,7 +474,7 @@ public class SimpleModelUtils { @@ -455,7 +474,7 @@ public class SimpleModelUtils {
455 FlowableListener flowableListener = new FlowableListener(); 474 FlowableListener flowableListener = new FlowableListener();
456 flowableListener.setEvent(TaskListener.EVENTNAME_CREATE); 475 flowableListener.setEvent(TaskListener.EVENTNAME_CREATE);
457 flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); 476 flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
458 - flowableListener.setImplementation(DELEGATE_EXPRESSION); 477 + flowableListener.setImplementation(BpmUserTaskListener.DELEGATE_EXPRESSION);
459 addListenerConfig(flowableListener, node.getTaskCreateListener()); 478 addListenerConfig(flowableListener, node.getTaskCreateListener());
460 flowableListeners.add(flowableListener); 479 flowableListeners.add(flowableListener);
461 } 480 }
@@ -464,7 +483,7 @@ public class SimpleModelUtils { @@ -464,7 +483,7 @@ public class SimpleModelUtils {
464 FlowableListener flowableListener = new FlowableListener(); 483 FlowableListener flowableListener = new FlowableListener();
465 flowableListener.setEvent(TaskListener.EVENTNAME_ASSIGNMENT); 484 flowableListener.setEvent(TaskListener.EVENTNAME_ASSIGNMENT);
466 flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); 485 flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
467 - flowableListener.setImplementation(DELEGATE_EXPRESSION); 486 + flowableListener.setImplementation(BpmUserTaskListener.DELEGATE_EXPRESSION);
468 addListenerConfig(flowableListener, node.getTaskAssignListener()); 487 addListenerConfig(flowableListener, node.getTaskAssignListener());
469 flowableListeners.add(flowableListener); 488 flowableListeners.add(flowableListener);
470 } 489 }
@@ -473,7 +492,7 @@ public class SimpleModelUtils { @@ -473,7 +492,7 @@ public class SimpleModelUtils {
473 FlowableListener flowableListener = new FlowableListener(); 492 FlowableListener flowableListener = new FlowableListener();
474 flowableListener.setEvent(TaskListener.EVENTNAME_COMPLETE); 493 flowableListener.setEvent(TaskListener.EVENTNAME_COMPLETE);
475 flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); 494 flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
476 - flowableListener.setImplementation(DELEGATE_EXPRESSION); 495 + flowableListener.setImplementation(BpmUserTaskListener.DELEGATE_EXPRESSION);
477 addListenerConfig(flowableListener, node.getTaskCompleteListener()); 496 addListenerConfig(flowableListener, node.getTaskCompleteListener());
478 flowableListeners.add(flowableListener); 497 flowableListeners.add(flowableListener);
479 } 498 }
@@ -486,7 +505,7 @@ public class SimpleModelUtils { @@ -486,7 +505,7 @@ public class SimpleModelUtils {
486 BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod); 505 BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod);
487 Assert.notNull(approveMethodEnum, "审批方式({})不能为空", approveMethodEnum); 506 Assert.notNull(approveMethodEnum, "审批方式({})不能为空", approveMethodEnum);
488 // 添加审批方式的扩展属性 507 // 添加审批方式的扩展属性
489 - addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_METHOD, approveMethod); 508 + addExtensionElement(userTask, USER_TASK_APPROVE_METHOD, approveMethod);
490 if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RANDOM) { 509 if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RANDOM) {
491 // 随机审批,不需要设置多实例属性 510 // 随机审批,不需要设置多实例属性
492 return; 511 return;
@@ -514,6 +533,15 @@ public class SimpleModelUtils { @@ -514,6 +533,15 @@ public class SimpleModelUtils {
514 533
515 } 534 }
516 535
  536 + private static class TransactorNodeConvert extends ApproveNodeConvert {
  537 +
  538 + @Override
  539 + public BpmSimpleModelNodeTypeEnum getType() {
  540 + return BpmSimpleModelNodeTypeEnum.TRANSACTOR_NODE;
  541 + }
  542 +
  543 + }
  544 +
517 private static class CopyNodeConvert implements NodeConvert { 545 private static class CopyNodeConvert implements NodeConvert {
518 546
519 @Override 547 @Override
@@ -684,20 +712,16 @@ public class SimpleModelUtils { @@ -684,20 +712,16 @@ public class SimpleModelUtils {
684 712
685 // 2. 添加接收任务的 Timer Boundary Event 713 // 2. 添加接收任务的 Timer Boundary Event
686 if (node.getDelaySetting() != null) { 714 if (node.getDelaySetting() != null) {
687 - // 2.1 定时器边界事件  
688 - BoundaryEvent boundaryEvent = new BoundaryEvent();  
689 - boundaryEvent.setId("Event-" + IdUtil.fastUUID());  
690 - boundaryEvent.setCancelActivity(false);  
691 - boundaryEvent.setAttachedToRef(receiveTask);  
692 - // 2.2 定义超时时间  
693 - TimerEventDefinition eventDefinition = new TimerEventDefinition(); 715 + BoundaryEvent boundaryEvent = null;
694 if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_DATE_TIME.getType())) { 716 if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_DATE_TIME.getType())) {
695 - eventDefinition.setTimeDuration(node.getDelaySetting().getDelayTime()); 717 + boundaryEvent = buildTimeoutBoundaryEvent(receiveTask, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(),
  718 + node.getDelaySetting().getDelayTime(), null, null);
696 } else if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_TIME_DURATION.getType())) { 719 } else if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_TIME_DURATION.getType())) {
697 - eventDefinition.setTimeDate(node.getDelaySetting().getDelayTime()); 720 + boundaryEvent = buildTimeoutBoundaryEvent(receiveTask, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(),
  721 + null, null, node.getDelaySetting().getDelayTime());
  722 + } else {
  723 + throw new UnsupportedOperationException("不支持的延迟类型:" + node.getDelaySetting());
698 } 724 }
699 - boundaryEvent.addEventDefinition(eventDefinition);  
700 - addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType());  
701 flowElements.add(boundaryEvent); 725 flowElements.add(boundaryEvent);
702 } 726 }
703 return flowElements; 727 return flowElements;
@@ -712,23 +736,36 @@ public class SimpleModelUtils { @@ -712,23 +736,36 @@ public class SimpleModelUtils {
712 public static class TriggerNodeConvert implements NodeConvert { 736 public static class TriggerNodeConvert implements NodeConvert {
713 737
714 @Override 738 @Override
715 - public ServiceTask convert(BpmSimpleModelNodeVO node) { 739 + public List<? extends FlowElement> convertList(BpmSimpleModelNodeVO node) {
  740 + Assert.notNull(node.getTriggerSetting(), "触发器节点设置不能为空");
  741 + List<FlowElement> flowElements = new ArrayList<>(2);
  742 + // HTTP 回调请求。需要附加一个 ReceiveTask、发起请求后、等待回调执行
  743 + if (BpmTriggerTypeEnum.HTTP_CALLBACK.getType().equals(node.getTriggerSetting().getType())) {
  744 + Assert.notNull(node.getTriggerSetting().getHttpRequestSetting(), "触发器 HTTP 回调请求设置不能为空");
  745 + ReceiveTask receiveTask = new ReceiveTask();
  746 + receiveTask.setId("Activity_" + IdUtil.fastUUID());
  747 + receiveTask.setName("HTTP 回调");
  748 + node.setAttachNodeId(receiveTask.getId());
  749 + flowElements.add(receiveTask);
  750 + // 重要:设置 callbackTaskDefineKey,用于 HTTP 回调
  751 + node.getTriggerSetting().getHttpRequestSetting().setCallbackTaskDefineKey(receiveTask.getId());
  752 + }
  753 +
716 // 触发器使用 ServiceTask 来实现 754 // 触发器使用 ServiceTask 来实现
717 ServiceTask serviceTask = new ServiceTask(); 755 ServiceTask serviceTask = new ServiceTask();
718 serviceTask.setId(node.getId()); 756 serviceTask.setId(node.getId());
719 serviceTask.setName(node.getName()); 757 serviceTask.setName(node.getName());
720 serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); 758 serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
721 serviceTask.setImplementation("${" + BpmTriggerTaskDelegate.BEAN_NAME + "}"); 759 serviceTask.setImplementation("${" + BpmTriggerTaskDelegate.BEAN_NAME + "}");
722 - if (node.getTriggerSetting() != null) {  
723 - addExtensionElement(serviceTask, TRIGGER_TYPE, node.getTriggerSetting().getType());  
724 - if (node.getTriggerSetting().getHttpRequestSetting() != null) {  
725 - addExtensionElementJson(serviceTask, TRIGGER_PARAM, node.getTriggerSetting().getHttpRequestSetting());  
726 - }  
727 - if (node.getTriggerSetting().getNormalFormSetting() != null) {  
728 - addExtensionElementJson(serviceTask, TRIGGER_PARAM, node.getTriggerSetting().getNormalFormSetting());  
729 - } 760 + addExtensionElement(serviceTask, TRIGGER_TYPE, node.getTriggerSetting().getType());
  761 + if (node.getTriggerSetting().getHttpRequestSetting() != null) {
  762 + addExtensionElementJson(serviceTask, TRIGGER_PARAM, node.getTriggerSetting().getHttpRequestSetting());
730 } 763 }
731 - return serviceTask; 764 + if (node.getTriggerSetting().getFormSettings() != null) {
  765 + addExtensionElementJson(serviceTask, TRIGGER_PARAM, node.getTriggerSetting().getFormSettings());
  766 + }
  767 + flowElements.add(serviceTask);
  768 + return flowElements;
732 } 769 }
733 770
734 @Override 771 @Override
@@ -762,10 +799,131 @@ public class SimpleModelUtils { @@ -762,10 +799,131 @@ public class SimpleModelUtils {
762 799
763 } 800 }
764 801
  802 + private static class ChildProcessConvert implements NodeConvert {
  803 +
  804 + @Override
  805 + public List<FlowElement> convertList(BpmSimpleModelNodeVO node) {
  806 + List<FlowElement> flowElements = new ArrayList<>(2);
  807 + BpmSimpleModelNodeVO.ChildProcessSetting childProcessSetting = node.getChildProcessSetting();
  808 + List<IOParameter> inVariables = childProcessSetting.getInVariables() == null ?
  809 + new ArrayList<>() : new ArrayList<>(childProcessSetting.getInVariables());
  810 + CallActivity callActivity = new CallActivity();
  811 + callActivity.setId(node.getId());
  812 + callActivity.setName(node.getName());
  813 + callActivity.setCalledElementType("key");
  814 + // 1. 是否异步
  815 + if (node.getChildProcessSetting().getAsync()) {
  816 + // TODO @lesan: 这里目前测试没有跳过执行call activity 后面的节点
  817 + callActivity.setAsynchronous(true);
  818 + }
  819 +
  820 + // 2. 调用的子流程
  821 + callActivity.setCalledElement(childProcessSetting.getCalledProcessDefinitionKey());
  822 + callActivity.setProcessInstanceName(childProcessSetting.getCalledProcessDefinitionName());
  823 +
  824 + // 3. 是否自动跳过子流程发起节点
  825 + IOParameter ioParameter = new IOParameter();
  826 + ioParameter.setSourceExpression(childProcessSetting.getSkipStartUserNode().toString());
  827 + ioParameter.setTarget(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE);
  828 + inVariables.add(ioParameter);
  829 +
  830 + // 4. 【默认需要传递的一些变量】流程状态
  831 + ioParameter = new IOParameter();
  832 + ioParameter.setSource(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS);
  833 + ioParameter.setTarget(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS);
  834 + inVariables.add(ioParameter);
  835 +
  836 + // 5. 主→子变量传递、子->主变量传递
  837 + callActivity.setInParameters(inVariables);
  838 + if (ArrayUtil.isNotEmpty(childProcessSetting.getOutVariables()) && ObjUtil.notEqual(childProcessSetting.getAsync(), Boolean.TRUE)) {
  839 + callActivity.setOutParameters(childProcessSetting.getOutVariables());
  840 + }
  841 +
  842 + // 6. 子流程发起人配置
  843 + List<FlowableListener> executionListeners = new ArrayList<>();
  844 + FlowableListener flowableListener = new FlowableListener();
  845 + flowableListener.setEvent(ExecutionListener.EVENTNAME_START);
  846 + flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
  847 + flowableListener.setImplementation(BpmCallActivityListener.DELEGATE_EXPRESSION);
  848 + FieldExtension fieldExtension = new FieldExtension();
  849 + fieldExtension.setFieldName("listenerConfig");
  850 + fieldExtension.setStringValue(JsonUtils.toJsonString(childProcessSetting.getStartUserSetting()));
  851 + flowableListener.getFieldExtensions().add(fieldExtension);
  852 + executionListeners.add(flowableListener);
  853 + callActivity.setExecutionListeners(executionListeners);
  854 +
  855 + // 7. 超时设置
  856 + if (childProcessSetting.getTimeoutSetting() != null && Boolean.TRUE.equals(childProcessSetting.getTimeoutSetting().getEnable())) {
  857 + BoundaryEvent boundaryEvent = null;
  858 + if (childProcessSetting.getTimeoutSetting().getType().equals(BpmDelayTimerTypeEnum.FIXED_DATE_TIME.getType())) {
  859 + boundaryEvent = buildTimeoutBoundaryEvent(callActivity, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(),
  860 + childProcessSetting.getTimeoutSetting().getTimeExpression(), null, null);
  861 + } else if (childProcessSetting.getTimeoutSetting().getType().equals(BpmDelayTimerTypeEnum.FIXED_TIME_DURATION.getType())) {
  862 + boundaryEvent = buildTimeoutBoundaryEvent(callActivity, BpmBoundaryEventTypeEnum.CHILD_PROCESS_TIMEOUT.getType(),
  863 + null, null, childProcessSetting.getTimeoutSetting().getTimeExpression());
  864 + }
  865 + flowElements.add(boundaryEvent);
  866 + }
  867 +
  868 + // 8. 多实例
  869 + if (childProcessSetting.getMultiInstanceSetting() != null && Boolean.TRUE.equals(childProcessSetting.getMultiInstanceSetting().getEnable())) {
  870 + MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics();
  871 + multiInstanceCharacteristics.setSequential(childProcessSetting.getMultiInstanceSetting().getSequential());
  872 + if (childProcessSetting.getMultiInstanceSetting().getSourceType().equals(BpmChildProcessMultiInstanceSourceTypeEnum.FIXED_QUANTITY.getType())) {
  873 + multiInstanceCharacteristics.setLoopCardinality(childProcessSetting.getMultiInstanceSetting().getSource());
  874 + }
  875 + if (childProcessSetting.getMultiInstanceSetting().getSourceType().equals(BpmChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM.getType()) ||
  876 + childProcessSetting.getMultiInstanceSetting().getSourceType().equals(BpmChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM.getType())) {
  877 + multiInstanceCharacteristics.setInputDataItem(childProcessSetting.getMultiInstanceSetting().getSource());
  878 + }
  879 + multiInstanceCharacteristics.setCompletionCondition(String.format(BpmUserTaskApproveMethodEnum.RATIO.getCompletionCondition(),
  880 + String.format("%.2f", childProcessSetting.getMultiInstanceSetting().getApproveRatio() / 100D)));
  881 + callActivity.setLoopCharacteristics(multiInstanceCharacteristics);
  882 + addExtensionElement(callActivity, CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE, childProcessSetting.getMultiInstanceSetting().getSourceType());
  883 + }
  884 +
  885 + // 添加节点类型
  886 + addNodeType(node.getType(), callActivity);
  887 + flowElements.add(callActivity);
  888 + return flowElements;
  889 + }
  890 +
  891 + @Override
  892 + public BpmSimpleModelNodeTypeEnum getType() {
  893 + return BpmSimpleModelNodeTypeEnum.CHILD_PROCESS;
  894 + }
  895 +
  896 + }
  897 +
765 private static String buildGatewayJoinId(String id) { 898 private static String buildGatewayJoinId(String id) {
766 return id + "_join"; 899 return id + "_join";
767 } 900 }
768 901
  902 + private static BoundaryEvent buildTimeoutBoundaryEvent(Activity attachedToRef, Integer type,
  903 + String timeDuration, String timeCycle, String timeDate) {
  904 + // 1.1 定时器边界事件
  905 + BoundaryEvent boundaryEvent = new BoundaryEvent();
  906 + boundaryEvent.setId("Event-" + IdUtil.fastUUID());
  907 + boundaryEvent.setCancelActivity(false); // 设置关联的任务为不会被中断
  908 + boundaryEvent.setAttachedToRef(attachedToRef);
  909 + // 1.2 定义超时时间表达式
  910 + TimerEventDefinition eventDefinition = new TimerEventDefinition();
  911 + if (ObjUtil.isNotNull(timeDuration)) {
  912 + eventDefinition.setTimeDuration(timeDuration);
  913 + }
  914 + if (ObjUtil.isNotNull(timeDuration)) {
  915 + eventDefinition.setTimeCycle(timeCycle);
  916 + }
  917 + if (ObjUtil.isNotNull(timeDate)) {
  918 + eventDefinition.setTimeDate(timeDate);
  919 + }
  920 + boundaryEvent.addEventDefinition(eventDefinition);
  921 +
  922 + // 2. 添加定时器边界事件类型
  923 + addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, type);
  924 + return boundaryEvent;
  925 + }
  926 +
769 // ========== SIMPLE 流程预测相关的方法 ========== 927 // ========== SIMPLE 流程预测相关的方法 ==========
770 928
771 public static List<BpmSimpleModelNodeVO> simulateProcess(BpmSimpleModelNodeVO rootNode, Map<String, Object> variables) { 929 public static List<BpmSimpleModelNodeVO> simulateProcess(BpmSimpleModelNodeVO rootNode, Map<String, Object> variables) {
@@ -785,11 +943,13 @@ public class SimpleModelUtils { @@ -785,11 +943,13 @@ public class SimpleModelUtils {
785 BpmSimpleModelNodeTypeEnum nodeType = BpmSimpleModelNodeTypeEnum.valueOf(currentNode.getType()); 943 BpmSimpleModelNodeTypeEnum nodeType = BpmSimpleModelNodeTypeEnum.valueOf(currentNode.getType());
786 Assert.notNull(nodeType, "模型节点类型不支持"); 944 Assert.notNull(nodeType, "模型节点类型不支持");
787 945
788 - // 情况:START_NODE/START_USER_NODE/APPROVE_NODE/COPY_NODE/END_NODE 946 + // 情况:START_NODE/START_USER_NODE/APPROVE_NODE/COPY_NODE/END_NODE/TRANSACTOR_NODE
789 if (nodeType == BpmSimpleModelNodeTypeEnum.START_NODE 947 if (nodeType == BpmSimpleModelNodeTypeEnum.START_NODE
790 || nodeType == BpmSimpleModelNodeTypeEnum.START_USER_NODE 948 || nodeType == BpmSimpleModelNodeTypeEnum.START_USER_NODE
791 || nodeType == BpmSimpleModelNodeTypeEnum.APPROVE_NODE 949 || nodeType == BpmSimpleModelNodeTypeEnum.APPROVE_NODE
  950 + || nodeType == BpmSimpleModelNodeTypeEnum.TRANSACTOR_NODE
792 || nodeType == BpmSimpleModelNodeTypeEnum.COPY_NODE 951 || nodeType == BpmSimpleModelNodeTypeEnum.COPY_NODE
  952 + || nodeType == BpmSimpleModelNodeTypeEnum.CHILD_PROCESS
793 || nodeType == BpmSimpleModelNodeTypeEnum.END_NODE) { 953 || nodeType == BpmSimpleModelNodeTypeEnum.END_NODE) {
794 // 添加元素 954 // 添加元素
795 resultNodes.add(currentNode); 955 resultNodes.add(currentNode);
@@ -799,8 +959,8 @@ public class SimpleModelUtils { @@ -799,8 +959,8 @@ public class SimpleModelUtils {
799 if (nodeType == BpmSimpleModelNodeTypeEnum.CONDITION_BRANCH_NODE) { 959 if (nodeType == BpmSimpleModelNodeTypeEnum.CONDITION_BRANCH_NODE) {
800 // 查找满足条件的 BpmSimpleModelNodeVO 节点 960 // 查找满足条件的 BpmSimpleModelNodeVO 节点
801 BpmSimpleModelNodeVO matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(), 961 BpmSimpleModelNodeVO matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(),
802 - conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())  
803 - && evalConditionExpress(variables, conditionNode.getConditionSetting())); 962 + conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())
  963 + && evalConditionExpress(variables, conditionNode.getConditionSetting()));
804 if (matchConditionNode == null) { 964 if (matchConditionNode == null) {
805 matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(), 965 matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(),
806 conditionNode -> BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())); 966 conditionNode -> BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow()));
@@ -814,8 +974,8 @@ public class SimpleModelUtils { @@ -814,8 +974,8 @@ public class SimpleModelUtils {
814 if (nodeType == BpmSimpleModelNodeTypeEnum.INCLUSIVE_BRANCH_NODE) { 974 if (nodeType == BpmSimpleModelNodeTypeEnum.INCLUSIVE_BRANCH_NODE) {
815 // 查找满足条件的 BpmSimpleModelNodeVO 节点 975 // 查找满足条件的 BpmSimpleModelNodeVO 节点
816 Collection<BpmSimpleModelNodeVO> matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(), 976 Collection<BpmSimpleModelNodeVO> matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(),
817 - conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())  
818 - && evalConditionExpress(variables, conditionNode.getConditionSetting())); 977 + conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())
  978 + && evalConditionExpress(variables, conditionNode.getConditionSetting()));
819 if (CollUtil.isEmpty(matchConditionNodes)) { 979 if (CollUtil.isEmpty(matchConditionNodes)) {
820 matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(), 980 matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(),
821 conditionNode -> BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())); 981 conditionNode -> BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow()));
@@ -841,27 +1001,4 @@ public class SimpleModelUtils { @@ -841,27 +1001,4 @@ public class SimpleModelUtils {
841 return BpmnModelUtils.evalConditionExpress(variables, buildConditionExpression(conditionSetting)); 1001 return BpmnModelUtils.evalConditionExpress(variables, buildConditionExpression(conditionSetting));
842 } 1002 }
843 1003
844 - // TODO @芋艿:【高】要不要优化下,抽个 HttpUtils  
845 -  
846 - /**  
847 - * 添加 HTTP 请求参数。请求头或者请求体  
848 - *  
849 - * @param params HTTP 请求参数  
850 - * @param paramSettings HTTP 请求参数设置  
851 - * @param processVariables 流程变量  
852 - */  
853 - public static void addHttpRequestParam(MultiValueMap<String, String> params,  
854 - List<BpmSimpleModelNodeVO.HttpRequestParam> paramSettings,  
855 - Map<String, Object> processVariables) {  
856 - if (CollUtil.isEmpty(paramSettings)) {  
857 - return;  
858 - }  
859 - paramSettings.forEach(item -> {  
860 - if (item.getType().equals(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType())) {  
861 - params.add(item.getKey(), item.getValue());  
862 - } else if (item.getType().equals(BpmHttpRequestParamTypeEnum.FROM_FORM.getType())) {  
863 - params.add(item.getKey(), processVariables.get(item.getValue()).toString());  
864 - }  
865 - });  
866 - }  
867 } 1004 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
@@ -16,6 +16,7 @@ import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; @@ -16,6 +16,7 @@ import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
16 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; 16 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum;
17 import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; 17 import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
18 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; 18 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
  19 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
19 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; 20 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
20 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; 21 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
21 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; 22 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils;
@@ -23,9 +24,7 @@ import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceCopyService; @@ -23,9 +24,7 @@ import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceCopyService;
23 import jakarta.annotation.Resource; 24 import jakarta.annotation.Resource;
24 import jakarta.validation.Valid; 25 import jakarta.validation.Valid;
25 import lombok.extern.slf4j.Slf4j; 26 import lombok.extern.slf4j.Slf4j;
26 -import org.flowable.bpmn.model.BpmnModel;  
27 -import org.flowable.bpmn.model.StartEvent;  
28 -import org.flowable.bpmn.model.UserTask; 27 +import org.flowable.bpmn.model.*;
29 import org.flowable.common.engine.impl.db.SuspensionState; 28 import org.flowable.common.engine.impl.db.SuspensionState;
30 import org.flowable.engine.HistoryService; 29 import org.flowable.engine.HistoryService;
31 import org.flowable.engine.RepositoryService; 30 import org.flowable.engine.RepositoryService;
@@ -41,13 +40,12 @@ import org.springframework.stereotype.Service; @@ -41,13 +40,12 @@ import org.springframework.stereotype.Service;
41 import org.springframework.transaction.annotation.Transactional; 40 import org.springframework.transaction.annotation.Transactional;
42 import org.springframework.validation.annotation.Validated; 41 import org.springframework.validation.annotation.Validated;
43 42
44 -import java.util.List;  
45 -import java.util.Map;  
46 -import java.util.Objects; 43 +import java.util.*;
47 44
48 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 45 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
49 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; 46 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
50 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; 47 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
  48 +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.parseCandidateStrategy;
51 49
52 /** 50 /**
53 * 流程模型实现:主要进行 Flowable {@link Model} 的维护 51 * 流程模型实现:主要进行 Flowable {@link Model} 的维护
@@ -209,11 +207,11 @@ public class BpmModelServiceImpl implements BpmModelService { @@ -209,11 +207,11 @@ public class BpmModelServiceImpl implements BpmModelService {
209 public void deployModel(Long userId, String id) { 207 public void deployModel(Long userId, String id) {
210 // 1.1 校验流程模型存在 208 // 1.1 校验流程模型存在
211 Model model = validateModelManager(id, userId); 209 Model model = validateModelManager(id, userId);
  210 + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
212 // 1.2 校验流程图 211 // 1.2 校验流程图
213 byte[] bpmnBytes = getModelBpmnXML(model.getId()); 212 byte[] bpmnBytes = getModelBpmnXML(model.getId());
214 - validateBpmnXml(bpmnBytes); 213 + validateBpmnXml(bpmnBytes, metaInfo.getType());
215 // 1.3 校验表单已配 214 // 1.3 校验表单已配
216 - BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);  
217 BpmFormDO form = validateFormConfig(metaInfo); 215 BpmFormDO form = validateFormConfig(metaInfo);
218 // 1.4 校验任务分配规则已配置 216 // 1.4 校验任务分配规则已配置
219 taskCandidateInvoker.validateBpmnConfig(bpmnBytes); 217 taskCandidateInvoker.validateBpmnConfig(bpmnBytes);
@@ -233,7 +231,7 @@ public class BpmModelServiceImpl implements BpmModelService { @@ -233,7 +231,7 @@ public class BpmModelServiceImpl implements BpmModelService {
233 repositoryService.saveModel(model); 231 repositoryService.saveModel(model);
234 } 232 }
235 233
236 - private void validateBpmnXml(byte[] bpmnBytes) { 234 + private void validateBpmnXml(byte[] bpmnBytes, Integer type) {
237 BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes); 235 BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes);
238 if (bpmnModel == null) { 236 if (bpmnModel == null) {
239 throw exception(MODEL_NOT_EXISTS); 237 throw exception(MODEL_NOT_EXISTS);
@@ -250,6 +248,17 @@ public class BpmModelServiceImpl implements BpmModelService { @@ -250,6 +248,17 @@ public class BpmModelServiceImpl implements BpmModelService {
250 throw exception(MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS, userTask.getId()); 248 throw exception(MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS, userTask.getId());
251 } 249 }
252 }); 250 });
  251 + // TODO @小北:是不是可以 UserTask firUserTask = CollUtil.get(userTasks, BpmModelTypeEnum.BPMN.getType().equals(type) ? 0 : 1);然后,最好判空。。。极端情况下,没 usertask ,哈哈哈哈。
  252 + // 3. 校验第一个用户任务节点的规则类型是否为“审批人自选”
  253 + Map<Integer, UserTask> userTaskMap = new HashMap<>();
  254 + // BPMN 设计器,校验第一个用户任务节点
  255 + userTaskMap.put(BpmModelTypeEnum.BPMN.getType(), userTasks.get(0));
  256 + // SIMPLE 设计器,第一个节点固定为发起人所以校验第二个用户任务节点
  257 + userTaskMap.put(BpmModelTypeEnum.SIMPLE.getType(), userTasks.get(1));
  258 + Integer candidateStrategy = parseCandidateStrategy(userTaskMap.get(type));
  259 + if (Objects.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) {
  260 + throw exception(MODEL_DEPLOY_FAIL_FIRST_USER_TASK_CANDIDATE_STRATEGY_ERROR, userTaskMap.get(type).getName());
  261 + }
253 } 262 }
254 263
255 @Override 264 @Override
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
@@ -28,8 +28,7 @@ import java.util.*; @@ -28,8 +28,7 @@ import java.util.*;
28 28
29 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 29 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
30 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.addIfNotNull; 30 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.addIfNotNull;
31 -import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_DEFINITION_KEY_NOT_MATCH;  
32 -import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_DEFINITION_NAME_NOT_MATCH; 31 +import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
33 import static java.util.Collections.emptyList; 32 import static java.util.Collections.emptyList;
34 33
35 /** 34 /**
@@ -144,9 +143,8 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ @@ -144,9 +143,8 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
144 143
145 // 插入拓展表 144 // 插入拓展表
146 BpmProcessDefinitionInfoDO definitionDO = BeanUtils.toBean(modelMetaInfo, BpmProcessDefinitionInfoDO.class) 145 BpmProcessDefinitionInfoDO definitionDO = BeanUtils.toBean(modelMetaInfo, BpmProcessDefinitionInfoDO.class)
147 - .setModelId(model.getId()).setProcessDefinitionId(definition.getId()) 146 + .setModelId(model.getId()).setCategory(model.getCategory()).setProcessDefinitionId(definition.getId())
148 .setModelType(modelMetaInfo.getType()).setSimpleModel(simpleJson); 147 .setModelType(modelMetaInfo.getType()).setSimpleModel(simpleJson);
149 -  
150 if (form != null) { 148 if (form != null) {
151 definitionDO.setFormFields(form.getFields()).setFormConf(form.getConf()); 149 definitionDO.setFormFields(form.getFields()).setFormConf(form.getConf());
152 } 150 }
@@ -156,16 +154,25 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ @@ -156,16 +154,25 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
156 154
157 @Override 155 @Override
158 public void updateProcessDefinitionState(String id, Integer state) { 156 public void updateProcessDefinitionState(String id, Integer state) {
  157 + ProcessDefinition processDefinition = repositoryService.getProcessDefinition(id);
  158 + if (processDefinition == null) {
  159 + throw exception(PROCESS_DEFINITION_NOT_EXISTS);
  160 + }
  161 +
159 // 激活 162 // 激活
160 if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), state)) { 163 if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), state)) {
161 - repositoryService.activateProcessDefinitionById(id, false, null); 164 + if (processDefinition.isSuspended()) {
  165 + repositoryService.activateProcessDefinitionById(id, false, null);
  166 + }
162 return; 167 return;
163 } 168 }
164 // 挂起 169 // 挂起
165 if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), state)) { 170 if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), state)) {
166 // suspendProcessInstances = false,进行中的任务,不进行挂起。 171 // suspendProcessInstances = false,进行中的任务,不进行挂起。
167 // 原因:只要新的流程不允许发起即可,老流程继续可以执行。 172 // 原因:只要新的流程不允许发起即可,老流程继续可以执行。
168 - repositoryService.suspendProcessDefinitionById(id, false, null); 173 + if (!processDefinition.isSuspended()) {
  174 + repositoryService.suspendProcessDefinitionById(id, false, null);
  175 + }
169 return; 176 return;
170 } 177 }
171 log.error("[updateProcessDefinitionState][流程定义({}) 修改未知状态({})]", id, state); 178 log.error("[updateProcessDefinitionState][流程定义({}) 修改未知状态({})]", id, state);
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java
@@ -7,6 +7,7 @@ import jakarta.validation.Valid; @@ -7,6 +7,7 @@ import jakarta.validation.Valid;
7 import org.flowable.engine.history.HistoricProcessInstance; 7 import org.flowable.engine.history.HistoricProcessInstance;
8 import org.flowable.engine.runtime.ProcessInstance; 8 import org.flowable.engine.runtime.ProcessInstance;
9 9
  10 +import java.util.Collection;
10 import java.util.List; 11 import java.util.List;
11 import java.util.Map; 12 import java.util.Map;
12 import java.util.Set; 13 import java.util.Set;
@@ -84,7 +85,6 @@ public interface BpmProcessInstanceService { @@ -84,7 +85,6 @@ public interface BpmProcessInstanceService {
84 PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, 85 PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId,
85 @Valid BpmProcessInstancePageReqVO pageReqVO); 86 @Valid BpmProcessInstancePageReqVO pageReqVO);
86 87
87 - // TODO @芋艿:重点在 review 下  
88 /** 88 /**
89 * 获取审批详情。 89 * 获取审批详情。
90 * <p> 90 * <p>
@@ -97,6 +97,15 @@ public interface BpmProcessInstanceService { @@ -97,6 +97,15 @@ public interface BpmProcessInstanceService {
97 BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, @Valid BpmApprovalDetailReqVO reqVO); 97 BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, @Valid BpmApprovalDetailReqVO reqVO);
98 98
99 /** 99 /**
  100 + * 获取下一个执行节点信息
  101 + *
  102 + * @param loginUserId 登录人的用户编号
  103 + * @param reqVO 请求信息
  104 + * @return 下一个执行节点信息
  105 + */
  106 + List<BpmApprovalDetailRespVO.ActivityNode> getNextApprovalNodes(Long loginUserId, @Valid BpmApprovalDetailReqVO reqVO);
  107 +
  108 + /**
100 * 获取流程实例的 BPMN 模型视图 109 * 获取流程实例的 BPMN 模型视图
101 * 110 *
102 * @param id 流程实例的编号 111 * @param id 流程实例的编号
@@ -148,6 +157,22 @@ public interface BpmProcessInstanceService { @@ -148,6 +157,22 @@ public interface BpmProcessInstanceService {
148 */ 157 */
149 void updateProcessInstanceReject(ProcessInstance processInstance, String reason); 158 void updateProcessInstanceReject(ProcessInstance processInstance, String reason);
150 159
  160 + /**
  161 + * 更新 ProcessInstance 的变量
  162 + *
  163 + * @param id 流程编号
  164 + * @param variables 流程变量
  165 + */
  166 + void updateProcessInstanceVariables(String id, Map<String, Object> variables);
  167 +
  168 + /**
  169 + * 删除 ProcessInstance 的变量
  170 + *
  171 + * @param id 流程编号
  172 + * @param variableNames 流程变量名
  173 + */
  174 + void removeProcessInstanceVariables(String id, Collection<String> variableNames);
  175 +
151 // ========== Event 事件相关方法 ========== 176 // ========== Event 事件相关方法 ==========
152 177
153 /** 178 /**
@@ -158,11 +183,9 @@ public interface BpmProcessInstanceService { @@ -158,11 +183,9 @@ public interface BpmProcessInstanceService {
158 void processProcessInstanceCompleted(ProcessInstance instance); 183 void processProcessInstanceCompleted(ProcessInstance instance);
159 184
160 /** 185 /**
161 - * 更新 ProcessInstance 的变量 186 + * 处理 ProcessInstance 开始事件,例如说:流程前置通知
162 * 187 *
163 - * @param id 流程编号  
164 - * @param variables 流程变量 188 + * @param instance 流程任务
165 */ 189 */
166 - void updateProcessInstanceVariables(String id, Map<String, Object> variables);  
167 - 190 + void processProcessInstanceCreated(ProcessInstance instance);
168 } 191 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
@@ -5,6 +5,7 @@ import cn.hutool.core.collection.ListUtil; @@ -5,6 +5,7 @@ import cn.hutool.core.collection.ListUtil;
5 import cn.hutool.core.date.DateUtil; 5 import cn.hutool.core.date.DateUtil;
6 import cn.hutool.core.lang.Assert; 6 import cn.hutool.core.lang.Assert;
7 import cn.hutool.core.util.ArrayUtil; 7 import cn.hutool.core.util.ArrayUtil;
  8 +import cn.hutool.core.util.ObjUtil;
8 import cn.hutool.core.util.ObjectUtil; 9 import cn.hutool.core.util.ObjectUtil;
9 import cn.hutool.core.util.StrUtil; 10 import cn.hutool.core.util.StrUtil;
10 import cn.iocoder.yudao.framework.common.pojo.PageResult; 11 import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -13,6 +14,7 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils; @@ -13,6 +14,7 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
13 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; 14 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
14 import cn.iocoder.yudao.framework.common.util.object.PageUtils; 15 import cn.iocoder.yudao.framework.common.util.object.PageUtils;
15 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; 16 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
  17 +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
16 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; 18 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
17 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; 19 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
18 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; 20 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
@@ -28,10 +30,11 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; @@ -28,10 +30,11 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
28 import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; 30 import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
29 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; 31 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
30 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; 32 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
31 -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept.BpmTaskCandidateStartUserSelectStrategy; 33 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
32 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; 34 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
33 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; 35 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
34 import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; 36 import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher;
  37 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils;
35 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; 38 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
36 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; 39 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
37 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; 40 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils;
@@ -54,11 +57,13 @@ import org.flowable.engine.history.HistoricProcessInstanceQuery; @@ -54,11 +57,13 @@ import org.flowable.engine.history.HistoricProcessInstanceQuery;
54 import org.flowable.engine.repository.ProcessDefinition; 57 import org.flowable.engine.repository.ProcessDefinition;
55 import org.flowable.engine.runtime.ProcessInstance; 58 import org.flowable.engine.runtime.ProcessInstance;
56 import org.flowable.engine.runtime.ProcessInstanceBuilder; 59 import org.flowable.engine.runtime.ProcessInstanceBuilder;
  60 +import org.flowable.task.api.Task;
57 import org.flowable.task.api.history.HistoricTaskInstance; 61 import org.flowable.task.api.history.HistoricTaskInstance;
58 import org.springframework.context.annotation.Lazy; 62 import org.springframework.context.annotation.Lazy;
59 import org.springframework.stereotype.Service; 63 import org.springframework.stereotype.Service;
60 import org.springframework.transaction.annotation.Transactional; 64 import org.springframework.transaction.annotation.Transactional;
61 import org.springframework.validation.annotation.Validated; 65 import org.springframework.validation.annotation.Validated;
  66 +import org.springframework.web.client.RestTemplate;
62 67
63 import java.util.*; 68 import java.util.*;
64 69
@@ -67,6 +72,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. @@ -67,6 +72,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
67 import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode; 72 import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode;
68 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; 73 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
69 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; 74 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID;
  75 +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.parseNodeType;
70 import static java.util.Arrays.asList; 76 import static java.util.Arrays.asList;
71 import static java.util.Collections.singletonList; 77 import static java.util.Collections.singletonList;
72 import static org.flowable.bpmn.constants.BpmnXMLConstants.*; 78 import static org.flowable.bpmn.constants.BpmnXMLConstants.*;
@@ -116,6 +122,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -116,6 +122,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
116 @Resource 122 @Resource
117 private BpmProcessIdRedisDAO processIdRedisDAO; 123 private BpmProcessIdRedisDAO processIdRedisDAO;
118 124
  125 + @Resource
  126 + private RestTemplate restTemplate;
  127 +
119 // ========== Query 查询相关方法 ========== 128 // ========== Query 查询相关方法 ==========
120 129
121 @Override 130 @Override
@@ -144,7 +153,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -144,7 +153,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
144 } 153 }
145 154
146 private Map<String, String> getFormFieldsPermission(BpmnModel bpmnModel, 155 private Map<String, String> getFormFieldsPermission(BpmnModel bpmnModel,
147 - String activityId, String taskId) { 156 + String activityId, String taskId) {
148 // 1. 获取流程活动编号。流程活动 Id 为空事,从流程任务中获取流程活动 Id 157 // 1. 获取流程活动编号。流程活动 Id 为空事,从流程任务中获取流程活动 Id
149 if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(taskId)) { 158 if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(taskId)) {
150 activityId = Optional.ofNullable(taskService.getHistoricTask(taskId)) 159 activityId = Optional.ofNullable(taskService.getHistoricTask(taskId))
@@ -164,7 +173,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -164,7 +173,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
164 Long startUserId = loginUserId; // 流程发起人 173 Long startUserId = loginUserId; // 流程发起人
165 HistoricProcessInstance historicProcessInstance = null; // 流程实例 174 HistoricProcessInstance historicProcessInstance = null; // 流程实例
166 Integer processInstanceStatus = BpmProcessInstanceStatusEnum.NOT_START.getStatus(); // 流程状态 175 Integer processInstanceStatus = BpmProcessInstanceStatusEnum.NOT_START.getStatus(); // 流程状态
167 - Map<String, Object> processVariables = reqVO.getProcessVariables(); // 流程变量 176 + Map<String, Object> processVariables = new HashMap<>(); // 流程变量
168 // 1.2 如果是流程已发起的场景,则使用流程实例的数据 177 // 1.2 如果是流程已发起的场景,则使用流程实例的数据
169 if (reqVO.getProcessInstanceId() != null) { 178 if (reqVO.getProcessInstanceId() != null) {
170 historicProcessInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); 179 historicProcessInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId());
@@ -173,7 +182,13 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -173,7 +182,13 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
173 } 182 }
174 startUserId = Long.valueOf(historicProcessInstance.getStartUserId()); 183 startUserId = Long.valueOf(historicProcessInstance.getStartUserId());
175 processInstanceStatus = FlowableUtils.getProcessInstanceStatus(historicProcessInstance); 184 processInstanceStatus = FlowableUtils.getProcessInstanceStatus(historicProcessInstance);
176 - processVariables = historicProcessInstance.getProcessVariables(); 185 + // 合并 DB 和前端传递的流量变量,以前端的为主
  186 + if (CollUtil.isNotEmpty(historicProcessInstance.getProcessVariables())) {
  187 + processVariables.putAll(historicProcessInstance.getProcessVariables());
  188 + }
  189 + }
  190 + if (CollUtil.isNotEmpty(reqVO.getProcessVariables())) {
  191 + processVariables.putAll(reqVO.getProcessVariables());
177 } 192 }
178 // 1.3 读取其它相关数据 193 // 1.3 读取其它相关数据
179 ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition( 194 ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition(
@@ -205,14 +220,16 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -205,14 +220,16 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
205 } 220 }
206 221
207 // 3.1 计算当前登录用户的待办任务 222 // 3.1 计算当前登录用户的待办任务
208 - // TODO @jason:有一个极端情况,如果一个用户有 2 个 task A 和 B,A 已经通过,B 需要审核。这个时,通过 A 进来,todo 拿到  
209 - // B,会不会表单权限不一致哈。  
210 - BpmTaskRespVO todoTask = taskService.getFirstTodoTask(loginUserId, reqVO.getProcessInstanceId());  
211 - 223 + BpmTaskRespVO todoTask = taskService.getTodoTask(loginUserId, reqVO.getTaskId(), reqVO.getProcessInstanceId());
212 // 3.2 预测未运行节点的审批信息 224 // 3.2 预测未运行节点的审批信息
213 List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel, 225 List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel,
214 processDefinitionInfo, 226 processDefinitionInfo,
215 processVariables, activities); 227 processVariables, activities);
  228 + // 3.3 如果是发起动作,activityId 为开始节点,不校验审批人自选节点
  229 + if (ObjUtil.equals(reqVO.getActivityId(), BpmnModelConstants.START_USER_NODE_ID)) {
  230 + simulateActivityNodes.removeIf(node ->
  231 + BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy().equals(node.getCandidateStrategy()));
  232 + }
216 233
217 // 4. 拼接最终数据 234 // 4. 拼接最终数据
218 return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance, 235 return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance,
@@ -220,6 +237,62 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -220,6 +237,62 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
220 } 237 }
221 238
222 @Override 239 @Override
  240 + public List<ActivityNode> getNextApprovalNodes(Long loginUserId, BpmApprovalDetailReqVO reqVO) {
  241 + // 1.1 校验任务存在,且是当前用户的
  242 + Task task = taskService.validateTask(loginUserId, reqVO.getTaskId());
  243 + // 1.2 校验流程实例存在
  244 + ProcessInstance instance = getProcessInstance(task.getProcessInstanceId());
  245 + if (instance == null) {
  246 + throw exception(PROCESS_INSTANCE_NOT_EXISTS);
  247 + }
  248 + HistoricProcessInstance historicProcessInstance = getHistoricProcessInstance(task.getProcessInstanceId());
  249 + if (historicProcessInstance == null) {
  250 + throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS);
  251 + }
  252 + // 1.3 校验BpmnModel
  253 + BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(task.getProcessDefinitionId());
  254 + if (bpmnModel == null) {
  255 + return null;
  256 + }
  257 +
  258 + // 2. 设置流程变量
  259 + Map<String, Object> processVariables = new HashMap<>();
  260 + // 2.1 获取历史中流程变量
  261 + if (CollUtil.isNotEmpty(historicProcessInstance.getProcessVariables())) {
  262 + processVariables.putAll(historicProcessInstance.getProcessVariables());
  263 + }
  264 + // 2.2 合并前端传递的流程变量,以前端为准
  265 + if (CollUtil.isNotEmpty(reqVO.getProcessVariables())) {
  266 + processVariables.putAll(reqVO.getProcessVariables());
  267 + }
  268 +
  269 + // 3 获取当前任务节点的信息
  270 + // 3.1 获取下一个将要执行的节点集合
  271 + FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey());
  272 + List<FlowNode> nextFlowNodes = BpmnModelUtils.getNextFlowNodes(flowElement, bpmnModel, processVariables);
  273 + return convertList(nextFlowNodes, node -> {
  274 + List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(),
  275 + loginUserId, historicProcessInstance.getProcessDefinitionId(), processVariables);
  276 + // 3.2 获取节点的审批人信息
  277 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(candidateUserIds);
  278 + // 3.3 获取节点的审批人部门信息
  279 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
  280 + // 3.4 存在一个节点多人审批的情况,组装审批人信息
  281 + List<UserSimpleBaseVO> candidateUsers = new ArrayList<>();
  282 + userMap.forEach((key, value) -> candidateUsers.add(BpmProcessInstanceConvert.INSTANCE.buildUser(key, userMap, deptMap)));
  283 + return new ActivityNode().setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType())
  284 + .setId(node.getId())
  285 + .setName(node.getName())
  286 + .setStatus(BpmTaskStatusEnum.RUNNING.getStatus())
  287 + .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node))
  288 + // TODO @小北:先把 candidateUserIds 设置完,然后最后拼接 candidateUsers 信息。这样,如果有多个节点,就不用重复查询啦;类似 buildApprovalDetail 思路;
  289 + // TODO 先拼接处 List ActivityNode
  290 + // TODO 接着,再起一段,处理 adminUserApi.getUserMap(candidateUserIds)、deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId))
  291 + .setCandidateUsers(candidateUsers);
  292 + });
  293 + }
  294 +
  295 + @Override
223 @SuppressWarnings("unchecked") 296 @SuppressWarnings("unchecked")
224 public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, 297 public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId,
225 BpmProcessInstancePageReqVO pageReqVO) { 298 BpmProcessInstancePageReqVO pageReqVO) {
@@ -283,15 +356,15 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -283,15 +356,15 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
283 * 主要是,拼接审批人的用户信息、部门信息 356 * 主要是,拼接审批人的用户信息、部门信息
284 */ 357 */
285 private BpmApprovalDetailRespVO buildApprovalDetail(BpmApprovalDetailReqVO reqVO, 358 private BpmApprovalDetailRespVO buildApprovalDetail(BpmApprovalDetailReqVO reqVO,
286 - BpmnModel bpmnModel,  
287 - ProcessDefinition processDefinition,  
288 - BpmProcessDefinitionInfoDO processDefinitionInfo,  
289 - HistoricProcessInstance processInstance,  
290 - Integer processInstanceStatus,  
291 - List<ActivityNode> endApprovalNodeInfos,  
292 - List<ActivityNode> runningApprovalNodeInfos,  
293 - List<ActivityNode> simulateApprovalNodeInfos,  
294 - BpmTaskRespVO todoTask) { 359 + BpmnModel bpmnModel,
  360 + ProcessDefinition processDefinition,
  361 + BpmProcessDefinitionInfoDO processDefinitionInfo,
  362 + HistoricProcessInstance processInstance,
  363 + Integer processInstanceStatus,
  364 + List<ActivityNode> endApprovalNodeInfos,
  365 + List<ActivityNode> runningApprovalNodeInfos,
  366 + List<ActivityNode> simulateApprovalNodeInfos,
  367 + BpmTaskRespVO todoTask) {
295 // 1. 获取所有需要读取用户信息的 userIds 368 // 1. 获取所有需要读取用户信息的 userIds
296 List<ActivityNode> approveNodes = newArrayList( 369 List<ActivityNode> approveNodes = newArrayList(
297 asList(endApprovalNodeInfos, runningApprovalNodeInfos, simulateApprovalNodeInfos)); 370 asList(endApprovalNodeInfos, runningApprovalNodeInfos, simulateApprovalNodeInfos));
@@ -313,19 +386,21 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -313,19 +386,21 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
313 * 获得【已结束】的活动节点们 386 * 获得【已结束】的活动节点们
314 */ 387 */
315 private List<ActivityNode> getEndActivityNodeList(Long startUserId, BpmnModel bpmnModel, 388 private List<ActivityNode> getEndActivityNodeList(Long startUserId, BpmnModel bpmnModel,
316 - BpmProcessDefinitionInfoDO processDefinitionInfo,  
317 - HistoricProcessInstance historicProcessInstance, Integer processInstanceStatus,  
318 - List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) { 389 + BpmProcessDefinitionInfoDO processDefinitionInfo,
  390 + HistoricProcessInstance historicProcessInstance, Integer processInstanceStatus,
  391 + List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) {
319 // 遍历 tasks 列表,只处理已结束的 UserTask 392 // 遍历 tasks 列表,只处理已结束的 UserTask
320 - // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities  
321 - // 的话,它无法成为一个节点 393 + // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities 的话,它无法成为一个节点
  394 + // TODO @芋艿:子流程只有activity,这里获取不到已结束的子流程;
  395 + // TODO @lesan:【子流程】基于 activities 查询出 usertask、callactivity,然后拼接?如果是子流程,就是可以点击过去?
322 List<HistoricTaskInstance> endTasks = filterList(tasks, task -> task.getEndTime() != null); 396 List<HistoricTaskInstance> endTasks = filterList(tasks, task -> task.getEndTime() != null);
323 List<ActivityNode> approvalNodes = convertList(endTasks, task -> { 397 List<ActivityNode> approvalNodes = convertList(endTasks, task -> {
324 FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); 398 FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
325 ActivityNode activityNode = new ActivityNode().setId(task.getTaskDefinitionKey()).setName(task.getName()) 399 ActivityNode activityNode = new ActivityNode().setId(task.getTaskDefinitionKey()).setName(task.getName())
326 .setNodeType(START_USER_NODE_ID.equals(task.getTaskDefinitionKey()) 400 .setNodeType(START_USER_NODE_ID.equals(task.getTaskDefinitionKey())
327 ? BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType() 401 ? BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType()
328 - : BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()) 402 + : ObjUtil.defaultIfNull(parseNodeType(flowNode), // 目的:解决“办理节点”的识别
  403 + BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()))
329 .setStatus(FlowableUtils.getTaskStatus(task)) 404 .setStatus(FlowableUtils.getTaskStatus(task))
330 .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode)) 405 .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))
331 .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime())) 406 .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime()))
@@ -381,18 +456,19 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -381,18 +456,19 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
381 * 获得【进行中】的活动节点们 456 * 获得【进行中】的活动节点们
382 */ 457 */
383 private List<ActivityNode> getRunApproveNodeList(Long startUserId, 458 private List<ActivityNode> getRunApproveNodeList(Long startUserId,
384 - BpmnModel bpmnModel,  
385 - ProcessDefinition processDefinition,  
386 - Map<String, Object> processVariables,  
387 - List<HistoricActivityInstance> activities,  
388 - List<HistoricTaskInstance> tasks) {  
389 - // 构建运行中的任务,基于 activityId 分组 459 + BpmnModel bpmnModel,
  460 + ProcessDefinition processDefinition,
  461 + Map<String, Object> processVariables,
  462 + List<HistoricActivityInstance> activities,
  463 + List<HistoricTaskInstance> tasks) {
  464 + // 构建运行中的任务、子流程,基于 activityId 分组
390 List<HistoricActivityInstance> runActivities = filterList(activities, activity -> activity.getEndTime() == null 465 List<HistoricActivityInstance> runActivities = filterList(activities, activity -> activity.getEndTime() == null
391 - && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_TASK_USER))); 466 + && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_TASK_USER, ELEMENT_CALL_ACTIVITY)));
392 Map<String, List<HistoricActivityInstance>> runningTaskMap = convertMultiMap(runActivities, 467 Map<String, List<HistoricActivityInstance>> runningTaskMap = convertMultiMap(runActivities,
393 HistoricActivityInstance::getActivityId); 468 HistoricActivityInstance::getActivityId);
394 469
395 // 按照 activityId 分组,构建 ApprovalNodeInfo 节点 470 // 按照 activityId 分组,构建 ApprovalNodeInfo 节点
  471 + // TODO @lesan:【子流程】在子流程进行审批的时候,HistoricActivityInstance 里面可以拿到 runActivities.get(0).getCalledProcessInstanceId()。要不要支持跳转???
396 Map<String, HistoricTaskInstance> taskMap = convertMap(tasks, HistoricTaskInstance::getId); 472 Map<String, HistoricTaskInstance> taskMap = convertMap(tasks, HistoricTaskInstance::getId);
397 return convertList(runningTaskMap.entrySet(), entry -> { 473 return convertList(runningTaskMap.entrySet(), entry -> {
398 String activityId = entry.getKey(); 474 String activityId = entry.getKey();
@@ -402,7 +478,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -402,7 +478,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
402 HistoricActivityInstance firstActivity = CollUtil.getFirst(taskActivities); // 取第一个任务,会签/或签的任务,开始时间相同 478 HistoricActivityInstance firstActivity = CollUtil.getFirst(taskActivities); // 取第一个任务,会签/或签的任务,开始时间相同
403 ActivityNode activityNode = new ActivityNode().setId(firstActivity.getActivityId()) 479 ActivityNode activityNode = new ActivityNode().setId(firstActivity.getActivityId())
404 .setName(firstActivity.getActivityName()) 480 .setName(firstActivity.getActivityName())
405 - .setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()) 481 + .setNodeType(ObjUtil.defaultIfNull(parseNodeType(flowNode), // 目的:解决“办理节点”和"子流程"的识别
  482 + BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()))
406 .setStatus(BpmTaskStatusEnum.RUNNING.getStatus()) 483 .setStatus(BpmTaskStatusEnum.RUNNING.getStatus())
407 .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode)) 484 .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))
408 .setStartTime(DateUtils.of(CollUtil.getFirst(taskActivities).getStartTime())) 485 .setStartTime(DateUtils.of(CollUtil.getFirst(taskActivities).getStartTime()))
@@ -410,6 +487,11 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -410,6 +487,11 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
410 // 处理每个任务的 tasks 属性 487 // 处理每个任务的 tasks 属性
411 for (HistoricActivityInstance activity : taskActivities) { 488 for (HistoricActivityInstance activity : taskActivities) {
412 HistoricTaskInstance task = taskMap.get(activity.getTaskId()); 489 HistoricTaskInstance task = taskMap.get(activity.getTaskId());
  490 + // 特殊情况:子流程节点 ChildProcess 仅存在于 activity 中,并且没有自身的 task,需要跳过执行
  491 + // TODO @芋艿:后续看看怎么优化!
  492 + if (task == null) {
  493 + continue;
  494 + }
413 activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task)); 495 activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task));
414 // 加签子任务,需要过滤掉已经完成的加签子任务 496 // 加签子任务,需要过滤掉已经完成的加签子任务
415 List<HistoricTaskInstance> childrenTasks = filterList( 497 List<HistoricTaskInstance> childrenTasks = filterList(
@@ -440,9 +522,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -440,9 +522,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
440 * 获得【预测(未来)】的活动节点们 522 * 获得【预测(未来)】的活动节点们
441 */ 523 */
442 private List<ActivityNode> getSimulateApproveNodeList(Long startUserId, BpmnModel bpmnModel, 524 private List<ActivityNode> getSimulateApproveNodeList(Long startUserId, BpmnModel bpmnModel,
443 - BpmProcessDefinitionInfoDO processDefinitionInfo,  
444 - Map<String, Object> processVariables,  
445 - List<HistoricActivityInstance> activities) { 525 + BpmProcessDefinitionInfoDO processDefinitionInfo,
  526 + Map<String, Object> processVariables,
  527 + List<HistoricActivityInstance> activities) {
446 // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance 528 // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance
447 // 包括了历史的操作,不是只有 startEvent 到当前节点的记录 529 // 包括了历史的操作,不是只有 startEvent 到当前节点的记录
448 Set<String> runActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId); 530 Set<String> runActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId);
@@ -464,8 +546,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -464,8 +546,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
464 } 546 }
465 547
466 private ActivityNode buildNotRunApproveNodeForSimple(Long startUserId, BpmnModel bpmnModel, 548 private ActivityNode buildNotRunApproveNodeForSimple(Long startUserId, BpmnModel bpmnModel,
467 - BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables,  
468 - BpmSimpleModelNodeVO node, Set<String> runActivityIds) { 549 + BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables,
  550 + BpmSimpleModelNodeVO node, Set<String> runActivityIds) {
469 // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance 551 // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance
470 // 包括了历史的操作,不是只有 startEvent 到当前节点的记录 552 // 包括了历史的操作,不是只有 startEvent 到当前节点的记录
471 if (runActivityIds.contains(node.getId())) { 553 if (runActivityIds.contains(node.getId())) {
@@ -479,7 +561,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -479,7 +561,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
479 // 1. 开始节点/审批节点 561 // 1. 开始节点/审批节点
480 if (ObjectUtils.equalsAny(node.getType(), 562 if (ObjectUtils.equalsAny(node.getType(),
481 BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType(), 563 BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType(),
482 - BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType())) { 564 + BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType(),
  565 + BpmSimpleModelNodeTypeEnum.TRANSACTOR_NODE.getType())) {
483 List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(), 566 List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(),
484 startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables); 567 startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables);
485 activityNode.setCandidateUserIds(candidateUserIds); 568 activityNode.setCandidateUserIds(candidateUserIds);
@@ -494,14 +577,22 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -494,14 +577,22 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
494 // 3. 抄送节点 577 // 3. 抄送节点
495 if (CollUtil.isEmpty(runActivityIds) && // 流程发起时:需要展示抄送节点,用于选择抄送人 578 if (CollUtil.isEmpty(runActivityIds) && // 流程发起时:需要展示抄送节点,用于选择抄送人
496 BpmSimpleModelNodeTypeEnum.COPY_NODE.getType().equals(node.getType())) { 579 BpmSimpleModelNodeTypeEnum.COPY_NODE.getType().equals(node.getType())) {
  580 + List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(),
  581 + startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables);
  582 + activityNode.setCandidateUserIds(candidateUserIds);
  583 + return activityNode;
  584 + }
  585 +
  586 + // 4. 子流程节点
  587 + if (BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getType().equals(node.getType())) {
497 return activityNode; 588 return activityNode;
498 } 589 }
499 return null; 590 return null;
500 } 591 }
501 592
502 private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, 593 private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel,
503 - BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables,  
504 - FlowElement node, Set<String> runActivityIds) { 594 + BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables,
  595 + FlowElement node, Set<String> runActivityIds) {
505 if (runActivityIds.contains(node.getId())) { 596 if (runActivityIds.contains(node.getId())) {
506 return null; 597 return null;
507 } 598 }
@@ -532,7 +623,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -532,7 +623,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
532 } 623 }
533 624
534 private List<Long> getTaskCandidateUserList(BpmnModel bpmnModel, String activityId, 625 private List<Long> getTaskCandidateUserList(BpmnModel bpmnModel, String activityId,
535 - Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { 626 + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
536 Set<Long> userIds = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId, 627 Set<Long> userIds = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId,
537 startUserId, processDefinitionId, processVariables); 628 startUserId, processDefinitionId, processVariables);
538 return new ArrayList<>(userIds); 629 return new ArrayList<>(userIds);
@@ -568,11 +659,11 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -568,11 +659,11 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
568 Set<String> finishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId, 659 Set<String> finishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId,
569 activityInstance -> activityInstance.getEndTime() != null 660 activityInstance -> activityInstance.getEndTime() != null
570 && ObjectUtil.notEqual(activityInstance.getActivityType(), 661 && ObjectUtil.notEqual(activityInstance.getActivityType(),
571 - BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW)); 662 + BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW));
572 Set<String> finishedSequenceFlowActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId, 663 Set<String> finishedSequenceFlowActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId,
573 activityInstance -> activityInstance.getEndTime() != null 664 activityInstance -> activityInstance.getEndTime() != null
574 && ObjectUtil.equals(activityInstance.getActivityType(), 665 && ObjectUtil.equals(activityInstance.getActivityType(),
575 - BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW)); 666 + BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW));
576 // 特殊:会签情况下,会有部分已完成(审批)、部分未完成(待审批),此时需要 finishedTaskActivityIds 移除掉 667 // 特殊:会签情况下,会有部分已完成(审批)、部分未完成(待审批),此时需要 finishedTaskActivityIds 移除掉
577 finishedTaskActivityIds.removeAll(unfinishedTaskActivityIds); 668 finishedTaskActivityIds.removeAll(unfinishedTaskActivityIds);
578 // 特殊:如果流程实例被拒绝,则需要计算是哪个活动节点。 669 // 特殊:如果流程实例被拒绝,则需要计算是哪个活动节点。
@@ -624,8 +715,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -624,8 +715,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
624 } 715 }
625 716
626 private String createProcessInstance0(Long userId, ProcessDefinition definition, 717 private String createProcessInstance0(Long userId, ProcessDefinition definition,
627 - Map<String, Object> variables, String businessKey,  
628 - Map<String, List<Long>> startUserSelectAssignees) { 718 + Map<String, Object> variables, String businessKey,
  719 + Map<String, List<Long>> startUserSelectAssignees) {
629 // 1.1 校验流程定义 720 // 1.1 校验流程定义
630 if (definition == null) { 721 if (definition == null) {
631 throw exception(PROCESS_DEFINITION_NOT_EXISTS); 722 throw exception(PROCESS_DEFINITION_NOT_EXISTS);
@@ -643,7 +734,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -643,7 +734,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
643 throw exception(PROCESS_INSTANCE_START_USER_CAN_START); 734 throw exception(PROCESS_INSTANCE_START_USER_CAN_START);
644 } 735 }
645 // 1.3 校验发起人自选审批人 736 // 1.3 校验发起人自选审批人
646 - validateStartUserSelectAssignees(definition, startUserSelectAssignees); 737 + validateStartUserSelectAssignees(userId, definition, startUserSelectAssignees, variables);
647 738
648 // 2. 创建流程实例 739 // 2. 创建流程实例
649 if (variables == null) { 740 if (variables == null) {
@@ -653,10 +744,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -653,10 +744,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
653 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, userId); // 设置流程变量,发起人 ID 744 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, userId); // 设置流程变量,发起人 ID
654 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 745 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中
655 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); 746 BpmProcessInstanceStatusEnum.RUNNING.getStatus());
656 - variables.put(BpmnVariableConstants.PROCESS_INSTANCE_SKIP_EXPRESSION_ENABLED, true); // 跳过表达式需要添加此变量为  
657 - // true,不影响没配置  
658 - // skipExpression 的节点 747 + variables.put(BpmnVariableConstants.PROCESS_INSTANCE_SKIP_EXPRESSION_ENABLED, true); // 跳过表达式需要添加此变量为 true,不影响没配置 skipExpression 的节点
659 if (CollUtil.isNotEmpty(startUserSelectAssignees)) { 748 if (CollUtil.isNotEmpty(startUserSelectAssignees)) {
  749 + // 设置流程变量,发起人自选审批人
660 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, 750 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES,
661 startUserSelectAssignees); 751 startUserSelectAssignees);
662 } 752 }
@@ -688,17 +778,23 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -688,17 +778,23 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
688 return instance.getId(); 778 return instance.getId();
689 } 779 }
690 780
691 - private void validateStartUserSelectAssignees(ProcessDefinition definition,  
692 - Map<String, List<Long>> startUserSelectAssignees) {  
693 - // 1. 获得发起人自选审批人的 UserTask/ServiceTask 列表  
694 - BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId());  
695 - List<Task> tasks = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectTaskList(bpmnModel);  
696 - if (CollUtil.isEmpty(tasks)) { 781 + private void validateStartUserSelectAssignees(Long userId, ProcessDefinition definition,
  782 + Map<String, List<Long>> startUserSelectAssignees,
  783 + Map<String, Object> variables) {
  784 + // 1. 获取预测的节点信息
  785 + BpmApprovalDetailRespVO detailRespVO = getApprovalDetail(userId, new BpmApprovalDetailReqVO()
  786 + .setProcessDefinitionId(definition.getId())
  787 + .setProcessVariables(variables));
  788 + List<ActivityNode> activityNodes = detailRespVO.getActivityNodes();
  789 + if (CollUtil.isEmpty(activityNodes)) {
697 return; 790 return;
698 } 791 }
699 792
700 - // 2. 校验发起人自选审批人的审批人和抄送人是否都配置了  
701 - tasks.forEach(task -> { 793 + // 2.1 移除掉不是发起人自选审批人节点
  794 + activityNodes.removeIf(task ->
  795 + ObjectUtil.notEqual(BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy(), task.getCandidateStrategy()));
  796 + // 2.2 流程发起时要先获取当前流程的预测走向节点,发起时只校验预测的节点发起人自选审批人的审批人和抄送人是否都配置了
  797 + activityNodes.forEach(task -> {
702 List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(task.getId()) : null; 798 List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(task.getId()) : null;
703 if (CollUtil.isEmpty(assignees)) { 799 if (CollUtil.isEmpty(assignees)) {
704 throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, task.getName()); 800 throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, task.getName());
@@ -771,6 +867,16 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -771,6 +867,16 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
771 BpmReasonEnum.REJECT_TASK.format(reason)); 867 BpmReasonEnum.REJECT_TASK.format(reason));
772 } 868 }
773 869
  870 + @Override
  871 + public void updateProcessInstanceVariables(String id, Map<String, Object> variables) {
  872 + runtimeService.setVariables(id, variables);
  873 + }
  874 +
  875 + @Override
  876 + public void removeProcessInstanceVariables(String id, Collection<String> variableNames) {
  877 + runtimeService.removeVariables(id, variableNames);
  878 + }
  879 +
774 // ========== Event 事件相关方法 ========== 880 // ========== Event 事件相关方法 ==========
775 881
776 @Override 882 @Override
@@ -802,12 +908,47 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -802,12 +908,47 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
802 // 3. 发送流程实例的状态事件 908 // 3. 发送流程实例的状态事件
803 processInstanceEventPublisher.sendProcessInstanceResultEvent( 909 processInstanceEventPublisher.sendProcessInstanceResultEvent(
804 BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); 910 BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status));
  911 +
  912 + // 4. 流程后置通知
  913 + if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) {
  914 + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.
  915 + getProcessDefinitionInfo(instance.getProcessDefinitionId());
  916 + if (ObjUtil.isNotNull(processDefinitionInfo) &&
  917 + ObjUtil.isNotNull(processDefinitionInfo.getPostProcessNotifySetting())) {
  918 + BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getPostProcessNotifySetting();
  919 +
  920 + BpmHttpRequestUtils.executeBpmHttpRequest(instance,
  921 + setting.getUrl(),
  922 + setting.getHeader(),
  923 + setting.getBody(),
  924 + true, setting.getResponse(),
  925 + restTemplate,
  926 + this);
  927 + }
  928 + }
805 }); 929 });
806 } 930 }
807 931
808 @Override 932 @Override
809 - public void updateProcessInstanceVariables(String id, Map<String, Object> variables) {  
810 - runtimeService.setVariables(id, variables); 933 + public void processProcessInstanceCreated(ProcessInstance instance) {
  934 + // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号
  935 + FlowableUtils.execute(instance.getTenantId(), () -> {
  936 + // 流程前置通知
  937 + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.
  938 + getProcessDefinitionInfo(instance.getProcessDefinitionId());
  939 + // TODO @lesan:if return 哈。减少括号。
  940 + if (ObjUtil.isNotNull(processDefinitionInfo) &&
  941 + ObjUtil.isNotNull(processDefinitionInfo.getPreProcessNotifySetting())) {
  942 + BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getPreProcessNotifySetting();
  943 + BpmHttpRequestUtils.executeBpmHttpRequest(instance,
  944 + setting.getUrl(),
  945 + setting.getHeader(),
  946 + setting.getBody(),
  947 + true, setting.getResponse(),
  948 + restTemplate,
  949 + this);
  950 + }
  951 + });
811 } 952 }
812 953
813 } 954 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
@@ -35,13 +35,16 @@ public interface BpmTaskService { @@ -35,13 +35,16 @@ public interface BpmTaskService {
35 PageResult<Task> getTaskTodoPage(Long userId, BpmTaskPageReqVO pageReqVO); 35 PageResult<Task> getTaskTodoPage(Long userId, BpmTaskPageReqVO pageReqVO);
36 36
37 /** 37 /**
38 - * 获得用户在指定流程下,首个需要处理(待办)的任务 38 + * 获得用户(待办)的任务:
  39 + * 1. 根据 taskId 查询待办任务
  40 + * 2. 如果任务不存在(或者已审核),获取指定流程下,首个需要处理任务
39 * 41 *
40 * @param userId 用户编号 42 * @param userId 用户编号
  43 + * @param taskId 任务编号
41 * @param processInstanceId 流程实例编号 44 * @param processInstanceId 流程实例编号
42 * @return 待办任务 45 * @return 待办任务
43 */ 46 */
44 - BpmTaskRespVO getFirstTodoTask(Long userId, String processInstanceId); 47 + BpmTaskRespVO getTodoTask(Long userId, String taskId, String processInstanceId);
45 48
46 /** 49 /**
47 * 获得已办的流程任务分页 50 * 获得已办的流程任务分页
@@ -90,6 +93,14 @@ public interface BpmTaskService { @@ -90,6 +93,14 @@ public interface BpmTaskService {
90 List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId, Boolean asc); 93 List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId, Boolean asc);
91 94
92 /** 95 /**
  96 + * 校验任务是否存在,并且是否是分配给自己的任务
  97 + *
  98 + * @param userId 用户 id
  99 + * @param taskId task id
  100 + */
  101 + Task validateTask(Long userId, String taskId);
  102 +
  103 + /**
93 * 获取任务 104 * 获取任务
94 * 105 *
95 * @param id 任务编号 106 * @param id 任务编号
@@ -277,11 +288,22 @@ public interface BpmTaskService { @@ -277,11 +288,22 @@ public interface BpmTaskService {
277 void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer handlerType); 288 void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer handlerType);
278 289
279 /** 290 /**
280 - * 处理 延迟器 超时事件 291 + * 处理 ChildProcess 子流程的审批超时事件
  292 + *
  293 + * @param processInstanceId 流程示例编号
  294 + * @param taskDefineKey 任务 Key
  295 + */
  296 + void processChildProcessTimeout(String processInstanceId, String taskDefineKey);
  297 +
  298 + /**
  299 + * 触发流程任务 (ReceiveTask) 的执行
  300 + * <p>
  301 + * 1. Simple 模型 HTTP 回调请求触发器节点的回调,触发流程继续执行
  302 + * 2. Simple 模型延迟器节点,到时触发流程继续执行
281 * 303 *
282 * @param processInstanceId 流程示例编号 304 * @param processInstanceId 流程示例编号
283 * @param taskDefineKey 任务 Key 305 * @param taskDefineKey 任务 Key
284 */ 306 */
285 - void processDelayTimerTimeout(String processInstanceId, String taskDefineKey); 307 + void triggerTask(String processInstanceId, String taskDefineKey);
286 308
287 } 309 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
1 package cn.iocoder.yudao.module.bpm.service.task; 1 package cn.iocoder.yudao.module.bpm.service.task;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.convert.Convert;
4 import cn.hutool.core.lang.Assert; 5 import cn.hutool.core.lang.Assert;
5 import cn.hutool.core.util.*; 6 import cn.hutool.core.util.*;
6 import cn.hutool.extra.spring.SpringUtil; 7 import cn.hutool.extra.spring.SpringUtil;
@@ -19,6 +20,7 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; @@ -19,6 +20,7 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum;
19 import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; 20 import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
20 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; 21 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum;
21 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; 22 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
  23 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
22 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; 24 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
23 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; 25 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
24 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; 26 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
@@ -40,6 +42,7 @@ import org.flowable.engine.ManagementService; @@ -40,6 +42,7 @@ import org.flowable.engine.ManagementService;
40 import org.flowable.engine.RuntimeService; 42 import org.flowable.engine.RuntimeService;
41 import org.flowable.engine.TaskService; 43 import org.flowable.engine.TaskService;
42 import org.flowable.engine.history.HistoricActivityInstance; 44 import org.flowable.engine.history.HistoricActivityInstance;
  45 +import org.flowable.engine.runtime.ActivityInstance;
43 import org.flowable.engine.runtime.Execution; 46 import org.flowable.engine.runtime.Execution;
44 import org.flowable.engine.runtime.ProcessInstance; 47 import org.flowable.engine.runtime.ProcessInstance;
45 import org.flowable.task.api.DelegationState; 48 import org.flowable.task.api.DelegationState;
@@ -61,7 +64,9 @@ import java.util.stream.Stream; @@ -61,7 +64,9 @@ import java.util.stream.Stream;
61 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 64 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
62 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; 65 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
63 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; 66 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
  67 +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID;
64 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG; 68 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG;
  69 +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE;
65 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*; 70 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*;
66 71
67 /** 72 /**
@@ -116,6 +121,9 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -116,6 +121,9 @@ public class BpmTaskServiceImpl implements BpmTaskService {
116 if (StrUtil.isNotEmpty(pageVO.getCategory())) { 121 if (StrUtil.isNotEmpty(pageVO.getCategory())) {
117 taskQuery.taskCategory(pageVO.getCategory()); 122 taskQuery.taskCategory(pageVO.getCategory());
118 } 123 }
  124 + if (StrUtil.isNotEmpty(pageVO.getProcessDefinitionKey())) {
  125 + taskQuery.processDefinitionKey(pageVO.getProcessDefinitionKey());
  126 + }
119 if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) { 127 if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
120 taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0])); 128 taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));
121 taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1])); 129 taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1]));
@@ -129,32 +137,19 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -129,32 +137,19 @@ public class BpmTaskServiceImpl implements BpmTaskService {
129 } 137 }
130 138
131 @Override 139 @Override
132 - public BpmTaskRespVO getFirstTodoTask(Long userId, String processInstanceId) {  
133 - if (processInstanceId == null) {  
134 - return null;  
135 - }  
136 - // 1. 查询所有任务  
137 - List<Task> tasks = taskService.createTaskQuery()  
138 - .active()  
139 - .processInstanceId(processInstanceId)  
140 - .includeTaskLocalVariables()  
141 - .includeProcessVariables()  
142 - .orderByTaskCreateTime().asc() // 按创建时间升序  
143 - .list();  
144 - if (CollUtil.isEmpty(tasks)) {  
145 - return null; 140 + public BpmTaskRespVO getTodoTask(Long userId, String taskId, String processInstanceId) {
  141 + // 1.1 获取指定的用户待办任务
  142 + Task todoTask = getMyTodoTask(userId, taskId);
  143 + // 1.2 获取不到,则获取该流程实例下,第一个用户的待办任务
  144 + if (todoTask == null) {
  145 + todoTask = getMyFirstTodoTask(userId, processInstanceId);
146 } 146 }
147 -  
148 - // 2.1 查询我的首个任务  
149 - Task todoTask = CollUtil.findOne(tasks, task -> {  
150 - return isAssignUserTask(userId, task) // 当前用户为审批人  
151 - || isAddSignUserTask(userId, task); // 当前用户为加签人(为了减签)  
152 - });  
153 if (todoTask == null) { 147 if (todoTask == null) {
154 return null; 148 return null;
155 } 149 }
156 - // 2.2 查询该任务的子任务  
157 - List<Task> childrenTasks = getAllChildrenTaskListByParentTaskId(todoTask.getId(), tasks); 150 +
  151 + // 2. 查询该任务的子任务
  152 + List<Task> childrenTasks = getAllChildrenTaskListByParentTaskId(todoTask.getId(), CollUtil.newArrayList(todoTask));
158 153
159 // 3. 转换返回 154 // 3. 转换返回
160 BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(todoTask.getProcessDefinitionId()); 155 BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(todoTask.getProcessDefinitionId());
@@ -162,6 +157,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -162,6 +157,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
162 bpmnModel, todoTask.getTaskDefinitionKey()); 157 bpmnModel, todoTask.getTaskDefinitionKey());
163 Boolean signEnable = parseSignEnable(bpmnModel, todoTask.getTaskDefinitionKey()); 158 Boolean signEnable = parseSignEnable(bpmnModel, todoTask.getTaskDefinitionKey());
164 Boolean reasonRequire = parseReasonRequire(bpmnModel, todoTask.getTaskDefinitionKey()); 159 Boolean reasonRequire = parseReasonRequire(bpmnModel, todoTask.getTaskDefinitionKey());
  160 + Integer nodeType = parseNodeType(BpmnModelUtils.getFlowElementById(bpmnModel, todoTask.getTaskDefinitionKey()));
165 161
166 // 4. 任务表单 162 // 4. 任务表单
167 BpmFormDO taskForm = null; 163 BpmFormDO taskForm = null;
@@ -170,8 +166,55 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -170,8 +166,55 @@ public class BpmTaskServiceImpl implements BpmTaskService {
170 } 166 }
171 167
172 return BpmTaskConvert.INSTANCE.buildTodoTask(todoTask, childrenTasks, buttonsSetting, taskForm) 168 return BpmTaskConvert.INSTANCE.buildTodoTask(todoTask, childrenTasks, buttonsSetting, taskForm)
173 - .setSignEnable(signEnable)  
174 - .setReasonRequire(reasonRequire); 169 + .setNodeType(nodeType).setSignEnable(signEnable).setReasonRequire(reasonRequire);
  170 + }
  171 +
  172 + /**
  173 + * 获得用户指定 taskId 任务编号的“待办”(未审批、且可审核)的任务
  174 + *
  175 + * @param userId 用户编号
  176 + * @param taskId 任务编号
  177 + * @return 任务
  178 + */
  179 + private Task getMyTodoTask(Long userId, String taskId) {
  180 + if (StrUtil.isEmpty(taskId)) {
  181 + return null;
  182 + }
  183 + Task task = getTask(taskId);
  184 + if (task == null) {
  185 + return null;
  186 + }
  187 + if (!isAssignUserTask(userId, task) && !isAddSignUserTask(userId, task)) {
  188 + return null;
  189 + }
  190 + return task;
  191 + }
  192 +
  193 + /**
  194 + * 获得用户指定 processInstanceId 流程编号下的首个“待办”(未审批、且可审核)的任务
  195 + *
  196 + * @param userId 用户编号
  197 + * @param processInstanceId 流程编号
  198 + * @return 任务
  199 + */
  200 + private Task getMyFirstTodoTask(Long userId, String processInstanceId) {
  201 + if (processInstanceId == null) {
  202 + return null;
  203 + }
  204 + // 1. 查询所有任务
  205 + List<Task> tasks = taskService.createTaskQuery()
  206 + .active()
  207 + .processInstanceId(processInstanceId)
  208 + .includeTaskLocalVariables()
  209 + .includeProcessVariables()
  210 + .orderByTaskCreateTime().asc() // 按创建时间升序
  211 + .list();
  212 +
  213 + // 2. 查询我的首个任务
  214 + return CollUtil.findOne(tasks, task -> {
  215 + return isAssignUserTask(userId, task) // 当前用户为审批人
  216 + || isAddSignUserTask(userId, task); // 当前用户为加签人(为了减签)
  217 + });
175 } 218 }
176 219
177 @Override 220 @Override
@@ -194,6 +237,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -194,6 +237,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
194 return PageResult.empty(); 237 return PageResult.empty();
195 } 238 }
196 List<HistoricTaskInstance> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize()); 239 List<HistoricTaskInstance> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
  240 +
  241 + // 特殊:强制移除自动完成的“发起人”节点
  242 + // 补充说明:由于 taskQuery 无法方面的过滤,所以暂时通过内存过滤
  243 + tasks.removeIf(task -> task.getTaskDefinitionKey().equals(START_USER_NODE_ID));
197 return new PageResult<>(tasks, count); 244 return new PageResult<>(tasks, count);
198 } 245 }
199 246
@@ -243,13 +290,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -243,13 +290,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
243 return query.list(); 290 return query.list();
244 } 291 }
245 292
246 - /**  
247 - * 校验任务是否存在,并且是否是分配给自己的任务  
248 - *  
249 - * @param userId 用户 id  
250 - * @param taskId task id  
251 - */  
252 - private Task validateTask(Long userId, String taskId) { 293 + @Override
  294 + public Task validateTask(Long userId, String taskId) {
253 Task task = validateTaskExist(taskId); 295 Task task = validateTaskExist(taskId);
254 // 为什么判断 assignee 非空的情况下? 296 // 为什么判断 assignee 非空的情况下?
255 // 例如说:在审批人为空时,我们会有“自动审批通过”的策略,此时 userId 为 null,允许通过 297 // 例如说:在审批人为空时,我们会有“自动审批通过”的策略,此时 userId 为 null,允许通过
@@ -515,21 +557,87 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -515,21 +557,87 @@ public class BpmTaskServiceImpl implements BpmTaskService {
515 // 2.2 添加评论 557 // 2.2 添加评论
516 taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.APPROVE.getType(), 558 taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.APPROVE.getType(),
517 BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason())); 559 BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason()));
518 - // 2.3 调用 BPM complete 去完成任务  
519 - // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用  
520 - if (CollUtil.isNotEmpty(reqVO.getVariables())) {  
521 - Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables());  
522 - // 修改表单的值需要存储到 ProcessInstance 变量  
523 - runtimeService.setVariables(task.getProcessInstanceId(), variables);  
524 - taskService.complete(task.getId(), variables, true);  
525 - } else {  
526 - taskService.complete(task.getId());  
527 - } 560 + // 2.3 校验并处理 APPROVE_USER_SELECT 当前审批人,选择下一节点审批人的逻辑
  561 + Map<String, Object> variables = validateAndSetNextAssignees(task.getTaskDefinitionKey(), reqVO.getVariables(),
  562 + bpmnModel, reqVO.getNextAssignees(), instance);
  563 + runtimeService.setVariables(task.getProcessInstanceId(), variables);
  564 + // 2.4 调用 BPM complete 去完成任务
  565 + taskService.complete(task.getId(), variables, true);
528 566
529 // 【加签专属】处理加签任务 567 // 【加签专属】处理加签任务
530 handleParentTaskIfSign(task.getParentTaskId()); 568 handleParentTaskIfSign(task.getParentTaskId());
531 } 569 }
532 570
  571 +
  572 + /**
  573 + * 校验选择的下一个节点的审批人,是否合法
  574 + *
  575 + * 1. 是否有漏选:没有选择审批人
  576 + * 2. 是否有多选:非下一个节点
  577 + *
  578 + * @param taskDefinitionKey 当前任务节点标识
  579 + * @param variables 流程变量
  580 + * @param bpmnModel 流程模型
  581 + * @param nextAssignees 下一个节点审批人集合(参数)
  582 + * @param processInstance 流程实例
  583 + */
  584 + private Map<String, Object> validateAndSetNextAssignees(String taskDefinitionKey, Map<String, Object> variables, BpmnModel bpmnModel,
  585 + Map<String, List<Long>> nextAssignees, ProcessInstance processInstance) {
  586 + // 1. 获取下一个将要执行的节点集合
  587 + FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey);
  588 + List<FlowNode> nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables);
  589 +
  590 + // 2. 校验选择的下一个节点的审批人,是否合法
  591 + Map<String, List<Long>> processVariables;
  592 + for (FlowNode nextFlowNode : nextFlowNodes) {
  593 + Integer candidateStrategy = parseCandidateStrategy(nextFlowNode);
  594 + // 2.1 情况一:如果节点中的审批人策略为 发起人自选
  595 + if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())) {
  596 + // 如果节点存在,但未配置审批人
  597 + List<Long> assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null;
  598 + if (CollUtil.isEmpty(assignees)) {
  599 + throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());
  600 + }
  601 + processVariables = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables());
  602 + // 特殊:如果当前节点已经存在审批人,则不允许覆盖
  603 + // TODO @小北:【不用改】通过 if return,让逻辑更简洁一点;虽然会多判断一次 processVariables,但是 if else 层级更少。
  604 + if (processVariables != null
  605 + && CollUtil.isNotEmpty(processVariables.get(nextFlowNode.getId()))) {
  606 + continue;
  607 + }
  608 + // 设置 PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES
  609 + if (processVariables == null) {
  610 + processVariables = new HashMap<>();
  611 + }
  612 + processVariables.put(nextFlowNode.getId(), assignees);
  613 + variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, processVariables);
  614 + }
  615 + // 2.2 情况二:如果节点中的审批人策略为 审批人,在审批时选择下一个节点的审批人,并且该节点的审批人为空
  616 + if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) {
  617 + // 如果节点存在,但未配置审批人
  618 + List<Long> assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null;
  619 + if (CollUtil.isEmpty(assignees)) {
  620 + throw exception(PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());
  621 + }
  622 + processVariables = FlowableUtils.getApproveUserSelectAssignees(processInstance.getProcessVariables());
  623 + if (processVariables == null) {
  624 + processVariables = new HashMap<>();
  625 + } else {
  626 + List<Long> approveUserSelectAssignee = processVariables.get(nextFlowNode.getId());
  627 + // 特殊:如果当前节点已经存在审批人,则不允许覆盖
  628 + // TODO @小北:这种,应该可以覆盖呢。
  629 + if (CollUtil.isNotEmpty(approveUserSelectAssignee)) {
  630 + continue;
  631 + }
  632 + }
  633 + // 设置 PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES
  634 + processVariables.put(nextFlowNode.getId(), assignees);
  635 + variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES, processVariables);
  636 + }
  637 + }
  638 + return variables;
  639 + }
  640 +
533 /** 641 /**
534 * 审批通过存在“后加签”的任务。 642 * 审批通过存在“后加签”的任务。
535 * <p> 643 * <p>
@@ -991,6 +1099,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -991,6 +1099,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
991 1099
992 @Override 1100 @Override
993 @Transactional(rollbackFor = Exception.class) 1101 @Transactional(rollbackFor = Exception.class)
  1102 + @SuppressWarnings("DataFlowIssue")
994 public void deleteSignTask(Long userId, BpmTaskSignDeleteReqVO reqVO) { 1103 public void deleteSignTask(Long userId, BpmTaskSignDeleteReqVO reqVO) {
995 // 1.1 校验 task 可以被减签 1104 // 1.1 校验 task 可以被减签
996 Task task = validateTaskCanSignDelete(reqVO.getId()); 1105 Task task = validateTaskCanSignDelete(reqVO.getId());
@@ -1207,19 +1316,31 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -1207,19 +1316,31 @@ public class BpmTaskServiceImpl implements BpmTaskService {
1207 } 1316 }
1208 } 1317 }
1209 1318
1210 - // 审批人与提交人为同一人时,根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理  
1211 - if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) {  
1212 - // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略  
1213 - // TODO 芋艿:【优化】未来有没更好的判断方式?!另外,还要考虑清理机制。就是说,下次处理了之后,就移除这个标识  
1214 - Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(),  
1215 - String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class); 1319 + // 获取发起人节点
  1320 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId());
  1321 + if (bpmnModel == null) {
  1322 + log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId());
  1323 + return;
  1324 + }
  1325 + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
  1326 + // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略
  1327 + // TODO 芋艿:【优化】未来有没更好的判断方式?!另外,还要考虑清理机制。就是说,下次处理了之后,就移除这个标识
  1328 + Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(),
  1329 + String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class);
  1330 + Boolean skipStartUserNodeFlag = Convert.toBool(runtimeService.getVariable(processInstance.getProcessInstanceId(),
  1331 + PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class));
  1332 + if (userTaskElement.getId().equals(START_USER_NODE_ID)
  1333 + && (skipStartUserNodeFlag == null // 目的:一般是“主流程”,发起人节点,自动通过审核
  1334 + || Boolean.TRUE.equals(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核
  1335 + && ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) {
  1336 + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
  1337 + .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE.getReason()));
  1338 + return;
  1339 + }
  1340 + // 当不为发起人节点时,审批人与提交人为同一人时,根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理
  1341 + if (ObjectUtil.notEqual(userTaskElement.getId(), START_USER_NODE_ID)
  1342 + && StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) {
1216 if (ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) { 1343 if (ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) {
1217 - BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId());  
1218 - if (bpmnModel == null) {  
1219 - log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId());  
1220 - return;  
1221 - }  
1222 - FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());  
1223 Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement); 1344 Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement);
1224 1345
1225 // 情况一:自动跳过 1346 // 情况一:自动跳过
@@ -1304,14 +1425,23 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -1304,14 +1425,23 @@ public class BpmTaskServiceImpl implements BpmTaskService {
1304 } 1425 }
1305 1426
1306 @Override 1427 @Override
1307 - public void processDelayTimerTimeout(String processInstanceId, String taskDefineKey) { 1428 + @Transactional(rollbackFor = Exception.class)
  1429 + public void processChildProcessTimeout(String processInstanceId, String taskDefineKey) {
  1430 + List<ActivityInstance> activityInstances = runtimeService.createActivityInstanceQuery()
  1431 + .processInstanceId(processInstanceId)
  1432 + .activityId(taskDefineKey).list();
  1433 + activityInstances.forEach(activityInstance -> FlowableUtils.execute(activityInstance.getTenantId(),
  1434 + () -> moveTaskToEnd(activityInstance.getCalledProcessInstanceId(), BpmReasonEnum.TIMEOUT_APPROVE.getReason())));
  1435 + }
  1436 +
  1437 + @Override
  1438 + public void triggerTask(String processInstanceId, String taskDefineKey) {
1308 Execution execution = runtimeService.createExecutionQuery() 1439 Execution execution = runtimeService.createExecutionQuery()
1309 .processInstanceId(processInstanceId) 1440 .processInstanceId(processInstanceId)
1310 .activityId(taskDefineKey) 1441 .activityId(taskDefineKey)
1311 .singleResult(); 1442 .singleResult();
1312 if (execution == null) { 1443 if (execution == null) {
1313 - log.error("[processDelayTimerTimeout][processInstanceId({}) activityId({}) 没有找到执行活动]",  
1314 - processInstanceId, taskDefineKey); 1444 + log.error("[triggerTask][processInstanceId({}) activityId({}) 没有找到执行活动]", processInstanceId, taskDefineKey);
1315 return; 1445 return;
1316 } 1446 }
1317 1447
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmCallActivityListener.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.service.task.listener;
  2 +
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.map.MapUtil;
  5 +import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  7 +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
  8 +import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
  9 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmChildProcessStartUserEmptyTypeEnum;
  10 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmChildProcessStartUserTypeEnum;
  11 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
  12 +import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
  13 +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
  14 +import jakarta.annotation.Resource;
  15 +import lombok.Setter;
  16 +import lombok.extern.slf4j.Slf4j;
  17 +import org.flowable.engine.delegate.DelegateExecution;
  18 +import org.flowable.engine.delegate.ExecutionListener;
  19 +import org.flowable.engine.impl.el.FixedValue;
  20 +import org.flowable.engine.runtime.ProcessInstance;
  21 +import org.springframework.stereotype.Component;
  22 +
  23 +import java.util.List;
  24 +
  25 +/**
  26 + * BPM 子流程监听器:设置流程的发起人
  27 + *
  28 + * @author Lesan
  29 + */
  30 +@Component
  31 +@Slf4j
  32 +public class BpmCallActivityListener implements ExecutionListener {
  33 +
  34 + public static final String DELEGATE_EXPRESSION = "${bpmCallActivityListener}";
  35 +
  36 + @Setter
  37 + private FixedValue listenerConfig;
  38 +
  39 + @Resource
  40 + private BpmProcessDefinitionService processDefinitionService;
  41 +
  42 + @Resource
  43 + private BpmProcessInstanceService processInstanceService;
  44 +
  45 + @Override
  46 + public void notify(DelegateExecution execution) {
  47 + String expressionText = listenerConfig.getExpressionText();
  48 + Assert.notNull(expressionText, "监听器扩展字段({})不能为空", expressionText);
  49 + BpmSimpleModelNodeVO.ChildProcessSetting.StartUserSetting startUserSetting = JsonUtils.parseObject(
  50 + expressionText, BpmSimpleModelNodeVO.ChildProcessSetting.StartUserSetting.class);
  51 + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getRootProcessInstanceId());
  52 +
  53 + // 1. 当发起人来源为主流程发起人时,并兜底 startUserSetting 为空时
  54 + if (startUserSetting == null
  55 + || startUserSetting.getType().equals(BpmChildProcessStartUserTypeEnum.MAIN_PROCESS_START_USER.getType())) {
  56 + FlowableUtils.setAuthenticatedUserId(Long.parseLong(processInstance.getStartUserId()));
  57 + return;
  58 + }
  59 +
  60 + // 2. 当发起人来源为表单时
  61 + if (startUserSetting.getType().equals(BpmChildProcessStartUserTypeEnum.FROM_FORM.getType())) {
  62 + String formFieldValue = MapUtil.getStr(processInstance.getProcessVariables(), startUserSetting.getFormField());
  63 + // 2.1 当表单值为空时
  64 + if (StrUtil.isEmpty(formFieldValue)) {
  65 + // 2.1.1 来自主流程发起人
  66 + if (startUserSetting.getEmptyType().equals(BpmChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_START_USER.getType())) {
  67 + FlowableUtils.setAuthenticatedUserId(Long.parseLong(processInstance.getStartUserId()));
  68 + return;
  69 + }
  70 + // 2.1.2 来自子流程管理员
  71 + if (startUserSetting.getEmptyType().equals(BpmChildProcessStartUserEmptyTypeEnum.CHILD_PROCESS_ADMIN.getType())) {
  72 + BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(execution.getProcessDefinitionId());
  73 + List<Long> managerUserIds = processDefinition.getManagerUserIds();
  74 + FlowableUtils.setAuthenticatedUserId(managerUserIds.get(0));
  75 + return;
  76 + }
  77 + // 2.1.3 来自主流程管理员
  78 + if (startUserSetting.getEmptyType().equals(BpmChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_ADMIN.getType())) {
  79 + BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(processInstance.getProcessDefinitionId());
  80 + List<Long> managerUserIds = processDefinition.getManagerUserIds();
  81 + FlowableUtils.setAuthenticatedUserId(managerUserIds.get(0));
  82 + return;
  83 + }
  84 + }
  85 + // 2.2 使用表单值,并兜底字符串转 Long 失败时使用主流程发起人
  86 + try {
  87 + FlowableUtils.setAuthenticatedUserId(Long.parseLong(formFieldValue));
  88 + } catch (Exception e) {
  89 + log.error("[notify][监听器:{},子流程监听器设置流程的发起人字符串转 Long 失败,字符串:{}]",
  90 + DELEGATE_EXPRESSION, formFieldValue);
  91 + FlowableUtils.setAuthenticatedUserId(Long.parseLong(processInstance.getStartUserId()));
  92 + }
  93 + }
  94 + }
  95 +
  96 +}
0 \ No newline at end of file 97 \ No newline at end of file
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmUserTaskListener.java
1 package cn.iocoder.yudao.module.bpm.service.task.listener; 1 package cn.iocoder.yudao.module.bpm.service.task.listener;
2 2
3 -import cn.hutool.core.util.StrUtil;  
4 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; 3 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
5 -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; 4 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmHttpRequestParamTypeEnum;
  5 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils;
6 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; 6 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
7 import jakarta.annotation.Resource; 7 import jakarta.annotation.Resource;
8 import lombok.Setter; 8 import lombok.Setter;
9 import lombok.extern.slf4j.Slf4j; 9 import lombok.extern.slf4j.Slf4j;
10 import org.flowable.engine.delegate.TaskListener; 10 import org.flowable.engine.delegate.TaskListener;
11 -import org.flowable.engine.history.HistoricProcessInstance;  
12 import org.flowable.engine.impl.el.FixedValue; 11 import org.flowable.engine.impl.el.FixedValue;
  12 +import org.flowable.engine.runtime.ProcessInstance;
13 import org.flowable.task.service.delegate.DelegateTask; 13 import org.flowable.task.service.delegate.DelegateTask;
14 import org.springframework.context.annotation.Scope; 14 import org.springframework.context.annotation.Scope;
15 -import org.springframework.http.HttpEntity;  
16 -import org.springframework.http.HttpMethod;  
17 -import org.springframework.http.ResponseEntity;  
18 import org.springframework.stereotype.Component; 15 import org.springframework.stereotype.Component;
19 -import org.springframework.util.LinkedMultiValueMap;  
20 -import org.springframework.util.MultiValueMap;  
21 -import org.springframework.web.client.RestClientException;  
22 import org.springframework.web.client.RestTemplate; 16 import org.springframework.web.client.RestTemplate;
23 17
24 -import java.util.Map;  
25 -  
26 -import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;  
27 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.parseListenerConfig; 18 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.parseListenerConfig;
28 19
29 // TODO @芋艿:可能会想换个包地址 20 // TODO @芋艿:可能会想换个包地址
@@ -51,46 +42,27 @@ public class BpmUserTaskListener implements TaskListener { @@ -51,46 +42,27 @@ public class BpmUserTaskListener implements TaskListener {
51 @Override 42 @Override
52 public void notify(DelegateTask delegateTask) { 43 public void notify(DelegateTask delegateTask) {
53 // 1. 获取所需基础信息 44 // 1. 获取所需基础信息
54 - HistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(delegateTask.getProcessInstanceId()); 45 + ProcessInstance processInstance = processInstanceService.getProcessInstance(delegateTask.getProcessInstanceId());
55 BpmSimpleModelNodeVO.ListenerHandler listenerHandler = parseListenerConfig(listenerConfig); 46 BpmSimpleModelNodeVO.ListenerHandler listenerHandler = parseListenerConfig(listenerConfig);
56 47
57 - // 2. 获取请求头和请求体  
58 - Map<String, Object> processVariables = processInstance.getProcessVariables();  
59 - MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();  
60 - MultiValueMap<String, String> body = new LinkedMultiValueMap<>();  
61 - SimpleModelUtils.addHttpRequestParam(headers, listenerHandler.getHeader(), processVariables);  
62 - SimpleModelUtils.addHttpRequestParam(body, listenerHandler.getBody(), processVariables);  
63 - // 2.1 请求头默认参数  
64 - if (StrUtil.isNotEmpty(delegateTask.getTenantId())) {  
65 - headers.add(HEADER_TENANT_ID, delegateTask.getTenantId());  
66 - }  
67 - // 2.2 请求体默认参数 48 + // 2. 发起请求
68 // TODO @芋艿:哪些默认参数,后续再调研下;感觉可以搞个 task 字段,把整个 delegateTask 放进去; 49 // TODO @芋艿:哪些默认参数,后续再调研下;感觉可以搞个 task 字段,把整个 delegateTask 放进去;
69 - body.add("processInstanceId", delegateTask.getProcessInstanceId());  
70 - body.add("assignee", delegateTask.getAssignee());  
71 - body.add("taskDefinitionKey", delegateTask.getTaskDefinitionKey());  
72 - body.add("taskId", delegateTask.getId()); 50 + listenerHandler.getBody().add(new BpmSimpleModelNodeVO.HttpRequestParam().setKey("processInstanceId")
  51 + .setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType()).setValue(delegateTask.getProcessInstanceId()));
  52 + listenerHandler.getBody().add(new BpmSimpleModelNodeVO.HttpRequestParam().setKey("assignee")
  53 + .setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType()).setValue(delegateTask.getAssignee()));
  54 + listenerHandler.getBody().add(new BpmSimpleModelNodeVO.HttpRequestParam().setKey("taskDefinitionKey")
  55 + .setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType()).setValue(delegateTask.getTaskDefinitionKey()));
  56 + listenerHandler.getBody().add(new BpmSimpleModelNodeVO.HttpRequestParam().setKey("taskId")
  57 + .setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType()).setValue(delegateTask.getId()));
  58 + BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
  59 + listenerHandler.getPath(),
  60 + listenerHandler.getHeader(),
  61 + listenerHandler.getBody(),
  62 + false, null,
  63 + restTemplate,
  64 + processInstanceService);
73 65
74 - // 3. 异步发起请求  
75 - // TODO @芋艿:确认要同步,还是异步  
76 - HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(body, headers);  
77 - try {  
78 - ResponseEntity<String> responseEntity = restTemplate.exchange(listenerHandler.getPath(), HttpMethod.POST,  
79 - requestEntity, String.class);  
80 - log.info("[notify][监听器:{},事件类型:{},请求头:{},请求体:{},响应结果:{}]",  
81 - DELEGATE_EXPRESSION,  
82 - delegateTask.getEventName(),  
83 - headers,  
84 - body,  
85 - responseEntity);  
86 - } catch (RestClientException e) {  
87 - log.error("[error][监听器:{},事件类型:{},请求头:{},请求体:{},请求出错:{}]",  
88 - DELEGATE_EXPRESSION,  
89 - delegateTask.getEventName(),  
90 - headers,  
91 - body,  
92 - e.getMessage());  
93 - }  
94 - // 4. 是否需要后续操作?TODO 芋艿:待定! 66 + // 3. 是否需要后续操作?TODO 芋艿:待定!
95 } 67 }
96 } 68 }
97 \ No newline at end of file 69 \ No newline at end of file
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/BpmHttpRequestTrigger.java deleted
1 -package cn.iocoder.yudao.module.bpm.service.task.trigger;  
2 -  
3 -import cn.hutool.core.collection.CollUtil;  
4 -import cn.hutool.core.util.StrUtil;  
5 -import cn.iocoder.yudao.framework.common.core.KeyValue;  
6 -import cn.iocoder.yudao.framework.common.pojo.CommonResult;  
7 -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;  
8 -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TriggerSetting.HttpRequestTriggerSetting;  
9 -import cn.iocoder.yudao.module.bpm.enums.definition.BpmTriggerTypeEnum;  
10 -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils;  
11 -import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;  
12 -import com.fasterxml.jackson.core.type.TypeReference;  
13 -import jakarta.annotation.Resource;  
14 -import lombok.extern.slf4j.Slf4j;  
15 -import org.flowable.engine.runtime.ProcessInstance;  
16 -import org.springframework.http.HttpEntity;  
17 -import org.springframework.http.HttpMethod;  
18 -import org.springframework.http.ResponseEntity;  
19 -import org.springframework.stereotype.Component;  
20 -import org.springframework.util.LinkedMultiValueMap;  
21 -import org.springframework.util.MultiValueMap;  
22 -import org.springframework.web.client.RestClientException;  
23 -import org.springframework.web.client.RestTemplate;  
24 -  
25 -import java.util.HashMap;  
26 -import java.util.List;  
27 -import java.util.Map;  
28 -  
29 -import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;  
30 -  
31 -/**  
32 - * BPM 发送 HTTP 请求触发器  
33 - *  
34 - * @author jason  
35 - */  
36 -@Component  
37 -@Slf4j  
38 -public class BpmHttpRequestTrigger implements BpmTrigger {  
39 -  
40 - @Resource  
41 - private BpmProcessInstanceService processInstanceService;  
42 -  
43 - @Resource  
44 - private RestTemplate restTemplate;  
45 -  
46 - @Override  
47 - public BpmTriggerTypeEnum getType() {  
48 - return BpmTriggerTypeEnum.HTTP_REQUEST;  
49 - }  
50 -  
51 - @Override  
52 - public void execute(String processInstanceId, String param) {  
53 - // 1. 解析 http 请求配置  
54 - HttpRequestTriggerSetting setting = JsonUtils.parseObject(param, HttpRequestTriggerSetting.class);  
55 - if (setting == null) {  
56 - log.error("[execute][流程({}) HTTP 触发器请求配置为空]", processInstanceId);  
57 - return;  
58 - }  
59 - // 2.1 设置请求头  
60 - ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);  
61 - Map<String, Object> processVariables = processInstance.getProcessVariables();  
62 - MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();  
63 - headers.add(HEADER_TENANT_ID, processInstance.getTenantId());  
64 - SimpleModelUtils.addHttpRequestParam(headers, setting.getHeader(), processVariables);  
65 - // 2.2 设置请求体  
66 - MultiValueMap<String, String> body = new LinkedMultiValueMap<>();  
67 - SimpleModelUtils.addHttpRequestParam(body, setting.getBody(), processVariables);  
68 - body.add("processInstanceId", processInstanceId);  
69 -  
70 - // TODO @芋艿:要不要抽象一个 Http 请求的工具类,方便复用呢?  
71 - // 3. 发起请求  
72 - HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(body, headers);  
73 - ResponseEntity<String> responseEntity;  
74 - try {  
75 - responseEntity = restTemplate.exchange(setting.getUrl(), HttpMethod.POST,  
76 - requestEntity, String.class);  
77 - log.info("[execute][HTTP 触发器,请求头:{},请求体:{},响应结果:{}]", headers, body, responseEntity);  
78 - } catch (RestClientException e) {  
79 - log.error("[execute][HTTP 触发器,请求头:{},请求体:{},请求出错:{}]", headers, body, e.getMessage());  
80 - return;  
81 - }  
82 -  
83 - // 4.1 判断是否需要解析返回值  
84 - if (StrUtil.isEmpty(responseEntity.getBody())  
85 - || !responseEntity.getStatusCode().is2xxSuccessful()  
86 - || CollUtil.isEmpty(setting.getResponse())) {  
87 - return;  
88 - }  
89 - // 4.2 解析返回值, 返回值必须符合 CommonResult 规范。  
90 - CommonResult<Map<String, Object>> respResult = JsonUtils.parseObjectQuietly(  
91 - responseEntity.getBody(), new TypeReference<>() {});  
92 - if (respResult == null || !respResult.isSuccess()){  
93 - return;  
94 - }  
95 - // 4.3 获取需要更新的流程变量  
96 - Map<String, Object> updateVariables = getNeedUpdatedVariablesFromResponse(respResult.getData(), setting.getResponse());  
97 - // 4.4 更新流程变量  
98 - if (CollUtil.isNotEmpty(updateVariables)) {  
99 - processInstanceService.updateProcessInstanceVariables(processInstanceId, updateVariables);  
100 - }  
101 - }  
102 -  
103 - /**  
104 - * 从请求返回值获取需要更新的流程变量  
105 - *  
106 - * @param result 请求返回结果  
107 - * @param responseSettings 返回设置  
108 - * @return 需要更新的流程变量  
109 - */  
110 - private Map<String, Object> getNeedUpdatedVariablesFromResponse(Map<String,Object> result,  
111 - List<KeyValue<String, String>> responseSettings) {  
112 - Map<String, Object> updateVariables = new HashMap<>();  
113 - if (CollUtil.isEmpty(result)) {  
114 - return updateVariables;  
115 - }  
116 - responseSettings.forEach(responseSetting -> {  
117 - if (StrUtil.isNotEmpty(responseSetting.getKey()) && result.containsKey(responseSetting.getValue())) {  
118 - updateVariables.put(responseSetting.getKey(), result.get(responseSetting.getValue()));  
119 - }  
120 - });  
121 - return updateVariables;  
122 - }  
123 -  
124 -}  
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/form/BpmFormDeleteTrigger.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.service.task.trigger.form;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  5 +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
  6 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmTriggerTypeEnum;
  7 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
  8 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils;
  9 +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
  10 +import cn.iocoder.yudao.module.bpm.service.task.trigger.BpmTrigger;
  11 +import com.fasterxml.jackson.core.type.TypeReference;
  12 +import jakarta.annotation.Resource;
  13 +import lombok.extern.slf4j.Slf4j;
  14 +import org.springframework.stereotype.Component;
  15 +
  16 +import java.util.HashSet;
  17 +import java.util.List;
  18 +import java.util.Map;
  19 +import java.util.Set;
  20 +
  21 +/**
  22 + * BPM 删除流程表单数据触发器
  23 + *
  24 + * @author jason
  25 + */
  26 +@Component
  27 +@Slf4j
  28 +public class BpmFormDeleteTrigger implements BpmTrigger {
  29 +
  30 + @Resource
  31 + private BpmProcessInstanceService processInstanceService;
  32 +
  33 + @Override
  34 + public BpmTriggerTypeEnum getType() {
  35 + return BpmTriggerTypeEnum.FORM_DELETE;
  36 + }
  37 +
  38 + @Override
  39 + public void execute(String processInstanceId, String param) {
  40 + // 1. 解析删除流程表单数据配置
  41 + List<BpmSimpleModelNodeVO.TriggerSetting.FormTriggerSetting> settings = JsonUtils.parseObject(param, new TypeReference<>() {});
  42 + if (CollUtil.isEmpty(settings)) {
  43 + log.error("[execute][流程({}) 删除流程表单数据触发器配置为空]", processInstanceId);
  44 + return;
  45 + }
  46 +
  47 + // 2. 获取流程变量
  48 + Map<String, Object> processVariables = processInstanceService.getProcessInstance(processInstanceId).getProcessVariables();
  49 +
  50 + // 3.1 获取需要删除的表单字段
  51 + Set<String> deleteFields = new HashSet<>();
  52 + settings.forEach(setting -> {
  53 + if (CollUtil.isEmpty(setting.getDeleteFields())) {
  54 + return;
  55 + }
  56 + // 配置了条件,判断条件是否满足
  57 + boolean isFieldDeletedNeeded = true;
  58 + if (setting.getConditionType() != null) {
  59 + String conditionExpression = SimpleModelUtils.buildConditionExpression(
  60 + setting.getConditionType(), setting.getConditionExpression(), setting.getConditionGroups());
  61 + isFieldDeletedNeeded = BpmnModelUtils.evalConditionExpress(processVariables, conditionExpression);
  62 + }
  63 + if (isFieldDeletedNeeded) {
  64 + deleteFields.addAll(setting.getDeleteFields());
  65 + }
  66 + });
  67 +
  68 + // 3.2 删除流程变量
  69 + if (CollUtil.isNotEmpty(deleteFields)) {
  70 + processInstanceService.removeProcessInstanceVariables(processInstanceId, deleteFields);
  71 + }
  72 + }
  73 +}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/BpmUpdateNormalFormTrigger.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/form/BpmFormUpdateTrigger.java
1 -package cn.iocoder.yudao.module.bpm.service.task.trigger; 1 +package cn.iocoder.yudao.module.bpm.service.task.trigger.form;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 4 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
5 -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TriggerSetting.NormalFormTriggerSetting; 5 +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TriggerSetting.FormTriggerSetting;
6 import cn.iocoder.yudao.module.bpm.enums.definition.BpmTriggerTypeEnum; 6 import cn.iocoder.yudao.module.bpm.enums.definition.BpmTriggerTypeEnum;
  7 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
  8 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils;
7 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; 9 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
  10 +import cn.iocoder.yudao.module.bpm.service.task.trigger.BpmTrigger;
  11 +import com.fasterxml.jackson.core.type.TypeReference;
8 import jakarta.annotation.Resource; 12 import jakarta.annotation.Resource;
9 import lombok.extern.slf4j.Slf4j; 13 import lombok.extern.slf4j.Slf4j;
10 import org.springframework.stereotype.Component; 14 import org.springframework.stereotype.Component;
11 15
12 -// TODO @jason:改成 BpmFormUpdateTrigger 16 +import java.util.List;
  17 +import java.util.Map;
  18 +
13 /** 19 /**
14 * BPM 更新流程表单触发器 20 * BPM 更新流程表单触发器
15 * 21 *
@@ -17,28 +23,44 @@ import org.springframework.stereotype.Component; @@ -17,28 +23,44 @@ import org.springframework.stereotype.Component;
17 */ 23 */
18 @Component 24 @Component
19 @Slf4j 25 @Slf4j
20 -public class BpmUpdateNormalFormTrigger implements BpmTrigger { 26 +public class BpmFormUpdateTrigger implements BpmTrigger {
21 27
22 @Resource 28 @Resource
23 private BpmProcessInstanceService processInstanceService; 29 private BpmProcessInstanceService processInstanceService;
24 30
25 @Override 31 @Override
26 public BpmTriggerTypeEnum getType() { 32 public BpmTriggerTypeEnum getType() {
27 - return BpmTriggerTypeEnum.UPDATE_NORMAL_FORM; 33 + return BpmTriggerTypeEnum.FORM_UPDATE;
28 } 34 }
29 35
30 @Override 36 @Override
31 public void execute(String processInstanceId, String param) { 37 public void execute(String processInstanceId, String param) {
32 // 1. 解析更新流程表单配置 38 // 1. 解析更新流程表单配置
33 - NormalFormTriggerSetting setting = JsonUtils.parseObject(param, NormalFormTriggerSetting.class);  
34 - if (setting == null) { 39 + List<FormTriggerSetting> settings = JsonUtils.parseObject(param, new TypeReference<>() {});
  40 + if (CollUtil.isEmpty(settings)) {
35 log.error("[execute][流程({}) 更新流程表单触发器配置为空]", processInstanceId); 41 log.error("[execute][流程({}) 更新流程表单触发器配置为空]", processInstanceId);
36 return; 42 return;
37 } 43 }
38 - // 2.更新流程变量  
39 - if (CollUtil.isNotEmpty(setting.getUpdateFormFields())) {  
40 - processInstanceService.updateProcessInstanceVariables(processInstanceId, setting.getUpdateFormFields()); 44 +
  45 + // 2. 获取流程变量
  46 + Map<String, Object> processVariables = processInstanceService.getProcessInstance(processInstanceId).getProcessVariables();
  47 +
  48 + // 3. 更新流程变量
  49 + for (FormTriggerSetting setting : settings) {
  50 + if (CollUtil.isEmpty(setting.getUpdateFormFields())) {
  51 + continue;
  52 + }
  53 + // 配置了条件,判断条件是否满足
  54 + boolean isFormUpdateNeeded = true;
  55 + if (setting.getConditionType() != null) {
  56 + String conditionExpression = SimpleModelUtils.buildConditionExpression(
  57 + setting.getConditionType(), setting.getConditionExpression(), setting.getConditionGroups());
  58 + isFormUpdateNeeded = BpmnModelUtils.evalConditionExpress(processVariables, conditionExpression);
  59 + }
  60 + // 更新流程表单
  61 + if (isFormUpdateNeeded) {
  62 + processInstanceService.updateProcessInstanceVariables(processInstanceId, setting.getUpdateFormFields());
  63 + }
41 } 64 }
42 } 65 }
43 -  
44 } 66 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmAbstractHttpRequestTrigger.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.service.task.trigger.http;
  2 +
  3 +import cn.iocoder.yudao.module.bpm.service.task.trigger.BpmTrigger;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +
  6 +/**
  7 + * BPM 发送 HTTP 请求触发器抽象类
  8 + *
  9 + * @author jason
  10 + */
  11 +@Slf4j
  12 +public abstract class BpmAbstractHttpRequestTrigger implements BpmTrigger {
  13 +
  14 +}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmHttpCallbackTrigger.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.service.task.trigger.http;
  2 +
  3 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  4 +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
  5 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmHttpRequestParamTypeEnum;
  6 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmTriggerTypeEnum;
  7 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils;
  8 +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
  9 +import jakarta.annotation.Resource;
  10 +import lombok.extern.slf4j.Slf4j;
  11 +import org.flowable.engine.runtime.ProcessInstance;
  12 +import org.springframework.stereotype.Component;
  13 +import org.springframework.web.client.RestTemplate;
  14 +
  15 +/**
  16 + * BPM HTTP 回调触发器
  17 + *
  18 + * @author jason
  19 + */
  20 +@Component
  21 +@Slf4j
  22 +public class BpmHttpCallbackTrigger extends BpmAbstractHttpRequestTrigger {
  23 +
  24 + @Resource
  25 + private RestTemplate restTemplate;
  26 +
  27 + @Resource
  28 + private BpmProcessInstanceService processInstanceService;
  29 +
  30 + @Override
  31 + public BpmTriggerTypeEnum getType() {
  32 + return BpmTriggerTypeEnum.HTTP_CALLBACK;
  33 + }
  34 +
  35 + @Override
  36 + public void execute(String processInstanceId, String param) {
  37 + // 1. 解析 http 请求配置
  38 + BpmSimpleModelNodeVO.TriggerSetting.HttpRequestTriggerSetting setting = JsonUtils.parseObject(param,
  39 + BpmSimpleModelNodeVO.TriggerSetting.HttpRequestTriggerSetting.class);
  40 + if (setting == null) {
  41 + log.error("[execute][流程({}) HTTP 回调触发器配置为空]", processInstanceId);
  42 + return;
  43 + }
  44 +
  45 + // 2. 发起请求
  46 + ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
  47 + setting.getBody().add(new BpmSimpleModelNodeVO.HttpRequestParam()
  48 + .setKey("taskDefineKey") // 重要:回调请求 taskDefineKey 需要传给被调用方,用于回调执行
  49 + .setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType()).setValue(setting.getCallbackTaskDefineKey()));
  50 + BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
  51 + setting.getUrl(),
  52 + setting.getHeader(),
  53 + setting.getBody(),
  54 + false, null,
  55 + restTemplate,
  56 + processInstanceService);
  57 + }
  58 +
  59 +}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmSyncHttpRequestTrigger.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.service.task.trigger.http;
  2 +
  3 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  4 +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TriggerSetting.HttpRequestTriggerSetting;
  5 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmTriggerTypeEnum;
  6 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils;
  7 +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
  8 +import jakarta.annotation.Resource;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +import org.flowable.engine.runtime.ProcessInstance;
  11 +import org.springframework.stereotype.Component;
  12 +import org.springframework.web.client.RestTemplate;
  13 +
  14 +/**
  15 + * BPM 发送同步 HTTP 请求触发器
  16 + *
  17 + * @author jason
  18 + */
  19 +@Component
  20 +@Slf4j
  21 +public class BpmSyncHttpRequestTrigger extends BpmAbstractHttpRequestTrigger {
  22 +
  23 + @Resource
  24 + private RestTemplate restTemplate;
  25 +
  26 + @Resource
  27 + private BpmProcessInstanceService processInstanceService;
  28 +
  29 + @Override
  30 + public BpmTriggerTypeEnum getType() {
  31 + return BpmTriggerTypeEnum.HTTP_REQUEST;
  32 + }
  33 +
  34 + @Override
  35 + public void execute(String processInstanceId, String param) {
  36 + // 1. 解析 http 请求配置
  37 + HttpRequestTriggerSetting setting = JsonUtils.parseObject(param, HttpRequestTriggerSetting.class);
  38 + if (setting == null) {
  39 + log.error("[execute][流程({}) HTTP 触发器请求配置为空]", processInstanceId);
  40 + return;
  41 + }
  42 +
  43 + // 2. 发起请求
  44 + ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
  45 + BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
  46 + setting.getUrl(),
  47 + setting.getHeader(),
  48 + setting.getBody(),
  49 + true, setting.getResponse(),
  50 + restTemplate,
  51 + processInstanceService);
  52 + }
  53 +
  54 +}
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/logger/ApiAccessLogServiceImpl.java
1 package cn.iocoder.yudao.module.infra.service.logger; 1 package cn.iocoder.yudao.module.infra.service.logger;
2 2
3 -import cn.hutool.core.util.StrUtil;  
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  5 +import cn.iocoder.yudao.framework.common.util.string.StrUtils;
6 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; 6 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
7 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; 7 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
8 import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO; 8 import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
@@ -35,8 +35,8 @@ public class ApiAccessLogServiceImpl implements ApiAccessLogService { @@ -35,8 +35,8 @@ public class ApiAccessLogServiceImpl implements ApiAccessLogService {
35 @Override 35 @Override
36 public void createApiAccessLog(ApiAccessLogCreateReqDTO createDTO) { 36 public void createApiAccessLog(ApiAccessLogCreateReqDTO createDTO) {
37 ApiAccessLogDO apiAccessLog = BeanUtils.toBean(createDTO, ApiAccessLogDO.class); 37 ApiAccessLogDO apiAccessLog = BeanUtils.toBean(createDTO, ApiAccessLogDO.class);
38 - apiAccessLog.setRequestParams(StrUtil.maxLength(apiAccessLog.getRequestParams(), REQUEST_PARAMS_MAX_LENGTH));  
39 - apiAccessLog.setResultMsg(StrUtil.maxLength(apiAccessLog.getResultMsg(), RESULT_MSG_MAX_LENGTH)); 38 + apiAccessLog.setRequestParams(StrUtils.maxLength(apiAccessLog.getRequestParams(), REQUEST_PARAMS_MAX_LENGTH));
  39 + apiAccessLog.setResultMsg(StrUtils.maxLength(apiAccessLog.getResultMsg(), RESULT_MSG_MAX_LENGTH));
40 if (TenantContextHolder.getTenantId() != null) { 40 if (TenantContextHolder.getTenantId() != null) {
41 apiAccessLogMapper.insert(apiAccessLog); 41 apiAccessLogMapper.insert(apiAccessLog);
42 } else { 42 } else {
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/logger/ApiErrorLogServiceImpl.java
1 package cn.iocoder.yudao.module.infra.service.logger; 1 package cn.iocoder.yudao.module.infra.service.logger;
2 2
3 -import cn.hutool.core.util.StrUtil;  
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  5 +import cn.iocoder.yudao.framework.common.util.string.StrUtils;
6 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; 6 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
7 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; 7 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
8 import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO; 8 import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
@@ -39,7 +39,7 @@ public class ApiErrorLogServiceImpl implements ApiErrorLogService { @@ -39,7 +39,7 @@ public class ApiErrorLogServiceImpl implements ApiErrorLogService {
39 public void createApiErrorLog(ApiErrorLogCreateReqDTO createDTO) { 39 public void createApiErrorLog(ApiErrorLogCreateReqDTO createDTO) {
40 ApiErrorLogDO apiErrorLog = BeanUtils.toBean(createDTO, ApiErrorLogDO.class) 40 ApiErrorLogDO apiErrorLog = BeanUtils.toBean(createDTO, ApiErrorLogDO.class)
41 .setProcessStatus(ApiErrorLogProcessStatusEnum.INIT.getStatus()); 41 .setProcessStatus(ApiErrorLogProcessStatusEnum.INIT.getStatus());
42 - apiErrorLog.setRequestParams(StrUtil.maxLength(apiErrorLog.getRequestParams(), REQUEST_PARAMS_MAX_LENGTH)); 42 + apiErrorLog.setRequestParams(StrUtils.maxLength(apiErrorLog.getRequestParams(), REQUEST_PARAMS_MAX_LENGTH));
43 if (TenantContextHolder.getTenantId() != null) { 43 if (TenantContextHolder.getTenantId() != null) {
44 apiErrorLogMapper.insert(apiErrorLog); 44 apiErrorLogMapper.insert(apiErrorLog);
45 } else { 45 } else {