Commit bc46c67a9955bc6e4cef6ca2931286c9b2dfa966
Merge remote-tracking branch 'origin/feature/bpm' into feature/bpm
Showing
22 changed files
with
346 additions
and
258 deletions
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java
| ... | ... | @@ -13,8 +13,7 @@ import lombok.Getter; |
| 13 | 13 | @AllArgsConstructor |
| 14 | 14 | public enum BpmBoundaryEventType { |
| 15 | 15 | |
| 16 | - USER_TASK_TIMEOUT(1,"用户任务超时"), | |
| 17 | - USER_TASK_REJECT_POST_PROCESS(2, "用户任务拒绝后处理"); | |
| 16 | + USER_TASK_TIMEOUT(1,"用户任务超时"); | |
| 18 | 17 | |
| 19 | 18 | private final Integer type; |
| 20 | 19 | private final String name; |
| ... | ... | @@ -22,4 +21,5 @@ public enum BpmBoundaryEventType { |
| 22 | 21 | public static BpmBoundaryEventType typeOf(Integer type) { |
| 23 | 22 | return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values()); |
| 24 | 23 | } |
| 24 | + | |
| 25 | 25 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.enums.definition; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; | |
| 4 | +import lombok.Getter; | |
| 5 | +import lombok.RequiredArgsConstructor; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * BPM 用户任务的审批人与发起人相同时,处理类型枚举 | |
| 9 | + * | |
| 10 | + * @author 芋道源码 | |
| 11 | + */ | |
| 12 | +@RequiredArgsConstructor | |
| 13 | +@Getter | |
| 14 | +public enum BpmUserTaskAssignStartUserHandlerTypeEnum implements IntArrayValuable { | |
| 15 | + | |
| 16 | + START_USER_AUDIT(1), // 由发起人对自己审批 | |
| 17 | + SKIP(2), // 自动跳过【参考飞书】:1)如果当前节点还有其他审批人,则交由其他审批人进行审批;2)如果当前节点没有其他审批人,则该节点自动通过 | |
| 18 | + ASSIGN_DEPT_LEADER(3); // 转交给部门负责人审批【参考飞书】:若部门负责人为空,则自动通过 | |
| 19 | + | |
| 20 | + private final Integer type; | |
| 21 | + | |
| 22 | + @Override | |
| 23 | + public int[] array() { | |
| 24 | + return new int[0]; | |
| 25 | + } | |
| 26 | + | |
| 27 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerType.java
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/message/BpmMessageEnum.java
| ... | ... | @@ -14,7 +14,8 @@ public enum BpmMessageEnum { |
| 14 | 14 | |
| 15 | 15 | PROCESS_INSTANCE_APPROVE("bpm_process_instance_approve"), // 流程任务被审批通过时,发送给申请人 |
| 16 | 16 | PROCESS_INSTANCE_REJECT("bpm_process_instance_reject"), // 流程任务被审批不通过时,发送给申请人 |
| 17 | - TASK_ASSIGNED("bpm_task_assigned"); // 任务被分配时,发送给审批人 | |
| 17 | + TASK_ASSIGNED("bpm_task_assigned"), // 任务被分配时,发送给审批人 | |
| 18 | + TASK_TIMEOUT("bpm_task_timeout"); // 任务审批超时时,发送给审批人 | |
| 18 | 19 | |
| 19 | 20 | /** |
| 20 | 21 | * 短信模板的标识 | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| 4 | -import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; | |
| 5 | -import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; | |
| 6 | -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; | |
| 7 | -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType; | |
| 4 | +import cn.iocoder.yudao.module.bpm.enums.definition.*; | |
| 8 | 5 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 9 | 6 | import com.fasterxml.jackson.annotation.JsonIgnore; |
| 10 | 7 | import com.fasterxml.jackson.annotation.JsonInclude; |
| ... | ... | @@ -84,6 +81,10 @@ public class BpmSimpleModelNodeVO { |
| 84 | 81 | */ |
| 85 | 82 | private TimeoutHandler timeoutHandler; |
| 86 | 83 | |
| 84 | + @Schema(description = "审批节点的审批人与发起人相同时,对应的处理类型", example = "1") | |
| 85 | + @InEnum(BpmUserTaskAssignStartUserHandlerTypeEnum.class) | |
| 86 | + private Integer assignStartUserHandlerType; | |
| 87 | + | |
| 87 | 88 | @Data |
| 88 | 89 | @Schema(description = "审批节点拒绝处理策略") |
| 89 | 90 | public static class RejectHandler { |
| ... | ... | @@ -96,6 +97,7 @@ public class BpmSimpleModelNodeVO { |
| 96 | 97 | private String returnNodeId; |
| 97 | 98 | } |
| 98 | 99 | |
| 100 | + // TODO @芋艿:参数校验 | |
| 99 | 101 | @Data |
| 100 | 102 | @Schema(description = "审批节点超时处理策略") |
| 101 | 103 | public static class TimeoutHandler { |
| ... | ... | @@ -103,6 +105,7 @@ public class BpmSimpleModelNodeVO { |
| 103 | 105 | @Schema(description = "是否开启超时处理", example = "false") |
| 104 | 106 | private Boolean enable; |
| 105 | 107 | |
| 108 | + // TODO @jason:type 是不是更合适哈; | |
| 106 | 109 | @Schema(description = "任务超时未处理的行为", example = "1") |
| 107 | 110 | @InEnum(BpmUserTaskTimeoutHandlerType.class) |
| 108 | 111 | private Integer action; |
| ... | ... | @@ -112,6 +115,7 @@ public class BpmSimpleModelNodeVO { |
| 112 | 115 | |
| 113 | 116 | @Schema(description = "最大提醒次数", example = "1") |
| 114 | 117 | private Integer maxRemindCount; |
| 118 | + | |
| 115 | 119 | } |
| 116 | 120 | |
| 117 | 121 | @Data |
| ... | ... | @@ -129,14 +133,7 @@ public class BpmSimpleModelNodeVO { |
| 129 | 133 | } |
| 130 | 134 | |
| 131 | 135 | // Map<String, Integer> formPermissions; 表单权限;仅发起、审批、抄送节点会使用 |
| 132 | - // Integer approveMethod; 审批方式;仅审批节点会使用 | |
| 133 | - // TODO @jason 后面和前端一起调整一下;下面的 ①、②、③ 是优先级 | |
| 134 | - // TODO @芋艿:① 审批人的选择; | |
| 135 | 136 | // TODO @芋艿:⑥ 没有人的策略? |
| 136 | - // TODO @芋艿:② 审批拒绝的策略? | |
| 137 | - // TODO @芋艿:③ 配置的可操作列表?(操作权限) | |
| 138 | - // TODO @芋艿:④ 表单的权限列表? | |
| 139 | - // TODO @芋艿:⑨ 超时配置;要支持指定时间点、指定时间间隔; | |
| 140 | 137 | // TODO @芋艿:条件;建议可以固化的一些选项;然后有个表达式兜底;要支持 |
| 141 | 138 | |
| 142 | 139 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java
| ... | ... | @@ -176,7 +176,6 @@ public interface BpmTaskConvert { |
| 176 | 176 | childTask.setParentTaskId(parentTask.getId()); |
| 177 | 177 | childTask.setProcessDefinitionId(parentTask.getProcessDefinitionId()); |
| 178 | 178 | childTask.setProcessInstanceId(parentTask.getProcessInstanceId()); |
| 179 | -// childTask.setExecutionId(parentTask.getExecutionId()); // TODO 芋艿:新加的,不太确定;尴尬,不加时,子任务不通过会失败(报错);加了,子任务审批通过会失败(报错) | |
| 180 | 179 | childTask.setTaskDefinitionKey(parentTask.getTaskDefinitionKey()); |
| 181 | 180 | childTask.setTaskDefinitionId(parentTask.getTaskDefinitionId()); |
| 182 | 181 | childTask.setPriority(parentTask.getPriority()); | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java
| ... | ... | @@ -2,11 +2,15 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate; |
| 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; | |
| 5 | 6 | import cn.hutool.core.util.StrUtil; |
| 7 | +import cn.hutool.extra.spring.SpringUtil; | |
| 6 | 8 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 7 | 9 | import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; |
| 10 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum; | |
| 8 | 11 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 9 | 12 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| 13 | +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; | |
| 10 | 14 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 11 | 15 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 12 | 16 | import com.google.common.annotations.VisibleForTesting; |
| ... | ... | @@ -14,6 +18,7 @@ import lombok.extern.slf4j.Slf4j; |
| 14 | 18 | import org.flowable.bpmn.model.BpmnModel; |
| 15 | 19 | import org.flowable.bpmn.model.UserTask; |
| 16 | 20 | import org.flowable.engine.delegate.DelegateExecution; |
| 21 | +import org.flowable.engine.runtime.ProcessInstance; | |
| 17 | 22 | |
| 18 | 23 | import java.util.HashMap; |
| 19 | 24 | import java.util.List; |
| ... | ... | @@ -86,6 +91,8 @@ public class BpmTaskCandidateInvoker { |
| 86 | 91 | Set<Long> userIds = getCandidateStrategy(strategy).calculateUsers(execution, param); |
| 87 | 92 | // 1.2 移除被禁用的用户 |
| 88 | 93 | removeDisableUsers(userIds); |
| 94 | + // 1.3 移除发起人的用户 | |
| 95 | + removeStartUserIfSkip(execution, userIds); | |
| 89 | 96 | |
| 90 | 97 | // 2. 校验是否有候选人 |
| 91 | 98 | if (CollUtil.isEmpty(userIds)) { |
| ... | ... | @@ -108,6 +115,29 @@ public class BpmTaskCandidateInvoker { |
| 108 | 115 | }); |
| 109 | 116 | } |
| 110 | 117 | |
| 118 | + /** | |
| 119 | + * 如果“审批人与发起人相同时”,配置了 SKIP 跳过,则移除发起人 | |
| 120 | + * | |
| 121 | + * 注意:如果只有一个候选人,则不处理,避免无法审批 | |
| 122 | + * | |
| 123 | + * @param execution 执行中的任务 | |
| 124 | + * @param assigneeUserIds 当前分配的候选人 | |
| 125 | + */ | |
| 126 | + @VisibleForTesting | |
| 127 | + void removeStartUserIfSkip(DelegateExecution execution, Set<Long> assigneeUserIds) { | |
| 128 | + if (CollUtil.size(assigneeUserIds) <= 1) { | |
| 129 | + return; | |
| 130 | + } | |
| 131 | + Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(execution.getCurrentFlowElement()); | |
| 132 | + if (ObjectUtil.notEqual(assignStartUserHandlerType, BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { | |
| 133 | + return; | |
| 134 | + } | |
| 135 | + ProcessInstance processInstance = SpringUtil.getBean(BpmProcessInstanceService.class) | |
| 136 | + .getProcessInstance(execution.getProcessInstanceId()); | |
| 137 | + Assert.notNull(processInstance, "流程实例({}) 不存在", execution.getProcessInstanceId()); | |
| 138 | + assigneeUserIds.remove(Long.valueOf(processInstance.getStartUserId())); | |
| 139 | + } | |
| 140 | + | |
| 111 | 141 | private BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) { |
| 112 | 142 | BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy); |
| 113 | 143 | Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy); | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
| ... | ... | @@ -29,12 +29,17 @@ public interface BpmnModelConstants { |
| 29 | 29 | String BOUNDARY_EVENT_TYPE = "boundaryEventType"; |
| 30 | 30 | |
| 31 | 31 | // TODO @jason:这个命名,应该也要改哈 |
| 32 | + // TODO @jason:1)是不是上面的 timeoutAction 改成 timeoutHandler; | |
| 32 | 33 | /** |
| 33 | 34 | * BPMN ExtensionElement 的扩展属性,用于标记用户任务超时执行动作 |
| 34 | 35 | */ |
| 35 | 36 | String USER_TASK_TIMEOUT_HANDLER_ACTION = "timeoutAction"; |
| 36 | 37 | |
| 37 | - // TODO @jason:1)是不是上面的 timeoutAction 改成 timeoutHandler;2)rejectHandlerType 改成 rejectHandler 哇? | |
| 38 | + /** | |
| 39 | + * BPMN ExtensionElement 的扩展属性,用于标记用户任务的审批人与发起人相同时,对应的处理类型 | |
| 40 | + */ | |
| 41 | + String USER_TASK_ASSIGN_START_USER_HANDLER_TYPE = "assignStartUserHandlerType"; | |
| 42 | + | |
| 38 | 43 | /** |
| 39 | 44 | * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝处理类型 |
| 40 | 45 | */ | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | +import cn.hutool.core.util.ObjectUtil; | |
| 4 | 5 | import cn.hutool.core.util.StrUtil; |
| 6 | +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | |
| 7 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; | |
| 8 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; | |
| 9 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; | |
| 10 | +import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; | |
| 5 | 11 | import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService; |
| 6 | 12 | import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; |
| 7 | 13 | import com.google.common.collect.ImmutableSet; |
| 8 | 14 | import jakarta.annotation.Resource; |
| 9 | 15 | import lombok.extern.slf4j.Slf4j; |
| 16 | +import org.flowable.bpmn.model.BoundaryEvent; | |
| 17 | +import org.flowable.bpmn.model.BpmnModel; | |
| 18 | +import org.flowable.bpmn.model.FlowElement; | |
| 10 | 19 | import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; |
| 11 | 20 | import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; |
| 12 | 21 | import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; |
| 13 | 22 | import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent; |
| 14 | 23 | import org.flowable.engine.history.HistoricActivityInstance; |
| 24 | +import org.flowable.job.api.Job; | |
| 15 | 25 | import org.flowable.task.api.Task; |
| 16 | 26 | import org.springframework.context.annotation.Lazy; |
| 17 | 27 | import org.springframework.stereotype.Component; |
| ... | ... | @@ -29,6 +39,9 @@ import java.util.Set; |
| 29 | 39 | public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { |
| 30 | 40 | |
| 31 | 41 | @Resource |
| 42 | + @Lazy // 延迟加载,避免循环依赖 | |
| 43 | + private BpmModelService modelService; | |
| 44 | + @Resource | |
| 32 | 45 | @Lazy // 解决循环依赖 |
| 33 | 46 | private BpmTaskService taskService; |
| 34 | 47 | @Resource |
| ... | ... | @@ -40,6 +53,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { |
| 40 | 53 | .add(FlowableEngineEventType.TASK_ASSIGNED) |
| 41 | 54 | // .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了。 |
| 42 | 55 | .add(FlowableEngineEventType.ACTIVITY_CANCELLED) |
| 56 | + .add(FlowableEngineEventType.TIMER_FIRED) // 监听审批超时 | |
| 43 | 57 | .build(); |
| 44 | 58 | |
| 45 | 59 | public BpmTaskEventListener() { |
| ... | ... | @@ -72,4 +86,30 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { |
| 72 | 86 | }); |
| 73 | 87 | } |
| 74 | 88 | |
| 89 | + @Override | |
| 90 | + protected void timerFired(FlowableEngineEntityEvent event) { | |
| 91 | + // 1.1 只处理 BoundaryEvent 边界计时时间 | |
| 92 | + String processDefinitionId = event.getProcessDefinitionId(); | |
| 93 | + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId); | |
| 94 | + Job entity = (Job) event.getEntity(); | |
| 95 | + FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId()); | |
| 96 | + if (!(element instanceof BoundaryEvent)) { | |
| 97 | + return; | |
| 98 | + } | |
| 99 | + // 1.2 判断是否为超时处理 | |
| 100 | + BoundaryEvent boundaryEvent = (BoundaryEvent) element; | |
| 101 | + String boundaryEventType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, | |
| 102 | + BpmnModelConstants.BOUNDARY_EVENT_TYPE); | |
| 103 | + BpmBoundaryEventType bpmTimerBoundaryEventType = BpmBoundaryEventType.typeOf(NumberUtils.parseInt(boundaryEventType)); | |
| 104 | + if (ObjectUtil.notEqual(bpmTimerBoundaryEventType, BpmBoundaryEventType.USER_TASK_TIMEOUT)) { | |
| 105 | + return; | |
| 106 | + } | |
| 107 | + | |
| 108 | + // 2. 处理超时 | |
| 109 | + String timeoutAction = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, | |
| 110 | + BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION); | |
| 111 | + String taskKey = boundaryEvent.getAttachedToRefId(); | |
| 112 | + taskService.processTaskTimeout(event.getProcessInstanceId(), taskKey, NumberUtils.parseInt(timeoutAction)); | |
| 113 | + } | |
| 114 | + | |
| 75 | 115 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java deleted
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; | |
| 2 | - | |
| 3 | -import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | |
| 4 | -import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | |
| 5 | -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO; | |
| 6 | -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRejectReqVO; | |
| 7 | -import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; | |
| 8 | -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType; | |
| 9 | -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; | |
| 10 | -import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage; | |
| 11 | -import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.producer.task.TodoTaskReminderProducer; | |
| 12 | -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; | |
| 13 | -import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; | |
| 14 | -import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; | |
| 15 | -import com.google.common.collect.ImmutableSet; | |
| 16 | -import jakarta.annotation.Resource; | |
| 17 | -import lombok.extern.slf4j.Slf4j; | |
| 18 | -import org.flowable.bpmn.model.BoundaryEvent; | |
| 19 | -import org.flowable.bpmn.model.BpmnModel; | |
| 20 | -import org.flowable.bpmn.model.FlowElement; | |
| 21 | -import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; | |
| 22 | -import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; | |
| 23 | -import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; | |
| 24 | -import org.flowable.job.api.Job; | |
| 25 | -import org.flowable.task.api.Task; | |
| 26 | -import org.springframework.context.annotation.Lazy; | |
| 27 | -import org.springframework.stereotype.Component; | |
| 28 | - | |
| 29 | -import java.util.List; | |
| 30 | -import java.util.Set; | |
| 31 | - | |
| 32 | -// TODO @芋艿:这块需要仔细再瞅瞅 | |
| 33 | -/** | |
| 34 | - * 监听定时器触发事件 | |
| 35 | - * | |
| 36 | - * @author jason | |
| 37 | - */ | |
| 38 | -@Component | |
| 39 | -@Slf4j | |
| 40 | -public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListener { | |
| 41 | - | |
| 42 | - @Resource | |
| 43 | - @Lazy // 延迟加载,避免循环依赖 | |
| 44 | - private BpmModelService bpmModelService; | |
| 45 | - @Resource | |
| 46 | - @Lazy // 延迟加载,避免循环依赖 | |
| 47 | - private BpmTaskService bpmTaskService; | |
| 48 | - | |
| 49 | - @Resource | |
| 50 | - private TodoTaskReminderProducer todoTaskReminderProducer; | |
| 51 | - | |
| 52 | - public static final Set<FlowableEngineEventType> TIME_EVENTS = ImmutableSet.<FlowableEngineEventType>builder() | |
| 53 | - .add(FlowableEngineEventType.TIMER_FIRED) | |
| 54 | - .build(); | |
| 55 | - | |
| 56 | - public BpmTimerFiredEventListener() { | |
| 57 | - super(TIME_EVENTS); | |
| 58 | - } | |
| 59 | - | |
| 60 | - @Override | |
| 61 | - protected void timerFired(FlowableEngineEntityEvent event) { | |
| 62 | - String processDefinitionId = event.getProcessDefinitionId(); | |
| 63 | - BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId); | |
| 64 | - Job entity = (Job) event.getEntity(); | |
| 65 | - FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId()); | |
| 66 | - // 如果是定时器边界事件 | |
| 67 | - if (element instanceof BoundaryEvent) { | |
| 68 | - BoundaryEvent boundaryEvent = (BoundaryEvent) element; | |
| 69 | - String boundaryEventType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.BOUNDARY_EVENT_TYPE); | |
| 70 | - BpmBoundaryEventType bpmTimerBoundaryEventType = BpmBoundaryEventType.typeOf(NumberUtils.parseInt(boundaryEventType)); | |
| 71 | - // 类型为用户任务超时未处理的情况 | |
| 72 | - if (bpmTimerBoundaryEventType == BpmBoundaryEventType.USER_TASK_TIMEOUT) { | |
| 73 | - String timeoutAction = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION); | |
| 74 | - userTaskTimeoutHandler(event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), NumberUtils.parseInt(timeoutAction)); | |
| 75 | - } | |
| 76 | - } | |
| 77 | - } | |
| 78 | - | |
| 79 | - private void userTaskTimeoutHandler(String processInstanceId, String taskDefKey, Integer timeoutAction) { | |
| 80 | - BpmUserTaskTimeoutHandlerType userTaskTimeoutAction = BpmUserTaskTimeoutHandlerType.typeOf(timeoutAction); | |
| 81 | - if (userTaskTimeoutAction != null) { | |
| 82 | - // 查询超时未处理的任务 TODO 加签的情况会不会有问题 ??? | |
| 83 | - List<Task> taskList = bpmTaskService.getRunningTaskListByProcessInstanceId(processInstanceId, true, taskDefKey); | |
| 84 | - taskList.forEach(task -> { | |
| 85 | - // 自动提醒 | |
| 86 | - if (userTaskTimeoutAction == BpmUserTaskTimeoutHandlerType.REMINDER) { | |
| 87 | - TodoTaskReminderMessage message = new TodoTaskReminderMessage().setTenantId(Long.parseLong(task.getTenantId())) | |
| 88 | - .setUserId(Long.parseLong(task.getAssignee())).setTaskName(task.getName()); | |
| 89 | - todoTaskReminderProducer.sendReminderMessage(message); | |
| 90 | - } | |
| 91 | - // 自动同意 | |
| 92 | - if (userTaskTimeoutAction == BpmUserTaskTimeoutHandlerType.APPROVE) { | |
| 93 | - // TODO @芋艿 这个上下文如何清除呢? 任务通过后, BpmProcessInstanceEventListener 会有回调 | |
| 94 | - TenantContextHolder.setTenantId(Long.parseLong(task.getTenantId())); | |
| 95 | - TenantContextHolder.setIgnore(false); | |
| 96 | - BpmTaskApproveReqVO req = new BpmTaskApproveReqVO().setId(task.getId()) | |
| 97 | - .setReason("超时系统自动同意"); | |
| 98 | - bpmTaskService.approveTask(Long.parseLong(task.getAssignee()), req); | |
| 99 | - } | |
| 100 | - // 自动拒绝 | |
| 101 | - if (userTaskTimeoutAction == BpmUserTaskTimeoutHandlerType.REJECT) { | |
| 102 | - // TODO @芋艿 这个上下文如何清除呢? 任务拒绝后, BpmProcessInstanceEventListener 会有回调 | |
| 103 | - TenantContextHolder.setTenantId(Long.parseLong(task.getTenantId())); | |
| 104 | - TenantContextHolder.setIgnore(false); | |
| 105 | - BpmTaskRejectReqVO req = new BpmTaskRejectReqVO().setId(task.getId()).setReason("超时系统自动拒绝"); | |
| 106 | - bpmTaskService.rejectTask(Long.parseLong(task.getAssignee()), req); | |
| 107 | - } | |
| 108 | - }); | |
| 109 | - } | |
| 110 | - } | |
| 111 | -} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/consumer/task/SysNotifyTodoTaskReminderConsumer.java deleted
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.consumer.task; | |
| 2 | - | |
| 3 | -import cn.hutool.core.map.MapUtil; | |
| 4 | -import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | |
| 5 | -import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage; | |
| 6 | -import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi; | |
| 7 | -import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO; | |
| 8 | -import jakarta.annotation.Resource; | |
| 9 | -import lombok.extern.slf4j.Slf4j; | |
| 10 | -import org.springframework.context.event.EventListener; | |
| 11 | -import org.springframework.scheduling.annotation.Async; | |
| 12 | -import org.springframework.stereotype.Component; | |
| 13 | - | |
| 14 | -import java.util.Map; | |
| 15 | - | |
| 16 | -/** | |
| 17 | - * 待办任务提醒 - 站内信的消费者 | |
| 18 | - * | |
| 19 | - * @author jason | |
| 20 | - */ | |
| 21 | -@Component | |
| 22 | -@Slf4j | |
| 23 | -public class SysNotifyTodoTaskReminderConsumer { | |
| 24 | - | |
| 25 | - private static final String TASK_REMIND_TEMPLATE_CODE = "user_task_remind"; | |
| 26 | - | |
| 27 | - @Resource | |
| 28 | - private NotifyMessageSendApi notifyMessageSendApi; | |
| 29 | - | |
| 30 | - @EventListener | |
| 31 | - @Async | |
| 32 | - public void onMessage(TodoTaskReminderMessage message) { | |
| 33 | - log.info("站内信消费者接收到消息 [消息内容({})] ", message); | |
| 34 | - TenantUtils.execute(message.getTenantId(), ()-> { | |
| 35 | - Map<String,Object> templateParams = MapUtil.newHashMap(); | |
| 36 | - templateParams.put("name", message.getTaskName()); | |
| 37 | - NotifySendSingleToUserReqDTO req = new NotifySendSingleToUserReqDTO().setUserId(message.getUserId()) | |
| 38 | - .setTemplateCode(TASK_REMIND_TEMPLATE_CODE).setTemplateParams(templateParams); | |
| 39 | - notifyMessageSendApi.sendSingleMessageToAdmin(req); | |
| 40 | - }); | |
| 41 | - } | |
| 42 | -} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/message/task/TodoTaskReminderMessage.java deleted
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task; | |
| 2 | - | |
| 3 | -import jakarta.validation.constraints.NotEmpty; | |
| 4 | -import jakarta.validation.constraints.NotNull; | |
| 5 | -import lombok.Data; | |
| 6 | - | |
| 7 | -/** | |
| 8 | - * 待办任务提醒消息 | |
| 9 | - * | |
| 10 | - * @author jason | |
| 11 | - */ | |
| 12 | -@Data | |
| 13 | -public class TodoTaskReminderMessage { | |
| 14 | - | |
| 15 | - /** | |
| 16 | - * 租户 Id | |
| 17 | - */ | |
| 18 | - @NotNull(message = "租户 Id 不能未空") | |
| 19 | - private Long tenantId; | |
| 20 | - | |
| 21 | - /** | |
| 22 | - * 用户Id | |
| 23 | - */ | |
| 24 | - @NotNull(message = "用户 Id 不能未空") | |
| 25 | - private Long userId; | |
| 26 | - | |
| 27 | - /** | |
| 28 | - * 任务名称 | |
| 29 | - */ | |
| 30 | - @NotEmpty(message = "任务名称不能未空") | |
| 31 | - private String taskName; | |
| 32 | - | |
| 33 | - // TODO 暂时只有站内信通知. 后面可以增加 | |
| 34 | -} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/producer/task/TodoTaskReminderProducer.java deleted
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.producer.task; | |
| 2 | - | |
| 3 | -import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage; | |
| 4 | -import jakarta.annotation.Resource; | |
| 5 | -import jakarta.validation.Valid; | |
| 6 | -import org.springframework.context.ApplicationContext; | |
| 7 | -import org.springframework.stereotype.Component; | |
| 8 | -import org.springframework.validation.annotation.Validated; | |
| 9 | - | |
| 10 | -// TODO @jason:建议直接调用 BpmMessageService 哈;更简化一点~ | |
| 11 | -/** | |
| 12 | - * 待办任务提醒 Producer | |
| 13 | - * | |
| 14 | - * @author jason | |
| 15 | - */ | |
| 16 | -@Component | |
| 17 | -@Validated | |
| 18 | -public class TodoTaskReminderProducer { | |
| 19 | - | |
| 20 | - @Resource | |
| 21 | - private ApplicationContext applicationContext; | |
| 22 | - | |
| 23 | - public void sendReminderMessage(@Valid TodoTaskReminderMessage message) { | |
| 24 | - applicationContext.publishEvent(message); | |
| 25 | - } | |
| 26 | - | |
| 27 | -} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
| ... | ... | @@ -53,6 +53,10 @@ public class BpmnModelUtils { |
| 53 | 53 | return BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID); |
| 54 | 54 | } |
| 55 | 55 | |
| 56 | + public static Integer parseAssignStartUserHandlerType(FlowElement userTask) { | |
| 57 | + return NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE)); | |
| 58 | + } | |
| 59 | + | |
| 56 | 60 | public static String parseExtensionElement(FlowElement flowElement, String elementName) { |
| 57 | 61 | if (flowElement == null) { |
| 58 | 62 | return null; | ... | ... |
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.util.ObjectUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; |
| 5 | +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | |
| 4 | 6 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; |
| 5 | 7 | import org.flowable.common.engine.api.delegate.Expression; |
| 6 | 8 | import org.flowable.common.engine.api.variable.VariableContainer; |
| ... | ... | @@ -16,6 +18,7 @@ import org.flowable.task.api.TaskInfo; |
| 16 | 18 | import java.util.HashMap; |
| 17 | 19 | import java.util.List; |
| 18 | 20 | import java.util.Map; |
| 21 | +import java.util.Objects; | |
| 19 | 22 | |
| 20 | 23 | /** |
| 21 | 24 | * Flowable 相关的工具方法 |
| ... | ... | @@ -39,6 +42,16 @@ public class FlowableUtils { |
| 39 | 42 | return tenantId != null ? String.valueOf(tenantId) : ProcessEngineConfiguration.NO_TENANT_ID; |
| 40 | 43 | } |
| 41 | 44 | |
| 45 | + public static void execute(String tenantIdStr, Runnable runnable) { | |
| 46 | + if (ObjectUtil.isEmpty(tenantIdStr) | |
| 47 | + || Objects.equals(tenantIdStr, ProcessEngineConfiguration.NO_TENANT_ID)) { | |
| 48 | + runnable.run(); | |
| 49 | + } else { | |
| 50 | + Long tenantId = Long.valueOf(tenantIdStr); | |
| 51 | + TenantUtils.execute(tenantId, runnable); | |
| 52 | + } | |
| 53 | + } | |
| 54 | + | |
| 42 | 55 | // ========== Execution 相关的工具方法 ========== |
| 43 | 56 | |
| 44 | 57 | /** | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
| ... | ... | @@ -10,6 +10,7 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 10 | 10 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; |
| 11 | 11 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.RejectHandler; |
| 12 | 12 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; |
| 13 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; | |
| 13 | 14 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType; |
| 14 | 15 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; |
| 15 | 16 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; |
| ... | ... | @@ -25,7 +26,6 @@ import java.util.Objects; |
| 25 | 26 | |
| 26 | 27 | import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting; |
| 27 | 28 | import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler; |
| 28 | -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_TIMEOUT; | |
| 29 | 29 | import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; |
| 30 | 30 | import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType.REMINDER; |
| 31 | 31 | import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; |
| ... | ... | @@ -59,7 +59,6 @@ public class SimpleModelUtils { |
| 59 | 59 | */ |
| 60 | 60 | public static final String APPROVE_BY_RATIO_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances/nrOfInstances >= %s}"; |
| 61 | 61 | |
| 62 | - // TODO-DONE @jason:建议方法名,改成 buildBpmnModel | |
| 63 | 62 | // TODO @yunai:注释需要完善下; |
| 64 | 63 | |
| 65 | 64 | /** |
| ... | ... | @@ -339,32 +338,41 @@ public class SimpleModelUtils { |
| 339 | 338 | List<FlowElement> flowElements = new ArrayList<>(); |
| 340 | 339 | UserTask userTask = buildBpmnUserTask(node); |
| 341 | 340 | flowElements.add(userTask); |
| 341 | + | |
| 342 | + // 添加用户任务的 Timer Boundary Event, 用于任务的审批超时处理 | |
| 342 | 343 | if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) { |
| 343 | - // 添加用户任务的 Timer Boundary Event, 用于任务的超时处理 | |
| 344 | 344 | BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, node.getTimeoutHandler()); |
| 345 | 345 | flowElements.add(boundaryEvent); |
| 346 | 346 | } |
| 347 | 347 | return flowElements; |
| 348 | 348 | } |
| 349 | 349 | |
| 350 | + /** | |
| 351 | + * 添加 UserTask 用户的审批超时 BoundaryEvent 事件 | |
| 352 | + * | |
| 353 | + * @param userTask 审批任务 | |
| 354 | + * @param timeoutHandler 超时处理器 | |
| 355 | + * @return BoundaryEvent 超时事件 | |
| 356 | + */ | |
| 350 | 357 | private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, TimeoutHandler timeoutHandler) { |
| 351 | - // 定时器边界事件 | |
| 358 | + // 1.1 定时器边界事件 | |
| 352 | 359 | BoundaryEvent boundaryEvent = new BoundaryEvent(); |
| 353 | 360 | boundaryEvent.setId("Event-" + IdUtil.fastUUID()); |
| 354 | - // 设置关联的任务为不会被中断 | |
| 355 | - boundaryEvent.setCancelActivity(false); | |
| 361 | + boundaryEvent.setCancelActivity(false); // 设置关联的任务为不会被中断 | |
| 356 | 362 | boundaryEvent.setAttachedToRef(userTask); |
| 363 | + // 1.2 定义超时时间、最大提醒次数 | |
| 357 | 364 | TimerEventDefinition eventDefinition = new TimerEventDefinition(); |
| 358 | 365 | eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration()); |
| 359 | 366 | if (Objects.equals(REMINDER.getAction(), timeoutHandler.getAction()) && |
| 360 | 367 | timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) { |
| 361 | - // 最大提醒次数 | |
| 362 | - eventDefinition.setTimeCycle(String.format("R%d/%s", timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration())); | |
| 368 | + eventDefinition.setTimeCycle(String.format("R%d/%s", | |
| 369 | + timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration())); | |
| 363 | 370 | } |
| 364 | 371 | boundaryEvent.addEventDefinition(eventDefinition); |
| 365 | - // 添加定时器边界事件类型 | |
| 366 | - addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, USER_TASK_TIMEOUT.getType().toString()); | |
| 367 | - // 添加超时执行动作元素 | |
| 372 | + | |
| 373 | + // 2.1 添加定时器边界事件类型 | |
| 374 | + addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, BpmBoundaryEventType.USER_TASK_TIMEOUT.getType().toString()); | |
| 375 | + // 2.2 添加超时执行动作元素 | |
| 368 | 376 | addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_ACTION, StrUtil.toStringOrNull(timeoutHandler.getAction())); |
| 369 | 377 | return boundaryEvent; |
| 370 | 378 | } |
| ... | ... | @@ -448,8 +456,6 @@ public class SimpleModelUtils { |
| 448 | 456 | userTask.setDueDate(node.getTimeoutHandler().getTimeDuration()); |
| 449 | 457 | } |
| 450 | 458 | |
| 451 | - // TODO 芋艿 + jason:要不要基于服务任务,实现或签下的审批不通过?或者说,按比例审批 | |
| 452 | - | |
| 453 | 459 | // TODO @jason:addCandidateElements、processMultiInstanceLoopCharacteristics 建议一起搞哈? |
| 454 | 460 | // 添加候选人元素 |
| 455 | 461 | addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask); |
| ... | ... | @@ -461,10 +467,11 @@ public class SimpleModelUtils { |
| 461 | 467 | processMultiInstanceLoopCharacteristics(node.getApproveMethod(), node.getApproveRatio(), userTask); |
| 462 | 468 | // 添加任务被拒绝的处理元素 |
| 463 | 469 | addTaskRejectElements(node.getRejectHandler(), userTask); |
| 470 | + // 添加用户任务的审批人与发起人相同时的处理元素 | |
| 471 | + addAssignStartUserHandlerType(node.getAssignStartUserHandlerType(), userTask); | |
| 464 | 472 | return userTask; |
| 465 | 473 | } |
| 466 | 474 | |
| 467 | - | |
| 468 | 475 | private static void addTaskRejectElements(RejectHandler rejectHandler, UserTask userTask) { |
| 469 | 476 | if (rejectHandler == null) { |
| 470 | 477 | return; |
| ... | ... | @@ -473,6 +480,13 @@ public class SimpleModelUtils { |
| 473 | 480 | addExtensionElement(userTask, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId()); |
| 474 | 481 | } |
| 475 | 482 | |
| 483 | + private static void addAssignStartUserHandlerType(Integer assignStartUserHandlerType, UserTask userTask) { | |
| 484 | + if (assignStartUserHandlerType == null) { | |
| 485 | + return; | |
| 486 | + } | |
| 487 | + addExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE, assignStartUserHandlerType.toString()); | |
| 488 | + } | |
| 489 | + | |
| 476 | 490 | private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) { |
| 477 | 491 | BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod); |
| 478 | 492 | if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.RANDOM) { | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageService.java
| ... | ... | @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.bpm.service.message; |
| 3 | 3 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; |
| 4 | 4 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; |
| 5 | 5 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; |
| 6 | - | |
| 6 | +import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; | |
| 7 | 7 | import jakarta.validation.Valid; |
| 8 | 8 | |
| 9 | 9 | /** |
| ... | ... | @@ -36,4 +36,11 @@ public interface BpmMessageService { |
| 36 | 36 | */ |
| 37 | 37 | void sendMessageWhenTaskAssigned(@Valid BpmMessageSendWhenTaskCreatedReqDTO reqDTO); |
| 38 | 38 | |
| 39 | + /** | |
| 40 | + * 发送任务审批超时的消息 | |
| 41 | + * | |
| 42 | + * @param reqDTO 发送信息 | |
| 43 | + */ | |
| 44 | + void sendMessageWhenTaskTimeout(@Valid BpmMessageSendWhenTaskTimeoutReqDTO reqDTO); | |
| 45 | + | |
| 39 | 46 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java
| ... | ... | @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.bpm.enums.message.BpmMessageEnum; |
| 6 | 6 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; |
| 7 | 7 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; |
| 8 | 8 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; |
| 9 | +import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; | |
| 9 | 10 | import cn.iocoder.yudao.module.system.api.sms.SmsSendApi; |
| 10 | 11 | import lombok.extern.slf4j.Slf4j; |
| 11 | 12 | import org.springframework.stereotype.Service; |
| ... | ... | @@ -61,6 +62,16 @@ public class BpmMessageServiceImpl implements BpmMessageService { |
| 61 | 62 | BpmMessageEnum.TASK_ASSIGNED.getSmsTemplateCode(), templateParams)); |
| 62 | 63 | } |
| 63 | 64 | |
| 65 | + @Override | |
| 66 | + public void sendMessageWhenTaskTimeout(BpmMessageSendWhenTaskTimeoutReqDTO reqDTO) { | |
| 67 | + Map<String, Object> templateParams = new HashMap<>(); | |
| 68 | + templateParams.put("processInstanceName", reqDTO.getProcessInstanceName()); | |
| 69 | + templateParams.put("taskName", reqDTO.getTaskName()); | |
| 70 | + templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId())); | |
| 71 | + smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getAssigneeUserId(), | |
| 72 | + BpmMessageEnum.TASK_TIMEOUT.getSmsTemplateCode(), templateParams)); | |
| 73 | + } | |
| 74 | + | |
| 64 | 75 | private String getProcessInstanceDetailUrl(String taskId) { |
| 65 | 76 | return webProperties.getAdminUi().getUrl() + "/bpm/process-instance/detail?id=" + taskId; |
| 66 | 77 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/dto/BpmMessageSendWhenTaskTimeoutReqDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.service.message.dto; | |
| 2 | + | |
| 3 | +import jakarta.validation.constraints.NotEmpty; | |
| 4 | +import jakarta.validation.constraints.NotNull; | |
| 5 | +import lombok.Data; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * BPM 发送任务审批超时 Request DTO | |
| 9 | + */ | |
| 10 | +@Data | |
| 11 | +public class BpmMessageSendWhenTaskTimeoutReqDTO { | |
| 12 | + | |
| 13 | + /** | |
| 14 | + * 流程实例的编号 | |
| 15 | + */ | |
| 16 | + @NotEmpty(message = "流程实例的编号不能为空") | |
| 17 | + private String processInstanceId; | |
| 18 | + /** | |
| 19 | + * 流程实例的名字 | |
| 20 | + */ | |
| 21 | + @NotEmpty(message = "流程实例的名字不能为空") | |
| 22 | + private String processInstanceName; | |
| 23 | + | |
| 24 | + /** | |
| 25 | + * 流程任务的编号 | |
| 26 | + */ | |
| 27 | + @NotEmpty(message = "流程任务的编号不能为空") | |
| 28 | + private String taskId; | |
| 29 | + /** | |
| 30 | + * 流程任务的名字 | |
| 31 | + */ | |
| 32 | + @NotEmpty(message = "流程任务的名字不能为空") | |
| 33 | + private String taskName; | |
| 34 | + | |
| 35 | + /** | |
| 36 | + * 审批人的用户编号 | |
| 37 | + */ | |
| 38 | + @NotNull(message = "审批人的用户编号不能为空") | |
| 39 | + private Long assigneeUserId; | |
| 40 | + | |
| 41 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
| 1 | -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); } } | |
| 2 | 1 | \ No newline at end of file |
| 2 | +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } | |
| 3 | 3 | \ No newline at end of file | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
| ... | ... | @@ -207,4 +207,13 @@ public interface BpmTaskService { |
| 207 | 207 | */ |
| 208 | 208 | void processTaskAssigned(Task task); |
| 209 | 209 | |
| 210 | + /** | |
| 211 | + * 处理 Task 审批超时事件,可能会处理多个当前审批中的任务 | |
| 212 | + * | |
| 213 | + * @param processInstanceId 流程示例编号 | |
| 214 | + * @param taskDefineKey 任务 Key | |
| 215 | + * @param taskAction 处理类型 | |
| 216 | + */ | |
| 217 | + void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer taskAction); | |
| 218 | + | |
| 210 | 219 | } | ... | ... |
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.lang.Assert; | |
| 4 | 5 | import cn.hutool.core.util.*; |
| 6 | +import cn.hutool.extra.spring.SpringUtil; | |
| 5 | 7 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | 8 | import cn.iocoder.yudao.framework.common.util.date.DateUtils; |
| 7 | 9 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; |
| 10 | +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; | |
| 8 | 11 | import cn.iocoder.yudao.framework.common.util.object.PageUtils; |
| 9 | 12 | import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; |
| 10 | 13 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; |
| 11 | 14 | import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; |
| 15 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum; | |
| 12 | 16 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; |
| 17 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType; | |
| 13 | 18 | import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; |
| 14 | 19 | import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; |
| 15 | 20 | import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; |
| ... | ... | @@ -19,6 +24,9 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| 19 | 24 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; |
| 20 | 25 | import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; |
| 21 | 26 | import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; |
| 27 | +import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; | |
| 28 | +import cn.iocoder.yudao.module.system.api.dept.DeptApi; | |
| 29 | +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; | |
| 22 | 30 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 23 | 31 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 24 | 32 | import jakarta.annotation.Resource; |
| ... | ... | @@ -44,7 +52,6 @@ import org.springframework.stereotype.Service; |
| 44 | 52 | import org.springframework.transaction.annotation.Transactional; |
| 45 | 53 | import org.springframework.transaction.support.TransactionSynchronization; |
| 46 | 54 | import org.springframework.transaction.support.TransactionSynchronizationManager; |
| 47 | -import org.springframework.util.Assert; | |
| 48 | 55 | |
| 49 | 56 | import java.util.*; |
| 50 | 57 | import java.util.stream.Stream; |
| ... | ... | @@ -77,12 +84,14 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 77 | 84 | @Resource |
| 78 | 85 | private BpmProcessInstanceCopyService processInstanceCopyService; |
| 79 | 86 | @Resource |
| 80 | - private BpmModelService bpmModelService; | |
| 87 | + private BpmModelService modelService; | |
| 81 | 88 | @Resource |
| 82 | 89 | private BpmMessageService messageService; |
| 83 | 90 | |
| 84 | 91 | @Resource |
| 85 | 92 | private AdminUserApi adminUserApi; |
| 93 | + @Resource | |
| 94 | + private DeptApi deptApi; | |
| 86 | 95 | |
| 87 | 96 | // ========== Query 查询相关方法 ========== |
| 88 | 97 | |
| ... | ... | @@ -226,7 +235,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 226 | 235 | // 1.1 校验当前任务 task 存在 |
| 227 | 236 | Task task = validateTaskExist(id); |
| 228 | 237 | // 1.2 根据流程定义获取流程模型信息 |
| 229 | - BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); | |
| 238 | + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); | |
| 230 | 239 | FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); |
| 231 | 240 | if (source == null) { |
| 232 | 241 | throw exception(TASK_NOT_EXISTS); |
| ... | ... | @@ -496,12 +505,12 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 496 | 505 | } |
| 497 | 506 | |
| 498 | 507 | // 3. 根据不同的 RejectHandler 处理策略 |
| 499 | - BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); | |
| 500 | - FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); | |
| 508 | + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); | |
| 509 | + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); | |
| 501 | 510 | // 3.1 情况一:驳回到指定的任务节点 |
| 502 | - BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(flowElement); | |
| 511 | + BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(userTaskElement); | |
| 503 | 512 | if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) { |
| 504 | - String returnTaskId = BpmnModelUtils.parseReturnTaskId(flowElement); | |
| 513 | + String returnTaskId = BpmnModelUtils.parseReturnTaskId(userTaskElement); | |
| 505 | 514 | Assert.notNull(returnTaskId, "回退的节点不能为空"); |
| 506 | 515 | returnTask(userId, new BpmTaskReturnReqVO().setId(task.getId()) |
| 507 | 516 | .setTargetTaskDefinitionKey(returnTaskId).setReason(reqVO.getReason())); |
| ... | ... | @@ -560,7 +569,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 560 | 569 | */ |
| 561 | 570 | private FlowElement validateTargetTaskCanReturn(String sourceKey, String targetKey, String processDefinitionId) { |
| 562 | 571 | // 1.1 获取流程模型信息 |
| 563 | - BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId); | |
| 572 | + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId); | |
| 564 | 573 | // 1.3 获取当前任务节点元素 |
| 565 | 574 | FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey); |
| 566 | 575 | // 1.3 获取跳转的节点元素 |
| ... | ... | @@ -688,7 +697,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 688 | 697 | }); |
| 689 | 698 | |
| 690 | 699 | // 2. 终止流程 |
| 691 | - BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(taskList.get(0).getProcessDefinitionId()); | |
| 700 | + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(taskList.get(0).getProcessDefinitionId()); | |
| 692 | 701 | List<String> activityIds = CollUtil.newArrayList(convertSet(taskList, Task::getTaskDefinitionKey)); |
| 693 | 702 | EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); |
| 694 | 703 | Assert.notNull(endEvent, "结束节点不能未空"); |
| ... | ... | @@ -913,16 +922,110 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 913 | 922 | @Override |
| 914 | 923 | public void afterCommit() { |
| 915 | 924 | if (StrUtil.isEmpty(task.getAssignee())) { |
| 925 | + log.error("[processTaskAssigned][taskId({}) 没有分配到负责人]", task.getId()); | |
| 916 | 926 | return; |
| 917 | 927 | } |
| 918 | 928 | ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); |
| 919 | - if (processInstance != null) { | |
| 920 | - AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); | |
| 921 | - messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); | |
| 929 | + if (processInstance == null) { | |
| 930 | + log.error("[processTaskAssigned][taskId({}) 没有找到流程实例]", task.getId()); | |
| 931 | + return; | |
| 932 | + } | |
| 933 | + | |
| 934 | + // 审批人与提交人为同一人时,根据策略进行处理 | |
| 935 | + if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) { | |
| 936 | + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); | |
| 937 | + if (bpmnModel == null) { | |
| 938 | + log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId()); | |
| 939 | + return; | |
| 940 | + } | |
| 941 | + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); | |
| 942 | + Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement); | |
| 943 | + | |
| 944 | + // 情况一:自动跳过 | |
| 945 | + if (ObjectUtils.equalsAny(assignStartUserHandlerType, | |
| 946 | + BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { | |
| 947 | + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO() | |
| 948 | + .setId(task.getId()).setReason("审批人与提交人为同一人时,自动通过")); | |
| 949 | + return; | |
| 950 | + } | |
| 951 | + // 情况二:转交给部门负责人审批 | |
| 952 | + if (ObjectUtils.equalsAny(assignStartUserHandlerType, | |
| 953 | + BpmUserTaskAssignStartUserHandlerTypeEnum.ASSIGN_DEPT_LEADER.getType())) { | |
| 954 | + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); | |
| 955 | + Assert.notNull(startUser, "提交人({})信息为空", processInstance.getStartUserId()); | |
| 956 | + DeptRespDTO dept = startUser.getDeptId() != null ? deptApi.getDept(startUser.getDeptId()) : null; | |
| 957 | + Assert.notNull(dept, "提交人({})部门({})信息为空", processInstance.getStartUserId(), startUser.getDeptId()); | |
| 958 | + // 找不到部门负责人的情况下,自动审批通过 | |
| 959 | + // noinspection DataFlowIssue | |
| 960 | + if (dept.getLeaderUserId() == null) { | |
| 961 | + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO() | |
| 962 | + .setId(task.getId()).setReason("审批人与提交人为同一人时,找不到部门负责人,自动通过")); | |
| 963 | + return; | |
| 964 | + } | |
| 965 | + // 找得到部门负责人的情况下,修改负责人 | |
| 966 | + if (ObjectUtil.notEqual(dept.getLeaderUserId(), startUser.getId())) { | |
| 967 | + getSelf().transferTask(Long.valueOf(task.getAssignee()), new BpmTaskTransferReqVO() | |
| 968 | + .setId(task.getId()).setAssigneeUserId(dept.getLeaderUserId()) | |
| 969 | + .setReason("审批人与提交人为同一人时,转交给部门负责人审批")); | |
| 970 | + return; | |
| 971 | + } | |
| 972 | + // 如果部门负责人是自己,还是自己审批吧~ | |
| 973 | + } | |
| 922 | 974 | } |
| 975 | + | |
| 976 | + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); | |
| 977 | + messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); | |
| 923 | 978 | } |
| 924 | 979 | |
| 925 | 980 | }); |
| 926 | 981 | } |
| 927 | 982 | |
| 983 | + @Override | |
| 984 | + @Transactional(rollbackFor = Exception.class) | |
| 985 | + public void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer taskAction) { | |
| 986 | + ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); | |
| 987 | + if (processInstance == null) { | |
| 988 | + log.error("[processTaskTimeout][processInstanceId({}) 没有找到流程实例]", processInstanceId); | |
| 989 | + return; | |
| 990 | + } | |
| 991 | + List<Task> taskList = getRunningTaskListByProcessInstanceId(processInstanceId, true, taskDefineKey); | |
| 992 | + // TODO 优化:未来需要考虑加签的情况 | |
| 993 | + if (CollUtil.isEmpty(taskList)) { | |
| 994 | + log.error("[processTaskTimeout][processInstanceId({}) 定义Key({}) 没有找到任务]", processInstanceId, taskDefineKey); | |
| 995 | + return; | |
| 996 | + } | |
| 997 | + | |
| 998 | + taskList.forEach(task -> FlowableUtils.execute(task.getTenantId(), () -> { | |
| 999 | + // 情况一:自动提醒 | |
| 1000 | + if (Objects.equals(taskAction, BpmUserTaskTimeoutHandlerType.REMINDER.getAction())) { | |
| 1001 | + messageService.sendMessageWhenTaskTimeout(new BpmMessageSendWhenTaskTimeoutReqDTO() | |
| 1002 | + .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName()) | |
| 1003 | + .setTaskId(task.getId()).setTaskName(task.getName()).setAssigneeUserId(Long.parseLong(task.getAssignee()))); | |
| 1004 | + return; | |
| 1005 | + } | |
| 1006 | + | |
| 1007 | + // 情况二:自动同意 | |
| 1008 | + if (Objects.equals(taskAction, BpmUserTaskTimeoutHandlerType.APPROVE.getAction())) { | |
| 1009 | + approveTask(Long.parseLong(task.getAssignee()), | |
| 1010 | + new BpmTaskApproveReqVO().setId(task.getId()).setReason("超时系统自动同意")); | |
| 1011 | + return; | |
| 1012 | + } | |
| 1013 | + | |
| 1014 | + // 情况三:自动拒绝 | |
| 1015 | + if (Objects.equals(taskAction, BpmUserTaskTimeoutHandlerType.REJECT.getAction())) { | |
| 1016 | + rejectTask(Long.parseLong(task.getAssignee()), | |
| 1017 | + new BpmTaskRejectReqVO().setId(task.getId()).setReason("超时系统自动拒绝")); | |
| 1018 | + } | |
| 1019 | + })); | |
| 1020 | + } | |
| 1021 | + | |
| 1022 | + /** | |
| 1023 | + * 获得自身的代理对象,解决 AOP 生效问题 | |
| 1024 | + * | |
| 1025 | + * @return 自己 | |
| 1026 | + */ | |
| 1027 | + private BpmTaskServiceImpl getSelf() { | |
| 1028 | + return SpringUtil.getBean(getClass()); | |
| 1029 | + } | |
| 1030 | + | |
| 928 | 1031 | } | ... | ... |