Commit fe3ca8deba506690d60ac771101807c16dc562b9

Authored by YunaiV
1 parent ae7dbd3c

【代码优化】BPM:审批超时提醒的实现

Showing 17 changed files with 179 additions and 218 deletions
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java
... ... @@ -4,6 +4,7 @@ import cn.hutool.core.util.ArrayUtil;
4 4 import lombok.AllArgsConstructor;
5 5 import lombok.Getter;
6 6  
  7 +// TODO @jason:这个是不是可以去掉了哈?
7 8 /**
8 9 * BPM 边界事件 (boundary event) 自定义类型枚举
9 10 *
... ...
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerType.java
... ... @@ -20,6 +20,7 @@ public enum BpmUserTaskTimeoutHandlerType implements IntArrayValuable {
20 20 APPROVE(2, "自动同意"),
21 21 REJECT(3, "自动拒绝");
22 22  
  23 + // TODO @jason:type 是不是更合适哈;
23 24 private final Integer action;
24 25 private final String name;
25 26  
... ...
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
... ... @@ -96,6 +96,7 @@ public class BpmSimpleModelNodeVO {
96 96 private String returnNodeId;
97 97 }
98 98  
  99 + // TODO @芋艿:参数校验
99 100 @Data
100 101 @Schema(description = "审批节点超时处理策略")
101 102 public static class TimeoutHandler {
... ... @@ -103,6 +104,7 @@ public class BpmSimpleModelNodeVO {
103 104 @Schema(description = "是否开启超时处理", example = "false")
104 105 private Boolean enable;
105 106  
  107 + // TODO @jason:type 是不是更合适哈;
106 108 @Schema(description = "任务超时未处理的行为", example = "1")
107 109 @InEnum(BpmUserTaskTimeoutHandlerType.class)
108 110 private Integer action;
... ... @@ -112,6 +114,7 @@ public class BpmSimpleModelNodeVO {
112 114  
113 115 @Schema(description = "最大提醒次数", example = "1")
114 116 private Integer maxRemindCount;
  117 +
115 118 }
116 119  
117 120 @Data
... ...
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/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
... ... @@ -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 /**
... ... @@ -347,6 +346,13 @@ public class SimpleModelUtils {
347 346 return flowElements;
348 347 }
349 348  
  349 + /**
  350 + * 添加 UserTask 用户审批的 BoundaryEvent 超时事件
  351 + *
  352 + * @param userTask 审批任务
  353 + * @param timeoutHandler 超时处理器
  354 + * @return
  355 + */
350 356 private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, TimeoutHandler timeoutHandler) {
351 357 // 定时器边界事件
352 358 BoundaryEvent boundaryEvent = new BoundaryEvent();
... ... @@ -362,6 +368,7 @@ public class SimpleModelUtils {
362 368 eventDefinition.setTimeCycle(String.format("R%d/%s", timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration()));
363 369 }
364 370 boundaryEvent.addEventDefinition(eventDefinition);
  371 +
365 372 // 添加定时器边界事件类型
366 373 addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, USER_TASK_TIMEOUT.getType().toString());
367 374 // 添加超时执行动作元素
... ...
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
... ... @@ -10,6 +10,7 @@ import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
10 10 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
11 11 import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert;
12 12 import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType;
  13 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType;
13 14 import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum;
14 15 import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
15 16 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum;
... ... @@ -19,6 +20,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
19 20 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
20 21 import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
21 22 import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
  23 +import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO;
22 24 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
23 25 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
24 26 import jakarta.annotation.Resource;
... ... @@ -925,4 +927,43 @@ public class BpmTaskServiceImpl implements BpmTaskService {
925 927 });
926 928 }
927 929  
  930 + @Override
  931 + @Transactional(rollbackFor = Exception.class)
  932 + public void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer taskAction) {
  933 + ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
  934 + if (processInstance == null) {
  935 + log.error("[processTaskTimeout][processInstanceId({}) 没有找到流程实例]", processInstanceId);
  936 + return;
  937 + }
  938 + List<Task> taskList = getRunningTaskListByProcessInstanceId(processInstanceId, true, taskDefineKey);
  939 + // TODO 优化:未来需要考虑加签的情况
  940 + if (CollUtil.isEmpty(taskList)) {
  941 + log.error("[processTaskTimeout][processInstanceId({}) 定义Key({}) 没有找到任务]", processInstanceId, taskDefineKey);
  942 + return;
  943 + }
  944 +
  945 + taskList.forEach(task -> FlowableUtils.execute(task.getTenantId(), () -> {
  946 + // 情况一:自动提醒
  947 + if (Objects.equals(taskAction, BpmUserTaskTimeoutHandlerType.REMINDER.getAction())) {
  948 + messageService.sendMessageWhenTaskTimeout(new BpmMessageSendWhenTaskTimeoutReqDTO()
  949 + .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName())
  950 + .setTaskId(task.getId()).setTaskName(task.getName()).setAssigneeUserId(Long.parseLong(task.getAssignee())));
  951 + return;
  952 + }
  953 +
  954 + // 情况二:自动同意
  955 + if (Objects.equals(taskAction, BpmUserTaskTimeoutHandlerType.APPROVE.getAction())) {
  956 + approveTask(Long.parseLong(task.getAssignee()),
  957 + new BpmTaskApproveReqVO().setId(task.getId()).setReason("超时系统自动同意"));
  958 + return;
  959 + }
  960 +
  961 + // 情况三:自动拒绝
  962 + if (Objects.equals(taskAction, BpmUserTaskTimeoutHandlerType.REJECT.getAction())) {
  963 + rejectTask(Long.parseLong(task.getAssignee()),
  964 + new BpmTaskRejectReqVO().setId(task.getId()).setReason("超时系统自动拒绝"));
  965 + }
  966 + }));
  967 + }
  968 +
928 969 }
... ...