mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-11-01 02:38:43 +08:00 
			
		
		
		
	【代码优化】BPM:审批超时提醒的实现
This commit is contained in:
		| @@ -4,6 +4,7 @@ import cn.hutool.core.util.ArrayUtil; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Getter; | ||||
|  | ||||
| // TODO @jason:这个是不是可以去掉了哈? | ||||
| /** | ||||
|  * BPM 边界事件 (boundary event) 自定义类型枚举 | ||||
|  * | ||||
|   | ||||
| @@ -20,6 +20,7 @@ public enum BpmUserTaskTimeoutHandlerType implements IntArrayValuable { | ||||
|     APPROVE(2, "自动同意"), | ||||
|     REJECT(3, "自动拒绝"); | ||||
|  | ||||
|     // TODO @jason:type 是不是更合适哈; | ||||
|     private final Integer action; | ||||
|     private final String name; | ||||
|  | ||||
|   | ||||
| @@ -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"); // 任务审批超时时,发送给审批人 | ||||
|  | ||||
|     /** | ||||
|      * 短信模板的标识 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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)); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @@ -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 暂时只有站内信通知. 后面可以增加 | ||||
| } | ||||
| @@ -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); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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 相关的工具方法 ========== | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -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()); | ||||
|         // 添加超时执行动作元素 | ||||
|   | ||||
| @@ -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); | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
| } | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -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); | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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("超时系统自动拒绝")); | ||||
|             } | ||||
|         })); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV