فهرست منبع

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

YunaiV 9 ماه پیش
والد
کامیت
fe3ca8deba
17فایلهای تغییر یافته به همراه178 افزوده شده و 217 حذف شده
  1. 1 0
      yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java
  2. 1 0
      yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerType.java
  3. 2 1
      yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/message/BpmMessageEnum.java
  4. 3 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
  5. 40 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
  6. 0 111
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java
  7. 0 42
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/consumer/task/SysNotifyTodoTaskReminderConsumer.java
  8. 0 34
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/message/task/TodoTaskReminderMessage.java
  9. 0 27
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/producer/task/TodoTaskReminderProducer.java
  10. 13 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java
  11. 8 1
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
  12. 8 1
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageService.java
  13. 11 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java
  14. 41 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/dto/BpmMessageSendWhenTaskTimeoutReqDTO.java
  15. 0 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
  16. 9 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
  17. 41 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java

+ 1 - 0
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;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
+// TODO @jason:这个是不是可以去掉了哈?
 /**
  * BPM 边界事件 (boundary event) 自定义类型枚举
  *

+ 1 - 0
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 {
     APPROVE(2, "自动同意"),
     REJECT(3, "自动拒绝");
 
+    // TODO @jason:type 是不是更合适哈;
     private final Integer action;
     private final String name;
 

+ 2 - 1
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 {
 
     PROCESS_INSTANCE_APPROVE("bpm_process_instance_approve"), // 流程任务被审批通过时,发送给申请人
     PROCESS_INSTANCE_REJECT("bpm_process_instance_reject"), // 流程任务被审批不通过时,发送给申请人
-    TASK_ASSIGNED("bpm_task_assigned"); // 任务被分配时,发送给审批人
+    TASK_ASSIGNED("bpm_task_assigned"), // 任务被分配时,发送给审批人
+    TASK_TIMEOUT("bpm_task_timeout"); // 任务审批超时时,发送给审批人
 
     /**
      * 短信模板的标识

+ 3 - 0
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 {
         private String returnNodeId;
     }
 
+    // TODO @芋艿:参数校验
     @Data
     @Schema(description = "审批节点超时处理策略")
     public static class TimeoutHandler {
@@ -103,6 +104,7 @@ public class BpmSimpleModelNodeVO {
         @Schema(description = "是否开启超时处理", example = "false")
         private Boolean enable;
 
+        // TODO @jason:type 是不是更合适哈;
         @Schema(description = "任务超时未处理的行为", example = "1")
         @InEnum(BpmUserTaskTimeoutHandlerType.class)
         private Integer action;
@@ -112,6 +114,7 @@ public class BpmSimpleModelNodeVO {
 
         @Schema(description = "最大提醒次数", example = "1")
         private Integer maxRemindCount;
+
     }
 
     @Data

+ 40 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java

@@ -1,17 +1,27 @@
 package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
 import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService;
 import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
 import com.google.common.collect.ImmutableSet;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
+import org.flowable.bpmn.model.BoundaryEvent;
+import org.flowable.bpmn.model.BpmnModel;
+import org.flowable.bpmn.model.FlowElement;
 import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
 import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
 import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener;
 import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent;
 import org.flowable.engine.history.HistoricActivityInstance;
+import org.flowable.job.api.Job;
 import org.flowable.task.api.Task;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Component;
@@ -28,6 +38,9 @@ import java.util.Set;
 @Slf4j
 public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
 
+    @Resource
+    @Lazy // 延迟加载,避免循环依赖
+    private BpmModelService modelService;
     @Resource
     @Lazy // 解决循环依赖
     private BpmTaskService taskService;
@@ -40,6 +53,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
             .add(FlowableEngineEventType.TASK_ASSIGNED)
 //            .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了。
             .add(FlowableEngineEventType.ACTIVITY_CANCELLED)
+            .add(FlowableEngineEventType.TIMER_FIRED) // 监听审批超时
             .build();
 
     public BpmTaskEventListener() {
@@ -72,4 +86,30 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
         });
     }
 
+    @Override
+    protected void timerFired(FlowableEngineEntityEvent event) {
+        // 1.1 只处理 BoundaryEvent 边界计时时间
+        String processDefinitionId = event.getProcessDefinitionId();
+        BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId);
+        Job entity = (Job) event.getEntity();
+        FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId());
+        if (!(element instanceof BoundaryEvent)) {
+            return;
+        }
+        // 1.2 判断是否为超时处理
+        BoundaryEvent boundaryEvent = (BoundaryEvent) element;
+        String boundaryEventType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent,
+                BpmnModelConstants.BOUNDARY_EVENT_TYPE);
+        BpmBoundaryEventType bpmTimerBoundaryEventType = BpmBoundaryEventType.typeOf(NumberUtils.parseInt(boundaryEventType));
+        if (ObjectUtil.notEqual(bpmTimerBoundaryEventType, BpmBoundaryEventType.USER_TASK_TIMEOUT)) {
+            return;
+        }
+
+        // 2. 处理超时
+        String timeoutAction = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent,
+                BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION);
+        String taskKey = boundaryEvent.getAttachedToRefId();
+        taskService.processTaskTimeout(event.getProcessInstanceId(), taskKey, NumberUtils.parseInt(timeoutAction));
+    }
+
 }

+ 0 - 111
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java

@@ -1,111 +0,0 @@
-package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
-
-import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
-import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
-import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO;
-import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRejectReqVO;
-import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType;
-import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType;
-import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
-import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage;
-import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.producer.task.TodoTaskReminderProducer;
-import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
-import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
-import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
-import com.google.common.collect.ImmutableSet;
-import jakarta.annotation.Resource;
-import lombok.extern.slf4j.Slf4j;
-import org.flowable.bpmn.model.BoundaryEvent;
-import org.flowable.bpmn.model.BpmnModel;
-import org.flowable.bpmn.model.FlowElement;
-import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
-import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
-import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener;
-import org.flowable.job.api.Job;
-import org.flowable.task.api.Task;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.stereotype.Component;
-
-import java.util.List;
-import java.util.Set;
-
-// TODO @芋艿:这块需要仔细再瞅瞅
-/**
- * 监听定时器触发事件
- *
- * @author jason
- */
-@Component
-@Slf4j
-public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListener {
-
-    @Resource
-    @Lazy // 延迟加载,避免循环依赖
-    private BpmModelService bpmModelService;
-    @Resource
-    @Lazy // 延迟加载,避免循环依赖
-    private BpmTaskService bpmTaskService;
-
-    @Resource
-    private TodoTaskReminderProducer todoTaskReminderProducer;
-
-    public static final Set<FlowableEngineEventType> TIME_EVENTS = ImmutableSet.<FlowableEngineEventType>builder()
-            .add(FlowableEngineEventType.TIMER_FIRED)
-            .build();
-
-    public BpmTimerFiredEventListener() {
-        super(TIME_EVENTS);
-    }
-
-    @Override
-    protected void timerFired(FlowableEngineEntityEvent event) {
-        String processDefinitionId = event.getProcessDefinitionId();
-        BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId);
-        Job entity = (Job) event.getEntity();
-        FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId());
-        // 如果是定时器边界事件
-        if (element instanceof BoundaryEvent) {
-            BoundaryEvent boundaryEvent = (BoundaryEvent) element;
-            String boundaryEventType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.BOUNDARY_EVENT_TYPE);
-            BpmBoundaryEventType bpmTimerBoundaryEventType = BpmBoundaryEventType.typeOf(NumberUtils.parseInt(boundaryEventType));
-            // 类型为用户任务超时未处理的情况
-            if (bpmTimerBoundaryEventType == BpmBoundaryEventType.USER_TASK_TIMEOUT) {
-                String timeoutAction = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION);
-                userTaskTimeoutHandler(event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), NumberUtils.parseInt(timeoutAction));
-            }
-        }
-    }
-
-    private void userTaskTimeoutHandler(String processInstanceId, String taskDefKey, Integer timeoutAction) {
-        BpmUserTaskTimeoutHandlerType userTaskTimeoutAction = BpmUserTaskTimeoutHandlerType.typeOf(timeoutAction);
-        if (userTaskTimeoutAction != null) {
-            // 查询超时未处理的任务 TODO 加签的情况会不会有问题 ???
-            List<Task> taskList = bpmTaskService.getRunningTaskListByProcessInstanceId(processInstanceId, true, taskDefKey);
-            taskList.forEach(task -> {
-                // 自动提醒
-                if (userTaskTimeoutAction == BpmUserTaskTimeoutHandlerType.REMINDER) {
-                    TodoTaskReminderMessage message = new TodoTaskReminderMessage().setTenantId(Long.parseLong(task.getTenantId()))
-                            .setUserId(Long.parseLong(task.getAssignee())).setTaskName(task.getName());
-                    todoTaskReminderProducer.sendReminderMessage(message);
-                }
-                // 自动同意
-                if (userTaskTimeoutAction == BpmUserTaskTimeoutHandlerType.APPROVE) {
-                    // TODO @芋艿 这个上下文如何清除呢? 任务通过后, BpmProcessInstanceEventListener 会有回调
-                    TenantContextHolder.setTenantId(Long.parseLong(task.getTenantId()));
-                    TenantContextHolder.setIgnore(false);
-                    BpmTaskApproveReqVO req = new BpmTaskApproveReqVO().setId(task.getId())
-                            .setReason("超时系统自动同意");
-                    bpmTaskService.approveTask(Long.parseLong(task.getAssignee()), req);
-                }
-                // 自动拒绝
-                if (userTaskTimeoutAction == BpmUserTaskTimeoutHandlerType.REJECT) {
-                    // TODO  @芋艿 这个上下文如何清除呢? 任务拒绝后, BpmProcessInstanceEventListener 会有回调
-                    TenantContextHolder.setTenantId(Long.parseLong(task.getTenantId()));
-                    TenantContextHolder.setIgnore(false);
-                    BpmTaskRejectReqVO req = new BpmTaskRejectReqVO().setId(task.getId()).setReason("超时系统自动拒绝");
-                    bpmTaskService.rejectTask(Long.parseLong(task.getAssignee()), req);
-                }
-            });
-        }
-    }
-}

+ 0 - 42
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/consumer/task/SysNotifyTodoTaskReminderConsumer.java

@@ -1,42 +0,0 @@
-package cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.consumer.task;
-
-import cn.hutool.core.map.MapUtil;
-import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
-import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage;
-import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
-import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
-import jakarta.annotation.Resource;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.context.event.EventListener;
-import org.springframework.scheduling.annotation.Async;
-import org.springframework.stereotype.Component;
-
-import java.util.Map;
-
-/**
- *  待办任务提醒 - 站内信的消费者
- *
- * @author jason
- */
-@Component
-@Slf4j
-public class SysNotifyTodoTaskReminderConsumer {
-
-    private static final String TASK_REMIND_TEMPLATE_CODE = "user_task_remind";
-
-    @Resource
-    private NotifyMessageSendApi notifyMessageSendApi;
-
-    @EventListener
-    @Async
-    public void onMessage(TodoTaskReminderMessage message) {
-        log.info("站内信消费者接收到消息 [消息内容({})] ", message);
-        TenantUtils.execute(message.getTenantId(), ()-> {
-            Map<String,Object> templateParams = MapUtil.newHashMap();
-            templateParams.put("name", message.getTaskName());
-            NotifySendSingleToUserReqDTO req = new NotifySendSingleToUserReqDTO().setUserId(message.getUserId())
-                    .setTemplateCode(TASK_REMIND_TEMPLATE_CODE).setTemplateParams(templateParams);
-            notifyMessageSendApi.sendSingleMessageToAdmin(req);
-        });
-    }
-}

+ 0 - 34
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/message/task/TodoTaskReminderMessage.java

@@ -1,34 +0,0 @@
-package cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task;
-
-import jakarta.validation.constraints.NotEmpty;
-import jakarta.validation.constraints.NotNull;
-import lombok.Data;
-
-/**
- * 待办任务提醒消息
- *
- * @author jason
- */
-@Data
-public class TodoTaskReminderMessage {
-
-    /**
-     * 租户 Id
-     */
-    @NotNull(message = "租户 Id 不能未空")
-    private Long tenantId;
-
-    /**
-     * 用户Id
-     */
-    @NotNull(message = "用户 Id 不能未空")
-    private Long userId;
-
-    /**
-     * 任务名称
-     */
-    @NotEmpty(message = "任务名称不能未空")
-    private String taskName;
-
-    // TODO 暂时只有站内信通知. 后面可以增加
-}

+ 0 - 27
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/producer/task/TodoTaskReminderProducer.java

@@ -1,27 +0,0 @@
-package cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.producer.task;
-
-import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage;
-import jakarta.annotation.Resource;
-import jakarta.validation.Valid;
-import org.springframework.context.ApplicationContext;
-import org.springframework.stereotype.Component;
-import org.springframework.validation.annotation.Validated;
-
-// TODO @jason:建议直接调用 BpmMessageService 哈;更简化一点~
-/**
- * 待办任务提醒 Producer
- *
- * @author jason
- */
-@Component
-@Validated
-public class TodoTaskReminderProducer {
-
-    @Resource
-    private ApplicationContext applicationContext;
-
-    public void sendReminderMessage(@Valid TodoTaskReminderMessage message) {
-        applicationContext.publishEvent(message);
-    }
-
-}

+ 13 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java

@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
 
+import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
+import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants;
 import org.flowable.common.engine.api.delegate.Expression;
 import org.flowable.common.engine.api.variable.VariableContainer;
@@ -16,6 +18,7 @@ import org.flowable.task.api.TaskInfo;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Flowable 相关的工具方法
@@ -39,6 +42,16 @@ public class FlowableUtils {
         return tenantId != null ? String.valueOf(tenantId) : ProcessEngineConfiguration.NO_TENANT_ID;
     }
 
+    public static void execute(String tenantIdStr, Runnable runnable) {
+        if (ObjectUtil.isEmpty(tenantIdStr)
+                || Objects.equals(tenantIdStr, ProcessEngineConfiguration.NO_TENANT_ID)) {
+            runnable.run();
+        } else {
+            Long tenantId = Long.valueOf(tenantIdStr);
+            TenantUtils.execute(tenantId, runnable);
+        }
+    }
+
     // ========== Execution 相关的工具方法 ==========
 
     /**

+ 8 - 1
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 {
      */
     public static final String APPROVE_BY_RATIO_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances/nrOfInstances >= %s}";
 
-    // TODO-DONE @jason:建议方法名,改成 buildBpmnModel
     // TODO @yunai:注释需要完善下;
 
     /**
@@ -347,6 +346,13 @@ public class SimpleModelUtils {
         return flowElements;
     }
 
+    /**
+     * 添加 UserTask 用户审批的 BoundaryEvent 超时事件
+     *
+     * @param userTask 审批任务
+     * @param timeoutHandler 超时处理器
+     * @return
+     */
     private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, TimeoutHandler timeoutHandler) {
         // 定时器边界事件
         BoundaryEvent boundaryEvent = new BoundaryEvent();
@@ -362,6 +368,7 @@ public class SimpleModelUtils {
             eventDefinition.setTimeCycle(String.format("R%d/%s", timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration()));
         }
         boundaryEvent.addEventDefinition(eventDefinition);
+
         // 添加定时器边界事件类型
         addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, USER_TASK_TIMEOUT.getType().toString());
         // 添加超时执行动作元素

+ 8 - 1
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;
 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO;
 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO;
 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
-
+import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO;
 import jakarta.validation.Valid;
 
 /**
@@ -36,4 +36,11 @@ public interface BpmMessageService {
      */
     void sendMessageWhenTaskAssigned(@Valid BpmMessageSendWhenTaskCreatedReqDTO reqDTO);
 
+    /**
+     * 发送任务审批超时的消息
+     *
+     * @param reqDTO 发送信息
+     */
+    void sendMessageWhenTaskTimeout(@Valid BpmMessageSendWhenTaskTimeoutReqDTO reqDTO);
+
 }

+ 11 - 0
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;
 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO;
 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO;
 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
+import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO;
 import cn.iocoder.yudao.module.system.api.sms.SmsSendApi;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
@@ -61,6 +62,16 @@ public class BpmMessageServiceImpl implements BpmMessageService {
                 BpmMessageEnum.TASK_ASSIGNED.getSmsTemplateCode(), templateParams));
     }
 
+    @Override
+    public void sendMessageWhenTaskTimeout(BpmMessageSendWhenTaskTimeoutReqDTO reqDTO) {
+        Map<String, Object> templateParams = new HashMap<>();
+        templateParams.put("processInstanceName", reqDTO.getProcessInstanceName());
+        templateParams.put("taskName", reqDTO.getTaskName());
+        templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId()));
+        smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getAssigneeUserId(),
+                BpmMessageEnum.TASK_TIMEOUT.getSmsTemplateCode(), templateParams));
+    }
+
     private String getProcessInstanceDetailUrl(String taskId) {
         return webProperties.getAdminUi().getUrl() + "/bpm/process-instance/detail?id=" + taskId;
     }

+ 41 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/dto/BpmMessageSendWhenTaskTimeoutReqDTO.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.bpm.service.message.dto;
+
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * BPM 发送任务审批超时 Request DTO
+ */
+@Data
+public class BpmMessageSendWhenTaskTimeoutReqDTO {
+
+    /**
+     * 流程实例的编号
+     */
+    @NotEmpty(message = "流程实例的编号不能为空")
+    private String processInstanceId;
+    /**
+     * 流程实例的名字
+     */
+    @NotEmpty(message = "流程实例的名字不能为空")
+    private String processInstanceName;
+
+    /**
+     * 流程任务的编号
+     */
+    @NotEmpty(message = "流程任务的编号不能为空")
+    private String taskId;
+    /**
+     * 流程任务的名字
+     */
+    @NotEmpty(message = "流程任务的名字不能为空")
+    private String taskName;
+
+    /**
+     * 审批人的用户编号
+     */
+    @NotNull(message = "审批人的用户编号不能为空")
+    private Long assigneeUserId;
+
+}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java


+ 9 - 0
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 {
      */
     void processTaskAssigned(Task task);
 
+    /**
+     * 处理 Task 审批超时事件,可能会处理多个当前审批中的任务
+     *
+     * @param processInstanceId 流程示例编号
+     * @param taskDefineKey 任务 Key
+     * @param taskAction 处理类型
+     */
+    void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer taskAction);
+
 }

+ 41 - 0
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;
 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
 import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert;
 import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType;
 import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum;
 import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum;
@@ -19,6 +20,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
 import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
 import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
+import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import jakarta.annotation.Resource;
@@ -925,4 +927,43 @@ public class BpmTaskServiceImpl implements BpmTaskService {
         });
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer taskAction) {
+        ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
+        if (processInstance == null) {
+            log.error("[processTaskTimeout][processInstanceId({}) 没有找到流程实例]", processInstanceId);
+            return;
+        }
+        List<Task> taskList = getRunningTaskListByProcessInstanceId(processInstanceId, true, taskDefineKey);
+        // TODO 优化:未来需要考虑加签的情况
+        if (CollUtil.isEmpty(taskList)) {
+            log.error("[processTaskTimeout][processInstanceId({}) 定义Key({}) 没有找到任务]", processInstanceId, taskDefineKey);
+            return;
+        }
+
+        taskList.forEach(task -> FlowableUtils.execute(task.getTenantId(), () -> {
+            // 情况一:自动提醒
+            if (Objects.equals(taskAction, BpmUserTaskTimeoutHandlerType.REMINDER.getAction())) {
+                messageService.sendMessageWhenTaskTimeout(new BpmMessageSendWhenTaskTimeoutReqDTO()
+                        .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName())
+                        .setTaskId(task.getId()).setTaskName(task.getName()).setAssigneeUserId(Long.parseLong(task.getAssignee())));
+                return;
+            }
+
+            // 情况二:自动同意
+            if (Objects.equals(taskAction, BpmUserTaskTimeoutHandlerType.APPROVE.getAction())) {
+                approveTask(Long.parseLong(task.getAssignee()),
+                        new BpmTaskApproveReqVO().setId(task.getId()).setReason("超时系统自动同意"));
+                return;
+            }
+
+            // 情况三:自动拒绝
+            if (Objects.equals(taskAction, BpmUserTaskTimeoutHandlerType.REJECT.getAction())) {
+                rejectTask(Long.parseLong(task.getAssignee()),
+                        new BpmTaskRejectReqVO().setId(task.getId()).setReason("超时系统自动拒绝"));
+            }
+        }));
+    }
+
 }

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است