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 149  
150 150 ### 工作流程
151 151  
152   -| | 功能 | 描述 |
153   -|----|-------|-----------------------------------------|
154   -| 🚀 | 流程模型 | 配置工作流的流程模型,支持 BPMN 和仿钉钉/飞书设计器 |
155   -| 🚀 | 流程表单 | 拖动表单元素生成相应的工作流表单,覆盖 Element UI 所有的表单组件 |
156   -| 🚀 | 用户分组 | 自定义用户分组,可用于工作流的审批分组 |
157   -| 🚀 | 我的流程 | 查看我发起的工作流程,支持新建、取消流程等操作,高亮流程图、审批时间线 |
158   -| 🚀 | 待办任务 | 查看自己【未】审批的工作任务,支持通过、不通过、转派、委派、退回、加减签等操作 |
159   -| 🚀 | 已办任务 | 查看自己【已】审批的工作任务,支持流程预测,展示未来审批人信息 |
160   -| 🚀 | OA 请假 | 作为业务自定义接入工作流的使用示例,只需创建请求对应的工作流程,即可进行审批 |
161   -
162 152 ![功能图](/.image/common/bpm-feature.png)
163 153  
  154 +基于 Flowable 构建,可支持信创(国产)数据库,满足中国特色流程操作:
  155 +
164 156 | BPMN 设计器 | 钉钉/飞书设计器 |
165 157 |------------------------------|--------------------------------|
166 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 1 package cn.iocoder.yudao.module.bpm.api.task;
2 2  
3 3 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
4   -
5 4 import jakarta.validation.Valid;
6 5  
7 6 /**
... ... @@ -20,4 +19,6 @@ public interface BpmProcessInstanceApi {
20 19 */
21 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 24 ErrorCode MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS = new ErrorCode(1_009_002_005, "部署流程失败,原因:BPMN 流程图中,没有开始事件");
25 25 ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在");
26 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 29 // ========== 流程定义 1-009-003-000 ==========
29 30 ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图");
... ... @@ -39,6 +40,8 @@ public interface ErrorCodeConstants {
39 40 ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "任务({})的候选人({})不存在");
40 41 ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程");
41 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 46 // ========== 流程任务 1-009-005-000 ==========
44 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 14 public enum BpmBoundaryEventTypeEnum {
15 15  
16 16 USER_TASK_TIMEOUT(1, "用户任务超时"),
17   - DELAY_TIMER_TIMEOUT(2, "延迟器超时");
  17 + DELAY_TIMER_TIMEOUT(2, "延迟器超时"),
  18 + CHILD_PROCESS_TIMEOUT(3, "子流程超时");
18 19  
19 20 private final Integer type;
20 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 25 START_USER_NODE(10, "发起人", "userTask"), // 发起人节点。前端的开始节点,Id 固定
26 26 APPROVE_NODE(11, "审批人", "userTask"),
27 27 COPY_NODE(12, "抄送人", "serviceTask"),
  28 + TRANSACTOR_NODE(13, "办理人", "userTask"),
28 29  
29 30 DELAY_TIMER_NODE(14, "延迟器", "receiveTask"),
30 31 TRIGGER_NODE(15, "触发器", "serviceTask"),
31 32  
  33 + CHILD_PROCESS(20, "子流程", "callActivity"),
  34 +
32 35 // 50 ~ 条件分支
33 36 CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式
34 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 16 @AllArgsConstructor
17 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 43 public static BpmTriggerTypeEnum typeOf(Integer type) {
40 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 26 TIMEOUT_REJECT("审批超时,系统自动不通过"),
27 27 ASSIGN_START_USER_APPROVE("审批人与提交人为同一人时,自动通过"),
28 28 ASSIGN_START_USER_APPROVE_WHEN_SKIP("审批人与提交人为同一人时,自动通过"),
  29 + ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE("发起人节点首次自动通过"), // 目前仅“子流程”使用
29 30 ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND("审批人与提交人为同一人时,找不到部门负责人,自动通过"),
30 31 ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"),
31 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 2  
3 3 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
4 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 5 import jakarta.annotation.Resource;
9 6 import jakarta.validation.Valid;
  7 +import org.springframework.stereotype.Service;
  8 +import org.springframework.validation.annotation.Validated;
10 9  
11 10 /**
12 11 * Flowable 流程实例 Api 实现类
... ... @@ -25,4 +24,5 @@ public class BpmProcessInstanceApiImpl implements BpmProcessInstanceApi {
25 24 public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO reqDTO) {
26 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 57 @GetMapping("/list")
58 58 @Operation(summary = "获得模型分页")
59 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 61 List<Model> list = modelService.getModelList(name);
62 62 if (CollUtil.isEmpty(list)) {
63 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 17 import io.swagger.v3.oas.annotations.tags.Tag;
18 18 import jakarta.annotation.Resource;
19 19 import org.flowable.bpmn.model.BpmnModel;
  20 +import org.flowable.common.engine.impl.db.SuspensionState;
20 21 import org.flowable.engine.repository.Deployment;
21 22 import org.flowable.engine.repository.ProcessDefinition;
22 23 import org.springframework.security.access.prepost.PreAuthorize;
... ... @@ -31,6 +32,7 @@ import java.util.List;
31 32 import java.util.Map;
32 33  
33 34 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  35 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
34 36 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
35 37 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
36 38  
... ... @@ -99,6 +101,17 @@ public class BpmProcessDefinitionController {
99 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 115 @GetMapping ("/get")
103 116 @Operation(summary = "获得流程定义")
104 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 2  
3 3 import cn.iocoder.yudao.framework.common.core.KeyValue;
4 4 import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
5 6 import cn.iocoder.yudao.module.bpm.enums.definition.BpmAutoApproveTypeEnum;
6 7 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
7 8 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum;
... ... @@ -27,8 +28,7 @@ import java.util.List;
27 28 @Data
28 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 32 @URL(message = "流程图标格式不正确")
33 33 private String icon;
34 34  
... ... @@ -46,6 +46,7 @@ public class BpmModelMetaInfoVO {
46 46 private Integer formType;
47 47 @Schema(description = "表单编号", example = "1024")
48 48 private Long formId; // formType 为 NORMAL 使用,必须非空
  49 +
49 50 @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/create")
50 51 private String formCustomCreatePath; // 表单类型为 CUSTOM 时,必须非空
51 52 @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/view")
... ... @@ -81,6 +82,14 @@ public class BpmModelMetaInfoVO {
81 82 @Schema(description = "摘要设置", example = "{}")
82 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 93 @Schema(description = "流程 ID 规则")
85 94 @Data
86 95 @Valid
... ... @@ -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 25 @Schema(description = "流程图标", example = "https://www.iocoder.cn/yudao.jpg")
26 26 private String icon;
27 27  
28   - @Schema(description = "流程分类编码", example = "1")
  28 + @Schema(description = "流程分类编号", example = "1")
29 29 private String category;
30 30 @Schema(description = "流程分类名字", example = "请假")
31 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 4 import cn.iocoder.yudao.framework.common.validation.InEnum;
5 5 import cn.iocoder.yudao.module.bpm.enums.definition.*;
6 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
  7 +import com.fasterxml.jackson.annotation.JsonIgnore;
7 8 import com.fasterxml.jackson.annotation.JsonInclude;
8 9 import io.swagger.v3.oas.annotations.media.Schema;
9 10 import jakarta.validation.Valid;
10 11 import jakarta.validation.constraints.NotEmpty;
11 12 import jakarta.validation.constraints.NotNull;
12 13 import lombok.Data;
  14 +import org.flowable.bpmn.model.IOParameter;
13 15 import org.hibernate.validator.constraints.URL;
14 16  
15 17 import java.util.List;
16 18 import java.util.Map;
  19 +import java.util.Set;
17 20  
18 21 @Schema(description = "管理后台 - 仿钉钉流程设计模型节点 VO")
19 22 @Data
... ... @@ -114,7 +117,8 @@ public class BpmSimpleModelNodeVO {
114 117 @Schema(description = "路由分支组", example = "[]")
115 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 122 private String routerDefaultFlowId; // 仅用于路由分支节点 BpmSimpleModelNodeType.ROUTER_BRANCH_NODE
119 123  
120 124 /**
... ... @@ -122,6 +126,15 @@ public class BpmSimpleModelNodeVO {
122 126 */
123 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 138 @Schema(description = "任务监听器")
126 139 @Valid
127 140 @Data
... ... @@ -345,12 +358,10 @@ public class BpmSimpleModelNodeVO {
345 358 @Valid
346 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 366 @Schema(description = "http 请求触发器设置", example = "{}")
356 367 @Data
... ... @@ -369,7 +380,6 @@ public class BpmSimpleModelNodeVO {
369 380 @Valid
370 381 private List<HttpRequestParam> body;
371 382  
372   - // TODO @json:可能未来看情况,搞个 HttpResponseParam;得看看有没别的业务需要,抽象统一
373 383 /**
374 384 * 请求返回处理设置,用于修改流程表单值
375 385 * <p>
... ... @@ -379,15 +389,137 @@ public class BpmSimpleModelNodeVO {
379 389 @Schema(description = "请求返回处理设置", example = "[]")
380 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 400 @Schema(description = "流程表单触发器设置", example = "{}")
385 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 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 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 4 import io.swagger.v3.oas.annotations.media.Schema;
4 5 import lombok.Data;
5 6  
... ... @@ -8,7 +9,7 @@ import java.util.List;
8 9  
9 10 @Schema(description = "管理后台 - 流程定义 Response VO")
10 11 @Data
11   -public class BpmProcessDefinitionRespVO {
  12 +public class BpmProcessDefinitionRespVO extends BpmModelMetaInfoVO {
12 13  
13 14 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
14 15 private String id;
... ... @@ -19,15 +20,9 @@ public class BpmProcessDefinitionRespVO {
19 20 @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
20 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 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 26 @Schema(description = "流程分类", example = "1")
32 27 private String category;
33 28 @Schema(description = "流程分类名字", example = "请假")
... ... @@ -36,22 +31,15 @@ public class BpmProcessDefinitionRespVO {
36 31 @Schema(description = "流程模型的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
37 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 37 @Schema(description = "表单的配置-JSON 字符串。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", requiredMode = Schema.RequiredMode.REQUIRED)
46 38 private String formConf;
47 39 @Schema(description = "表单项的数组-JSON 字符串的数组。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", requiredMode = Schema.RequiredMode.REQUIRED)
48 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 44 @Schema(description = "中断状态-参见 SuspensionState 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
57 45 private Integer suspensionState; // 参见 SuspensionState 枚举
... ... @@ -67,7 +55,7 @@ public class BpmProcessDefinitionRespVO {
67 55  
68 56 @Schema(description = "流程定义排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
69 57 private Long sort;
70   -
  58 +
71 59 @Schema(description = "BPMN UserTask 用户任务")
72 60 @Data
73 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 1 package cn.iocoder.yudao.module.bpm.controller.admin.task;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.StrUtil;
4 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
6 8 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
7 9 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
8 10 import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert;
... ... @@ -30,10 +32,10 @@ import org.springframework.web.bind.annotation.*;
30 32  
31 33 import java.util.List;
32 34 import java.util.Map;
  35 +import java.util.Set;
33 36  
34 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 39 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
38 40  
39 41 @Tag(name = "管理后台 - 流程实例") // 流程实例,通过流程定义创建的一次“申请”
... ... @@ -76,8 +78,14 @@ public class BpmProcessInstanceController {
76 78 convertSet(processDefinitionMap.values(), ProcessDefinition::getCategory));
77 79 Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap(
78 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 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 91 @GetMapping("/manager-page")
... ... @@ -140,6 +148,7 @@ public class BpmProcessInstanceController {
140 148 processDefinition, processDefinitionInfo, startUser, dept));
141 149 }
142 150  
  151 + // TODO @lesan:【子流程】子流程如果取消,主流程应该是通过、还是不通过哈?还是禁用掉子流程的取消?
143 152 @DeleteMapping("/cancel-by-start-user")
144 153 @Operation(summary = "用户取消流程实例", description = "取消发起的流程")
145 154 @PreAuthorize("@ss.hasPermission('bpm:process-instance:cancel')")
... ... @@ -162,10 +171,25 @@ public class BpmProcessInstanceController {
162 171 @Operation(summary = "获得审批详情")
163 172 @Parameter(name = "id", description = "流程实例的编号", required = true)
164 173 @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
  174 + @SuppressWarnings("unchecked")
165 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 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 193 @GetMapping("/get-bpmn-model-view")
170 194 @Operation(summary = "获取流程实例的 BPMN 模型视图", description = "在【流程详细】界面中,进行调用")
171 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 18 @Schema(description = "流程变量")
19 19 private Map<String, Object> processVariables; // 使用场景:同 processDefinitionId,用于流程预测
20 20  
  21 + @Schema(description = "流程变量")
  22 + private String processVariablesStr; // 解决 GET 无法传递对象的问题,最终转换成 processVariables 变量
  23 +
21 24 @Schema(description = "流程实例的编号", example = "1024")
22 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 3 import cn.iocoder.yudao.framework.common.core.KeyValue;
4 4 import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
5 5 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
  6 +import com.fasterxml.jackson.annotation.JsonIgnore;
6 7 import io.swagger.v3.oas.annotations.media.Schema;
7 8 import lombok.Data;
8 9  
... ... @@ -73,6 +74,13 @@ public class BpmProcessInstanceRespVO {
73 74 @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
74 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 4 import jakarta.validation.constraints.NotEmpty;
5 5 import lombok.Data;
6 6  
  7 +import java.util.List;
7 8 import java.util.Map;
8 9  
9 10 @Schema(description = "管理后台 - 通过流程任务的 Request VO")
... ... @@ -23,4 +24,7 @@ public class BpmTaskApproveReqVO {
23 24 @Schema(description = "变量实例(动态表单)", requiredMode = Schema.RequiredMode.REQUIRED)
24 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 18 @Schema(description = "流程分类", example = "1")
19 19 private String category;
20 20  
  21 + @Schema(description = "流程定义的标识", example = "2048")
  22 + private String processDefinitionKey; // 精准匹配
  23 +
21 24 @Schema(description = "创建时间")
22 25 @DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
23 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 85 @Schema(description = "是否填写审批意见", example = "false")
86 86 private Boolean reasonRequire;
87 87  
  88 + @Schema(description = "节点类型", example = "10")
  89 + private Integer nodeType; // 参见 BpmSimpleModelNodeTypeEnum 枚举。
  90 +
88 91 @Data
89 92 @Schema(description = "流程实例")
90 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 76 respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
77 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 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 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 4 import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
5   -import cn.iocoder.yudao.framework.mybatis.core.type.StringListTypeHandler;
6 5 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
7 6 import cn.iocoder.yudao.module.bpm.enums.definition.BpmAutoApproveTypeEnum;
8 7 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
... ... @@ -61,6 +60,14 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
61 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 73 private String icon;
... ... @@ -149,7 +156,7 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
149 156 *
150 157 * 关联 {@link AdminUserRespDTO#getId()} 字段的数组
151 158 */
152   - @TableField(typeHandler = StringListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤
  159 + @TableField(typeHandler = LongListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤
153 160 private List<Long> managerUserIds;
154 161  
155 162 /**
... ... @@ -175,11 +182,22 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
175 182 */
176 183 @TableField(typeHandler = JacksonTypeHandler.class)
177 184 private BpmModelMetaInfoVO.TitleSetting titleSetting;
178   -
179 185 /**
180 186 * 摘要设置
181 187 */
182 188 @TableField(typeHandler = JacksonTypeHandler.class)
183 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 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
2 2  
3 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 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 8 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
6   -import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
7 9 import lombok.Setter;
8 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 14 import org.flowable.engine.delegate.DelegateExecution;
10 15 import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
11 16 import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
12 17  
  18 +import java.util.List;
13 19 import java.util.Set;
14 20  
15 21 /**
... ... @@ -42,27 +48,44 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
42 48 */
43 49 @Override
44 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 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
  5 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmChildProcessMultiInstanceSourceTypeEnum;
5 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 8 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
7 9 import lombok.Setter;
8 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 14 import org.flowable.engine.delegate.DelegateExecution;
10 15 import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
11 16 import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
12 17  
  18 +import java.util.List;
13 19 import java.util.Set;
14 20  
15 21 /**
... ... @@ -35,28 +41,45 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB
35 41 */
36 42 @Override
37 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 19 import com.google.common.annotations.VisibleForTesting;
20 20 import lombok.extern.slf4j.Slf4j;
21 21 import org.flowable.bpmn.model.BpmnModel;
  22 +import org.flowable.bpmn.model.CallActivity;
22 23 import org.flowable.bpmn.model.FlowElement;
23 24 import org.flowable.bpmn.model.UserTask;
24 25 import org.flowable.engine.delegate.DelegateExecution;
... ... @@ -129,8 +130,12 @@ public class BpmTaskCandidateInvoker {
129 130  
130 131 public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId,
131 132 Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
132   - // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过
  133 + // 如果是 CallActivity 子流程,不进行计算候选人
133 134 FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId);
  135 + if (flowElement instanceof CallActivity) {
  136 + return new HashSet<>();
  137 + }
  138 + // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过
134 139 Integer approveType = BpmnModelUtils.parseApproveType(flowElement);
135 140 if (ObjectUtils.equalsAny(approveType,
136 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 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.lang.Assert;
5   -import cn.hutool.core.util.ObjectUtil;
6 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy;
7 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 7 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
10 8 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
11 9 import com.google.common.collect.Sets;
12 10 import jakarta.annotation.Resource;
13 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 12 import org.flowable.engine.delegate.DelegateExecution;
18 13 import org.flowable.engine.runtime.ProcessInstance;
19 14 import org.springframework.context.annotation.Lazy;
20 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 22 * 发起人自选 {@link BpmTaskCandidateUserStrategy} 实现类
... ... @@ -55,7 +52,7 @@ public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCand
55 52 execution.getProcessInstanceId());
56 53 // 获得审批人
57 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 58 @Override
... ... @@ -70,28 +67,7 @@ public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCand
70 67 }
71 68 // 获得审批人
72 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 24 MULTI_DEPT_LEADER_MULTI(23, "连续多级部门的负责人"),
25 25 POST(22, "岗位"),
26 26 USER(30, "用户"),
27   - START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时选择此节点的审批人
  27 + APPROVE_USER_SELECT(34, "审批人自身"), // 当前审批人,可在审批时,选择下一个节点的审批人
  28 + START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时,选择此节点的审批人
28 29 START_USER(36, "发起人自己"), // 申请人自己, 一般紧挨开始节点,常用于发起人信息审核场景
29 30 START_USER_DEPT_LEADER(37, "发起人部门负责人"),
30 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 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 6 * BPMN XML 常量信息
5 7 *
... ... @@ -67,6 +69,11 @@ public interface BpmnModelConstants {
67 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 77 * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限
71 78 */
72 79 String FORM_FIELD_PERMISSION_ELEMENT = "fieldsPermission";
... ... @@ -129,4 +136,11 @@ public interface BpmnModelConstants {
129 136 */
130 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 27 * 流程实例的变量 - 发起用户选择的审批人 Map
28 28 *
29 29 * @see ProcessInstance#getProcessVariables()
  30 + * @see BpmTaskCandidateStrategyEnum#START_USER_SELECT
30 31 */
31 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 41 * 流程实例的变量 - 发起用户 ID
34 42 *
35 43 * @see ProcessInstance#getProcessVariables()
... ... @@ -52,6 +60,13 @@ public class BpmnVariableConstants {
52 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 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 22 public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEventListener {
23 23  
24 24 public static final Set<FlowableEngineEventType> PROCESS_INSTANCE_EVENTS = ImmutableSet.<FlowableEngineEventType>builder()
  25 + .add(FlowableEngineEventType.PROCESS_CREATED)
25 26 .add(FlowableEngineEventType.PROCESS_COMPLETED)
26 27 .add(FlowableEngineEventType.PROCESS_CANCELLED)
27 28 .build();
... ... @@ -35,6 +36,11 @@ public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEvent
35 36 }
36 37  
37 38 @Override
  39 + protected void processCreated(FlowableEngineEntityEvent event) {
  40 + processInstanceService.processProcessInstanceCreated((ProcessInstance)event.getEntity());
  41 + }
  42 +
  43 + @Override
38 44 protected void processCompleted(FlowableEngineEntityEvent event) {
39 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 109 // 2.2 延迟器超时处理
110 110 } else if (ObjectUtil.equal(bpmTimerBoundaryEventType, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT)) {
111 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 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 175 * @param rejectHandler 任务拒绝处理
... ... @@ -410,6 +421,26 @@ public class BpmnModelUtils {
410 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 444 // ========== BPM 简单查找相关的方法 ==========
414 445  
415 446 /**
... ... @@ -777,71 +808,214 @@ public class BpmnModelUtils {
777 808 // 情况:ExclusiveGateway 排它,只有一个满足条件的。如果没有,就走默认的
778 809 if (currentElement instanceof ExclusiveGateway) {
779 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 812 // 遍历满足条件的 SequenceFlow 路径
793 813 if (matchSequenceFlow != null) {
794 814 simulateNextFlowElements(matchSequenceFlow.getTargetFlowElement(), variables, resultElements, visitElements);
795 815 }
796   - return;
797 816 }
798   -
799 817 // 情况:InclusiveGateway 包容,多个满足条件的。如果没有,就走默认的
800   - if (currentElement instanceof InclusiveGateway) {
  818 + else if (currentElement instanceof InclusiveGateway) {
801 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 821 // 遍历满足条件的 SequenceFlow 路径
815 822 matchSequenceFlows.forEach(
816 823 flow -> simulateNextFlowElements(flow.getTargetFlowElement(), variables, resultElements, visitElements));
817 824 }
818   -
819 825 // 情况:ParallelGateway 并行,都满足,都走
820   - if (currentElement instanceof ParallelGateway) {
  826 + else if (currentElement instanceof ParallelGateway) {
821 827 Gateway gateway = (Gateway) currentElement;
822 828 // 遍历子节点
823 829 gateway.getOutgoingFlows().forEach(
824 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 997 * 计算条件表达式是否为 true 满足条件
831 998 *
832 999 * @param variables 流程实例
833   - * @param express 条件表达式
  1000 + * @param expression 条件表达式
834 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 1005 return Boolean.FALSE;
839 1006 }
  1007 + // 如果 variables 为空,则创建一个的原因?可能 expression 的计算,不依赖于 variables
  1008 + if (variables == null) {
  1009 + variables = new HashMap<>();
  1010 + }
  1011 +
  1012 + // 执行计算
840 1013 try {
841   - Object result = FlowableUtils.getExpressionValue(variables, express);
  1014 + Object result = FlowableUtils.getExpressionValue(variables, expression);
842 1015 return Boolean.TRUE.equals(result);
843 1016 } catch (FlowableException ex) {
844   - log.error("[evalConditionExpress][条件表达式({}) 变量({}) 解析报错]", express, variables, ex);
  1017 + // 为什么使用 info 日志?原因是,expression 如果从 variables 取不到值,会报错。实际这种情况下,可以忽略
  1018 + log.info("[evalConditionExpress][条件表达式({}) 变量({}) 解析报错]", expression, variables, ex);
845 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 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
2 2  
  3 +import cn.hutool.core.map.MapUtil;
3 4 import cn.hutool.core.util.ObjectUtil;
4 5 import cn.hutool.extra.spring.SpringUtil;
5 6 import cn.iocoder.yudao.framework.common.core.KeyValue;
... ... @@ -24,7 +25,10 @@ import org.flowable.engine.impl.util.CommandContextUtil;
24 25 import org.flowable.engine.runtime.ProcessInstance;
25 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 32 import java.util.concurrent.Callable;
29 33 import java.util.stream.Collectors;
30 34  
... ... @@ -190,13 +194,38 @@ public class FlowableUtils {
190 194 @SuppressWarnings("unchecked")
191 195 public static Map<String, List<Long>> getStartUserSelectAssignees(Map<String, Object> processVariables) {
192 196 if (processVariables == null) {
193   - return null;
  197 + return new HashMap<>();
194 198 }
195 199 return (Map<String, List<Long>>) processVariables.get(
196 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 231 * 仅有 {@link BpmModelFormTypeEnum#getType()} 表单,才有摘要。
... ... @@ -240,7 +269,7 @@ public class FlowableUtils {
240 269 return formFieldsMap.entrySet().stream()
241 270 .limit(3)
242 271 .map(entry -> new KeyValue<>(entry.getValue().getTitle(),
243   - processVariables.getOrDefault(entry.getValue().getField(), "").toString()))
  272 + MapUtil.getStr(processVariables, entry.getValue().getField(), "")))
244 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 5 import cn.hutool.core.map.MapUtil;
6 6 import cn.hutool.core.util.*;
7 7 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  8 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
8 9 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
9 10 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.ConditionGroups;
10 11 import cn.iocoder.yudao.module.bpm.enums.definition.*;
11 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 14 import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate;
14 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 18 import org.flowable.bpmn.BpmnAutoLayout;
16 19 import org.flowable.bpmn.constants.BpmnXMLConstants;
17 20 import org.flowable.bpmn.model.Process;
18 21 import org.flowable.bpmn.model.*;
  22 +import org.flowable.engine.delegate.ExecutionListener;
19 23 import org.flowable.engine.delegate.TaskListener;
20   -import org.springframework.util.MultiValueMap;
21 24  
22 25 import java.util.*;
23 26  
24 27 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
25 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 29 import static java.util.Arrays.asList;
28 30  
29 31 /**
... ... @@ -40,9 +42,10 @@ public class SimpleModelUtils {
40 42  
41 43 static {
42 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 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 49 converts.forEach(convert -> NODE_CONVERTS.put(convert.getType(), convert));
47 50 }
48 51  
... ... @@ -78,7 +81,7 @@ public class SimpleModelUtils {
78 81 traverseNodeToBuildFlowNode(startNode, process);
79 82  
80 83 // 3. 构建并添加节点之间的连线 Sequence Flow
81   - EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel);
  84 + EndEvent endEvent = getEndEvent(bpmnModel);
82 85 traverseNodeToBuildSequenceFlow(process, startNode, endEvent.getId());
83 86  
84 87 // 4. 自动布局
... ... @@ -164,8 +167,16 @@ public class SimpleModelUtils {
164 167 // 情况一:有“子”节点,则建立连线
165 168 // 情况二:没有“子节点”,则直接跟 targetNodeId 建立连线。例如说,结束节点、条件分支(分支节点的孩子节点或聚合节点)的最后一个节点
166 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 182 if (isChildNodeValid) {
... ... @@ -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 201 * 遍历条件节点,构建 SequenceFlow 元素
178 202 *
179 203 * @param process Bpmn 流程
... ... @@ -337,7 +361,7 @@ public class SimpleModelUtils {
337 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 366 addCandidateElements(BpmTaskCandidateStrategyEnum.START_USER.getStrategy(), null, userTask);
343 367 // 添加表单字段权限属性元素
... ... @@ -388,24 +412,17 @@ public class SimpleModelUtils {
388 412 */
389 413 private BoundaryEvent buildUserTaskTimeoutBoundaryEvent(UserTask userTask,
390 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 417 if (Objects.equals(BpmUserTaskTimeoutHandlerTypeEnum.REMINDER.getType(), timeoutHandler.getType()) &&
400 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 426 addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_TYPE, timeoutHandler.getType());
410 427 return boundaryEvent;
411 428 }
... ... @@ -445,6 +462,8 @@ public class SimpleModelUtils {
445 462 addSignEnable(node.getSignEnable(), userTask);
446 463 // 审批意见
447 464 addReasonRequire(node.getReasonRequire(), userTask);
  465 + // 节点类型
  466 + addNodeType(node.getType(), userTask);
448 467 return userTask;
449 468 }
450 469  
... ... @@ -455,7 +474,7 @@ public class SimpleModelUtils {
455 474 FlowableListener flowableListener = new FlowableListener();
456 475 flowableListener.setEvent(TaskListener.EVENTNAME_CREATE);
457 476 flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
458   - flowableListener.setImplementation(DELEGATE_EXPRESSION);
  477 + flowableListener.setImplementation(BpmUserTaskListener.DELEGATE_EXPRESSION);
459 478 addListenerConfig(flowableListener, node.getTaskCreateListener());
460 479 flowableListeners.add(flowableListener);
461 480 }
... ... @@ -464,7 +483,7 @@ public class SimpleModelUtils {
464 483 FlowableListener flowableListener = new FlowableListener();
465 484 flowableListener.setEvent(TaskListener.EVENTNAME_ASSIGNMENT);
466 485 flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
467   - flowableListener.setImplementation(DELEGATE_EXPRESSION);
  486 + flowableListener.setImplementation(BpmUserTaskListener.DELEGATE_EXPRESSION);
468 487 addListenerConfig(flowableListener, node.getTaskAssignListener());
469 488 flowableListeners.add(flowableListener);
470 489 }
... ... @@ -473,7 +492,7 @@ public class SimpleModelUtils {
473 492 FlowableListener flowableListener = new FlowableListener();
474 493 flowableListener.setEvent(TaskListener.EVENTNAME_COMPLETE);
475 494 flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
476   - flowableListener.setImplementation(DELEGATE_EXPRESSION);
  495 + flowableListener.setImplementation(BpmUserTaskListener.DELEGATE_EXPRESSION);
477 496 addListenerConfig(flowableListener, node.getTaskCompleteListener());
478 497 flowableListeners.add(flowableListener);
479 498 }
... ... @@ -486,7 +505,7 @@ public class SimpleModelUtils {
486 505 BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod);
487 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 509 if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RANDOM) {
491 510 // 随机审批,不需要设置多实例属性
492 511 return;
... ... @@ -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 545 private static class CopyNodeConvert implements NodeConvert {
518 546  
519 547 @Override
... ... @@ -684,20 +712,16 @@ public class SimpleModelUtils {
684 712  
685 713 // 2. 添加接收任务的 Timer Boundary Event
686 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 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 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 725 flowElements.add(boundaryEvent);
702 726 }
703 727 return flowElements;
... ... @@ -712,23 +736,36 @@ public class SimpleModelUtils {
712 736 public static class TriggerNodeConvert implements NodeConvert {
713 737  
714 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 754 // 触发器使用 ServiceTask 来实现
717 755 ServiceTask serviceTask = new ServiceTask();
718 756 serviceTask.setId(node.getId());
719 757 serviceTask.setName(node.getName());
720 758 serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
721 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 771 @Override
... ... @@ -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 898 private static String buildGatewayJoinId(String id) {
766 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 927 // ========== SIMPLE 流程预测相关的方法 ==========
770 928  
771 929 public static List<BpmSimpleModelNodeVO> simulateProcess(BpmSimpleModelNodeVO rootNode, Map<String, Object> variables) {
... ... @@ -785,11 +943,13 @@ public class SimpleModelUtils {
785 943 BpmSimpleModelNodeTypeEnum nodeType = BpmSimpleModelNodeTypeEnum.valueOf(currentNode.getType());
786 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 947 if (nodeType == BpmSimpleModelNodeTypeEnum.START_NODE
790 948 || nodeType == BpmSimpleModelNodeTypeEnum.START_USER_NODE
791 949 || nodeType == BpmSimpleModelNodeTypeEnum.APPROVE_NODE
  950 + || nodeType == BpmSimpleModelNodeTypeEnum.TRANSACTOR_NODE
792 951 || nodeType == BpmSimpleModelNodeTypeEnum.COPY_NODE
  952 + || nodeType == BpmSimpleModelNodeTypeEnum.CHILD_PROCESS
793 953 || nodeType == BpmSimpleModelNodeTypeEnum.END_NODE) {
794 954 // 添加元素
795 955 resultNodes.add(currentNode);
... ... @@ -799,8 +959,8 @@ public class SimpleModelUtils {
799 959 if (nodeType == BpmSimpleModelNodeTypeEnum.CONDITION_BRANCH_NODE) {
800 960 // 查找满足条件的 BpmSimpleModelNodeVO 节点
801 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 964 if (matchConditionNode == null) {
805 965 matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(),
806 966 conditionNode -> BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow()));
... ... @@ -814,8 +974,8 @@ public class SimpleModelUtils {
814 974 if (nodeType == BpmSimpleModelNodeTypeEnum.INCLUSIVE_BRANCH_NODE) {
815 975 // 查找满足条件的 BpmSimpleModelNodeVO 节点
816 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 979 if (CollUtil.isEmpty(matchConditionNodes)) {
820 980 matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(),
821 981 conditionNode -> BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow()));
... ... @@ -841,27 +1001,4 @@ public class SimpleModelUtils {
841 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 16 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum;
17 17 import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
18 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 20 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
20 21 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
21 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 24 import jakarta.annotation.Resource;
24 25 import jakarta.validation.Valid;
25 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 28 import org.flowable.common.engine.impl.db.SuspensionState;
30 29 import org.flowable.engine.HistoryService;
31 30 import org.flowable.engine.RepositoryService;
... ... @@ -41,13 +40,12 @@ import org.springframework.stereotype.Service;
41 40 import org.springframework.transaction.annotation.Transactional;
42 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 45 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
49 46 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
50 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 51 * 流程模型实现:主要进行 Flowable {@link Model} 的维护
... ... @@ -209,11 +207,11 @@ public class BpmModelServiceImpl implements BpmModelService {
209 207 public void deployModel(Long userId, String id) {
210 208 // 1.1 校验流程模型存在
211 209 Model model = validateModelManager(id, userId);
  210 + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
212 211 // 1.2 校验流程图
213 212 byte[] bpmnBytes = getModelBpmnXML(model.getId());
214   - validateBpmnXml(bpmnBytes);
  213 + validateBpmnXml(bpmnBytes, metaInfo.getType());
215 214 // 1.3 校验表单已配
216   - BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
217 215 BpmFormDO form = validateFormConfig(metaInfo);
218 216 // 1.4 校验任务分配规则已配置
219 217 taskCandidateInvoker.validateBpmnConfig(bpmnBytes);
... ... @@ -233,7 +231,7 @@ public class BpmModelServiceImpl implements BpmModelService {
233 231 repositoryService.saveModel(model);
234 232 }
235 233  
236   - private void validateBpmnXml(byte[] bpmnBytes) {
  234 + private void validateBpmnXml(byte[] bpmnBytes, Integer type) {
237 235 BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes);
238 236 if (bpmnModel == null) {
239 237 throw exception(MODEL_NOT_EXISTS);
... ... @@ -250,6 +248,17 @@ public class BpmModelServiceImpl implements BpmModelService {
250 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 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 28  
29 29 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
30 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 32 import static java.util.Collections.emptyList;
34 33  
35 34 /**
... ... @@ -144,9 +143,8 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
144 143  
145 144 // 插入拓展表
146 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 147 .setModelType(modelMetaInfo.getType()).setSimpleModel(simpleJson);
149   -
150 148 if (form != null) {
151 149 definitionDO.setFormFields(form.getFields()).setFormConf(form.getConf());
152 150 }
... ... @@ -156,16 +154,25 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
156 154  
157 155 @Override
158 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 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 167 return;
163 168 }
164 169 // 挂起
165 170 if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), state)) {
166 171 // suspendProcessInstances = false,进行中的任务,不进行挂起。
167 172 // 原因:只要新的流程不允许发起即可,老流程继续可以执行。
168   - repositoryService.suspendProcessDefinitionById(id, false, null);
  173 + if (!processDefinition.isSuspended()) {
  174 + repositoryService.suspendProcessDefinitionById(id, false, null);
  175 + }
169 176 return;
170 177 }
171 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 7 import org.flowable.engine.history.HistoricProcessInstance;
8 8 import org.flowable.engine.runtime.ProcessInstance;
9 9  
  10 +import java.util.Collection;
10 11 import java.util.List;
11 12 import java.util.Map;
12 13 import java.util.Set;
... ... @@ -84,7 +85,6 @@ public interface BpmProcessInstanceService {
84 85 PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId,
85 86 @Valid BpmProcessInstancePageReqVO pageReqVO);
86 87  
87   - // TODO @芋艿:重点在 review 下
88 88 /**
89 89 * 获取审批详情。
90 90 * <p>
... ... @@ -97,6 +97,15 @@ public interface BpmProcessInstanceService {
97 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 109 * 获取流程实例的 BPMN 模型视图
101 110 *
102 111 * @param id 流程实例的编号
... ... @@ -148,6 +157,22 @@ public interface BpmProcessInstanceService {
148 157 */
149 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 176 // ========== Event 事件相关方法 ==========
152 177  
153 178 /**
... ... @@ -158,11 +183,9 @@ public interface BpmProcessInstanceService {
158 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 5 import cn.hutool.core.date.DateUtil;
6 6 import cn.hutool.core.lang.Assert;
7 7 import cn.hutool.core.util.ArrayUtil;
  8 +import cn.hutool.core.util.ObjUtil;
8 9 import cn.hutool.core.util.ObjectUtil;
9 10 import cn.hutool.core.util.StrUtil;
10 11 import cn.iocoder.yudao.framework.common.pojo.PageResult;
... ... @@ -13,6 +14,7 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
13 14 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
14 15 import cn.iocoder.yudao.framework.common.util.object.PageUtils;
15 16 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
  17 +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
16 18 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
17 19 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
18 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 30 import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
29 31 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
30 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 34 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
33 35 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
34 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 38 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
36 39 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
37 40 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils;
... ... @@ -54,11 +57,13 @@ import org.flowable.engine.history.HistoricProcessInstanceQuery;
54 57 import org.flowable.engine.repository.ProcessDefinition;
55 58 import org.flowable.engine.runtime.ProcessInstance;
56 59 import org.flowable.engine.runtime.ProcessInstanceBuilder;
  60 +import org.flowable.task.api.Task;
57 61 import org.flowable.task.api.history.HistoricTaskInstance;
58 62 import org.springframework.context.annotation.Lazy;
59 63 import org.springframework.stereotype.Service;
60 64 import org.springframework.transaction.annotation.Transactional;
61 65 import org.springframework.validation.annotation.Validated;
  66 +import org.springframework.web.client.RestTemplate;
62 67  
63 68 import java.util.*;
64 69  
... ... @@ -67,6 +72,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
67 72 import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode;
68 73 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
69 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 76 import static java.util.Arrays.asList;
71 77 import static java.util.Collections.singletonList;
72 78 import static org.flowable.bpmn.constants.BpmnXMLConstants.*;
... ... @@ -116,6 +122,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
116 122 @Resource
117 123 private BpmProcessIdRedisDAO processIdRedisDAO;
118 124  
  125 + @Resource
  126 + private RestTemplate restTemplate;
  127 +
119 128 // ========== Query 查询相关方法 ==========
120 129  
121 130 @Override
... ... @@ -144,7 +153,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
144 153 }
145 154  
146 155 private Map<String, String> getFormFieldsPermission(BpmnModel bpmnModel,
147   - String activityId, String taskId) {
  156 + String activityId, String taskId) {
148 157 // 1. 获取流程活动编号。流程活动 Id 为空事,从流程任务中获取流程活动 Id
149 158 if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(taskId)) {
150 159 activityId = Optional.ofNullable(taskService.getHistoricTask(taskId))
... ... @@ -164,7 +173,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
164 173 Long startUserId = loginUserId; // 流程发起人
165 174 HistoricProcessInstance historicProcessInstance = null; // 流程实例
166 175 Integer processInstanceStatus = BpmProcessInstanceStatusEnum.NOT_START.getStatus(); // 流程状态
167   - Map<String, Object> processVariables = reqVO.getProcessVariables(); // 流程变量
  176 + Map<String, Object> processVariables = new HashMap<>(); // 流程变量
168 177 // 1.2 如果是流程已发起的场景,则使用流程实例的数据
169 178 if (reqVO.getProcessInstanceId() != null) {
170 179 historicProcessInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId());
... ... @@ -173,7 +182,13 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
173 182 }
174 183 startUserId = Long.valueOf(historicProcessInstance.getStartUserId());
175 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 193 // 1.3 读取其它相关数据
179 194 ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition(
... ... @@ -205,14 +220,16 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
205 220 }
206 221  
207 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 224 // 3.2 预测未运行节点的审批信息
213 225 List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel,
214 226 processDefinitionInfo,
215 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 234 // 4. 拼接最终数据
218 235 return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance,
... ... @@ -220,6 +237,62 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
220 237 }
221 238  
222 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 296 @SuppressWarnings("unchecked")
224 297 public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId,
225 298 BpmProcessInstancePageReqVO pageReqVO) {
... ... @@ -283,15 +356,15 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
283 356 * 主要是,拼接审批人的用户信息、部门信息
284 357 */
285 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 368 // 1. 获取所有需要读取用户信息的 userIds
296 369 List<ActivityNode> approveNodes = newArrayList(
297 370 asList(endApprovalNodeInfos, runningApprovalNodeInfos, simulateApprovalNodeInfos));
... ... @@ -313,19 +386,21 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
313 386 * 获得【已结束】的活动节点们
314 387 */
315 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 392 // 遍历 tasks 列表,只处理已结束的 UserTask
320   - // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities
321   - // 的话,它无法成为一个节点
  393 + // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities 的话,它无法成为一个节点
  394 + // TODO @芋艿:子流程只有activity,这里获取不到已结束的子流程;
  395 + // TODO @lesan:【子流程】基于 activities 查询出 usertask、callactivity,然后拼接?如果是子流程,就是可以点击过去?
322 396 List<HistoricTaskInstance> endTasks = filterList(tasks, task -> task.getEndTime() != null);
323 397 List<ActivityNode> approvalNodes = convertList(endTasks, task -> {
324 398 FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
325 399 ActivityNode activityNode = new ActivityNode().setId(task.getTaskDefinitionKey()).setName(task.getName())
326 400 .setNodeType(START_USER_NODE_ID.equals(task.getTaskDefinitionKey())
327 401 ? BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType()
328   - : BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType())
  402 + : ObjUtil.defaultIfNull(parseNodeType(flowNode), // 目的:解决“办理节点”的识别
  403 + BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()))
329 404 .setStatus(FlowableUtils.getTaskStatus(task))
330 405 .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))
331 406 .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime()))
... ... @@ -381,18 +456,19 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
381 456 * 获得【进行中】的活动节点们
382 457 */
383 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 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 467 Map<String, List<HistoricActivityInstance>> runningTaskMap = convertMultiMap(runActivities,
393 468 HistoricActivityInstance::getActivityId);
394 469  
395 470 // 按照 activityId 分组,构建 ApprovalNodeInfo 节点
  471 + // TODO @lesan:【子流程】在子流程进行审批的时候,HistoricActivityInstance 里面可以拿到 runActivities.get(0).getCalledProcessInstanceId()。要不要支持跳转???
396 472 Map<String, HistoricTaskInstance> taskMap = convertMap(tasks, HistoricTaskInstance::getId);
397 473 return convertList(runningTaskMap.entrySet(), entry -> {
398 474 String activityId = entry.getKey();
... ... @@ -402,7 +478,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
402 478 HistoricActivityInstance firstActivity = CollUtil.getFirst(taskActivities); // 取第一个任务,会签/或签的任务,开始时间相同
403 479 ActivityNode activityNode = new ActivityNode().setId(firstActivity.getActivityId())
404 480 .setName(firstActivity.getActivityName())
405   - .setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType())
  481 + .setNodeType(ObjUtil.defaultIfNull(parseNodeType(flowNode), // 目的:解决“办理节点”和"子流程"的识别
  482 + BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()))
406 483 .setStatus(BpmTaskStatusEnum.RUNNING.getStatus())
407 484 .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))
408 485 .setStartTime(DateUtils.of(CollUtil.getFirst(taskActivities).getStartTime()))
... ... @@ -410,6 +487,11 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
410 487 // 处理每个任务的 tasks 属性
411 488 for (HistoricActivityInstance activity : taskActivities) {
412 489 HistoricTaskInstance task = taskMap.get(activity.getTaskId());
  490 + // 特殊情况:子流程节点 ChildProcess 仅存在于 activity 中,并且没有自身的 task,需要跳过执行
  491 + // TODO @芋艿:后续看看怎么优化!
  492 + if (task == null) {
  493 + continue;
  494 + }
413 495 activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task));
414 496 // 加签子任务,需要过滤掉已经完成的加签子任务
415 497 List<HistoricTaskInstance> childrenTasks = filterList(
... ... @@ -440,9 +522,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
440 522 * 获得【预测(未来)】的活动节点们
441 523 */
442 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 528 // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance
447 529 // 包括了历史的操作,不是只有 startEvent 到当前节点的记录
448 530 Set<String> runActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId);
... ... @@ -464,8 +546,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
464 546 }
465 547  
466 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 551 // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance
470 552 // 包括了历史的操作,不是只有 startEvent 到当前节点的记录
471 553 if (runActivityIds.contains(node.getId())) {
... ... @@ -479,7 +561,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
479 561 // 1. 开始节点/审批节点
480 562 if (ObjectUtils.equalsAny(node.getType(),
481 563 BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType(),
482   - BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType())) {
  564 + BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType(),
  565 + BpmSimpleModelNodeTypeEnum.TRANSACTOR_NODE.getType())) {
483 566 List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(),
484 567 startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables);
485 568 activityNode.setCandidateUserIds(candidateUserIds);
... ... @@ -494,14 +577,22 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
494 577 // 3. 抄送节点
495 578 if (CollUtil.isEmpty(runActivityIds) && // 流程发起时:需要展示抄送节点,用于选择抄送人
496 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 588 return activityNode;
498 589 }
499 590 return null;
500 591 }
501 592  
502 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 596 if (runActivityIds.contains(node.getId())) {
506 597 return null;
507 598 }
... ... @@ -532,7 +623,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
532 623 }
533 624  
534 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 627 Set<Long> userIds = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId,
537 628 startUserId, processDefinitionId, processVariables);
538 629 return new ArrayList<>(userIds);
... ... @@ -568,11 +659,11 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
568 659 Set<String> finishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId,
569 660 activityInstance -> activityInstance.getEndTime() != null
570 661 && ObjectUtil.notEqual(activityInstance.getActivityType(),
571   - BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW));
  662 + BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW));
572 663 Set<String> finishedSequenceFlowActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId,
573 664 activityInstance -> activityInstance.getEndTime() != null
574 665 && ObjectUtil.equals(activityInstance.getActivityType(),
575   - BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW));
  666 + BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW));
576 667 // 特殊:会签情况下,会有部分已完成(审批)、部分未完成(待审批),此时需要 finishedTaskActivityIds 移除掉
577 668 finishedTaskActivityIds.removeAll(unfinishedTaskActivityIds);
578 669 // 特殊:如果流程实例被拒绝,则需要计算是哪个活动节点。
... ... @@ -624,8 +715,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
624 715 }
625 716  
626 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 720 // 1.1 校验流程定义
630 721 if (definition == null) {
631 722 throw exception(PROCESS_DEFINITION_NOT_EXISTS);
... ... @@ -643,7 +734,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
643 734 throw exception(PROCESS_INSTANCE_START_USER_CAN_START);
644 735 }
645 736 // 1.3 校验发起人自选审批人
646   - validateStartUserSelectAssignees(definition, startUserSelectAssignees);
  737 + validateStartUserSelectAssignees(userId, definition, startUserSelectAssignees, variables);
647 738  
648 739 // 2. 创建流程实例
649 740 if (variables == null) {
... ... @@ -653,10 +744,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
653 744 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, userId); // 设置流程变量,发起人 ID
654 745 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中
655 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 748 if (CollUtil.isNotEmpty(startUserSelectAssignees)) {
  749 + // 设置流程变量,发起人自选审批人
660 750 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES,
661 751 startUserSelectAssignees);
662 752 }
... ... @@ -688,17 +778,23 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
688 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 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 798 List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(task.getId()) : null;
703 799 if (CollUtil.isEmpty(assignees)) {
704 800 throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, task.getName());
... ... @@ -771,6 +867,16 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
771 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 880 // ========== Event 事件相关方法 ==========
775 881  
776 882 @Override
... ... @@ -802,12 +908,47 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
802 908 // 3. 发送流程实例的状态事件
803 909 processInstanceEventPublisher.sendProcessInstanceResultEvent(
804 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 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 35 PageResult<Task> getTaskTodoPage(Long userId, BpmTaskPageReqVO pageReqVO);
36 36  
37 37 /**
38   - * 获得用户在指定流程下,首个需要处理(待办)的任务
  38 + * 获得用户(待办)的任务:
  39 + * 1. 根据 taskId 查询待办任务
  40 + * 2. 如果任务不存在(或者已审核),获取指定流程下,首个需要处理任务
39 41 *
40 42 * @param userId 用户编号
  43 + * @param taskId 任务编号
41 44 * @param processInstanceId 流程实例编号
42 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 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 106 * @param id 任务编号
... ... @@ -277,11 +288,22 @@ public interface BpmTaskService {
277 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 304 * @param processInstanceId 流程示例编号
283 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 1 package cn.iocoder.yudao.module.bpm.service.task;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.convert.Convert;
4 5 import cn.hutool.core.lang.Assert;
5 6 import cn.hutool.core.util.*;
6 7 import cn.hutool.extra.spring.SpringUtil;
... ... @@ -19,6 +20,7 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum;
19 20 import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
20 21 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum;
21 22 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
  23 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
22 24 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
23 25 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
24 26 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
... ... @@ -40,6 +42,7 @@ import org.flowable.engine.ManagementService;
40 42 import org.flowable.engine.RuntimeService;
41 43 import org.flowable.engine.TaskService;
42 44 import org.flowable.engine.history.HistoricActivityInstance;
  45 +import org.flowable.engine.runtime.ActivityInstance;
43 46 import org.flowable.engine.runtime.Execution;
44 47 import org.flowable.engine.runtime.ProcessInstance;
45 48 import org.flowable.task.api.DelegationState;
... ... @@ -61,7 +64,9 @@ import java.util.stream.Stream;
61 64 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
62 65 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
63 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 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 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 121 if (StrUtil.isNotEmpty(pageVO.getCategory())) {
117 122 taskQuery.taskCategory(pageVO.getCategory());
118 123 }
  124 + if (StrUtil.isNotEmpty(pageVO.getProcessDefinitionKey())) {
  125 + taskQuery.processDefinitionKey(pageVO.getProcessDefinitionKey());
  126 + }
119 127 if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
120 128 taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));
121 129 taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1]));
... ... @@ -129,32 +137,19 @@ public class BpmTaskServiceImpl implements BpmTaskService {
129 137 }
130 138  
131 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 147 if (todoTask == null) {
154 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 154 // 3. 转换返回
160 155 BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(todoTask.getProcessDefinitionId());
... ... @@ -162,6 +157,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
162 157 bpmnModel, todoTask.getTaskDefinitionKey());
163 158 Boolean signEnable = parseSignEnable(bpmnModel, todoTask.getTaskDefinitionKey());
164 159 Boolean reasonRequire = parseReasonRequire(bpmnModel, todoTask.getTaskDefinitionKey());
  160 + Integer nodeType = parseNodeType(BpmnModelUtils.getFlowElementById(bpmnModel, todoTask.getTaskDefinitionKey()));
165 161  
166 162 // 4. 任务表单
167 163 BpmFormDO taskForm = null;
... ... @@ -170,8 +166,55 @@ public class BpmTaskServiceImpl implements BpmTaskService {
170 166 }
171 167  
172 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 220 @Override
... ... @@ -194,6 +237,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
194 237 return PageResult.empty();
195 238 }
196 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 244 return new PageResult<>(tasks, count);
198 245 }
199 246  
... ... @@ -243,13 +290,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
243 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 295 Task task = validateTaskExist(taskId);
254 296 // 为什么判断 assignee 非空的情况下?
255 297 // 例如说:在审批人为空时,我们会有“自动审批通过”的策略,此时 userId 为 null,允许通过
... ... @@ -515,21 +557,87 @@ public class BpmTaskServiceImpl implements BpmTaskService {
515 557 // 2.2 添加评论
516 558 taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.APPROVE.getType(),
517 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 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 643 * <p>
... ... @@ -991,6 +1099,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
991 1099  
992 1100 @Override
993 1101 @Transactional(rollbackFor = Exception.class)
  1102 + @SuppressWarnings("DataFlowIssue")
994 1103 public void deleteSignTask(Long userId, BpmTaskSignDeleteReqVO reqVO) {
995 1104 // 1.1 校验 task 可以被减签
996 1105 Task task = validateTaskCanSignDelete(reqVO.getId());
... ... @@ -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 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 1344 Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement);
1224 1345  
1225 1346 // 情况一:自动跳过
... ... @@ -1304,14 +1425,23 @@ public class BpmTaskServiceImpl implements BpmTaskService {
1304 1425 }
1305 1426  
1306 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 1439 Execution execution = runtimeService.createExecutionQuery()
1309 1440 .processInstanceId(processInstanceId)
1310 1441 .activityId(taskDefineKey)
1311 1442 .singleResult();
1312 1443 if (execution == null) {
1313   - log.error("[processDelayTimerTimeout][processInstanceId({}) activityId({}) 没有找到执行活动]",
1314   - processInstanceId, taskDefineKey);
  1444 + log.error("[triggerTask][processInstanceId({}) activityId({}) 没有找到执行活动]", processInstanceId, taskDefineKey);
1315 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 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 1 package cn.iocoder.yudao.module.bpm.service.task.listener;
2 2  
3   -import cn.hutool.core.util.StrUtil;
4 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 6 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
7 7 import jakarta.annotation.Resource;
8 8 import lombok.Setter;
9 9 import lombok.extern.slf4j.Slf4j;
10 10 import org.flowable.engine.delegate.TaskListener;
11   -import org.flowable.engine.history.HistoricProcessInstance;
12 11 import org.flowable.engine.impl.el.FixedValue;
  12 +import org.flowable.engine.runtime.ProcessInstance;
13 13 import org.flowable.task.service.delegate.DelegateTask;
14 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 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 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 18 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.parseListenerConfig;
28 19  
29 20 // TODO @芋艿:可能会想换个包地址
... ... @@ -51,46 +42,27 @@ public class BpmUserTaskListener implements TaskListener {
51 42 @Override
52 43 public void notify(DelegateTask delegateTask) {
53 44 // 1. 获取所需基础信息
54   - HistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(delegateTask.getProcessInstanceId());
  45 + ProcessInstance processInstance = processInstanceService.getProcessInstance(delegateTask.getProcessInstanceId());
55 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 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 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 3 import cn.hutool.core.collection.CollUtil;
4 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 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 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 12 import jakarta.annotation.Resource;
9 13 import lombok.extern.slf4j.Slf4j;
10 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 20 * BPM 更新流程表单触发器
15 21 *
... ... @@ -17,28 +23,44 @@ import org.springframework.stereotype.Component;
17 23 */
18 24 @Component
19 25 @Slf4j
20   -public class BpmUpdateNormalFormTrigger implements BpmTrigger {
  26 +public class BpmFormUpdateTrigger implements BpmTrigger {
21 27  
22 28 @Resource
23 29 private BpmProcessInstanceService processInstanceService;
24 30  
25 31 @Override
26 32 public BpmTriggerTypeEnum getType() {
27   - return BpmTriggerTypeEnum.UPDATE_NORMAL_FORM;
  33 + return BpmTriggerTypeEnum.FORM_UPDATE;
28 34 }
29 35  
30 36 @Override
31 37 public void execute(String processInstanceId, String param) {
32 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 41 log.error("[execute][流程({}) 更新流程表单触发器配置为空]", processInstanceId);
36 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 1 package cn.iocoder.yudao.module.infra.service.logger;
2 2  
3   -import cn.hutool.core.util.StrUtil;
4 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  5 +import cn.iocoder.yudao.framework.common.util.string.StrUtils;
6 6 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
7 7 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
8 8 import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
... ... @@ -35,8 +35,8 @@ public class ApiAccessLogServiceImpl implements ApiAccessLogService {
35 35 @Override
36 36 public void createApiAccessLog(ApiAccessLogCreateReqDTO createDTO) {
37 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 40 if (TenantContextHolder.getTenantId() != null) {
41 41 apiAccessLogMapper.insert(apiAccessLog);
42 42 } else {
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/logger/ApiErrorLogServiceImpl.java
1 1 package cn.iocoder.yudao.module.infra.service.logger;
2 2  
3   -import cn.hutool.core.util.StrUtil;
4 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  5 +import cn.iocoder.yudao.framework.common.util.string.StrUtils;
6 6 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
7 7 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
8 8 import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
... ... @@ -39,7 +39,7 @@ public class ApiErrorLogServiceImpl implements ApiErrorLogService {
39 39 public void createApiErrorLog(ApiErrorLogCreateReqDTO createDTO) {
40 40 ApiErrorLogDO apiErrorLog = BeanUtils.toBean(createDTO, ApiErrorLogDO.class)
41 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 43 if (TenantContextHolder.getTenantId() != null) {
44 44 apiErrorLogMapper.insert(apiErrorLog);
45 45 } else {
... ...