Commit d34fef67dae7259e7a05c5f19778d097467ec0e4

Authored by jason
1 parent afad8ac6

仿钉钉流程设计- 审批节点超时处理

Showing 11 changed files with 417 additions and 24 deletions
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTimerBoundaryEventType.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.enums.definition;
  2 +
  3 +import cn.hutool.core.util.ArrayUtil;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +/**
  8 + * 定时器边界事件类型枚举
  9 + *
  10 + * @author jason
  11 + */
  12 +@Getter
  13 +@AllArgsConstructor
  14 +public enum BpmTimerBoundaryEventType {
  15 +
  16 + USER_TASK_TIMEOUT(1,"用户任务超时");
  17 +
  18 + private final Integer type;
  19 + private final String name;
  20 +
  21 + public static BpmTimerBoundaryEventType typeOf(Integer type) {
  22 + return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values());
  23 + }
  24 +}
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutActionEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.enums.definition;
  2 +
  3 +import cn.hutool.core.util.ArrayUtil;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +/**
  8 + * 用户任务超时处理执行动作枚举
  9 + *
  10 + * @author jason
  11 + */
  12 +@Getter
  13 +@AllArgsConstructor
  14 +public enum BpmUserTaskTimeoutActionEnum {
  15 +
  16 + AUTO_REMINDER(1,"自动提醒"),
  17 + AUTO_APPROVE(2, "自动同意"),
  18 + AUTO_REJECT(3, "自动拒绝");
  19 +
  20 + private final Integer action;
  21 + private final String name;
  22 +
  23 + public static BpmUserTaskTimeoutActionEnum actionOf(Integer action) {
  24 + return ArrayUtil.firstMatch(item -> item.getAction().equals(action), values());
  25 + }
  26 +}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
@@ -31,6 +31,16 @@ public interface BpmnModelConstants { @@ -31,6 +31,16 @@ public interface BpmnModelConstants {
31 String USER_TASK_CANDIDATE_PARAM = "candidateParam"; 31 String USER_TASK_CANDIDATE_PARAM = "candidateParam";
32 32
33 /** 33 /**
  34 + * BPMN ExtensionElement 的扩展属性,用于标记用户任务超时执行动作
  35 + */
  36 + String USER_TASK_TIMEOUT_HANDLER_ACTION = "timeoutAction";
  37 +
  38 + /**
  39 + * BPMN ExtensionElement 的扩展属性,用于标记定时边界事件类型
  40 + */
  41 + String TIMER_BOUNDARY_EVENT_TYPE = "timerBoundaryEventType";
  42 +
  43 + /**
34 * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限 44 * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限
35 */ 45 */
36 String FORM_FIELD_PERMISSION_ELEMENT = "fieldsPermission"; 46 String FORM_FIELD_PERMISSION_ELEMENT = "fieldsPermission";
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  5 +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  6 +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO;
  7 +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRejectReqVO;
  8 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmTimerBoundaryEventType;
  9 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum;
  10 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
  11 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage;
  12 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.producer.task.TodoTaskReminderProducer;
  13 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
  14 +import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
  15 +import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
  16 +import com.google.common.collect.ImmutableSet;
  17 +import jakarta.annotation.Resource;
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.flowable.bpmn.model.BoundaryEvent;
  20 +import org.flowable.bpmn.model.BpmnModel;
  21 +import org.flowable.bpmn.model.ExtensionElement;
  22 +import org.flowable.bpmn.model.FlowElement;
  23 +import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
  24 +import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
  25 +import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener;
  26 +import org.flowable.job.api.Job;
  27 +import org.flowable.task.api.Task;
  28 +import org.springframework.context.annotation.Lazy;
  29 +import org.springframework.stereotype.Component;
  30 +
  31 +import java.util.List;
  32 +import java.util.Optional;
  33 +import java.util.Set;
  34 +
  35 +/**
  36 + * 监听定时器触发事件
  37 + *
  38 + * @author jason
  39 + */
  40 +@Component
  41 +@Slf4j
  42 +public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListener {
  43 +
  44 + @Resource
  45 + @Lazy // 延迟加载,避免循环依赖
  46 + private BpmModelService bpmModelService;
  47 +
  48 + @Resource
  49 + @Lazy // 延迟加载,避免循环依赖
  50 + private BpmTaskService bpmTaskService;
  51 +
  52 + @Resource
  53 + private TodoTaskReminderProducer todoTaskReminderProducer;
  54 +
  55 + public static final Set<FlowableEngineEventType> TIME_EVENTS = ImmutableSet.<FlowableEngineEventType>builder()
  56 + .add(FlowableEngineEventType.TIMER_FIRED)
  57 + .build();
  58 +
  59 + public BpmTimerFiredEventListener() {
  60 + super(TIME_EVENTS);
  61 + }
  62 +
  63 + @Override
  64 + protected void timerFired(FlowableEngineEntityEvent event) {
  65 + String processDefinitionId = event.getProcessDefinitionId();
  66 + BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId);
  67 + Job entity = (Job) event.getEntity();
  68 + FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId());
  69 + // 如果是定时器边界事件
  70 + if (element instanceof BoundaryEvent) {
  71 + BoundaryEvent boundaryEvent = (BoundaryEvent) element;
  72 + ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(BpmnModelConstants.TIMER_BOUNDARY_EVENT_TYPE));
  73 + Integer timerBoundaryEventType = NumberUtils.parseInt(Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null));
  74 + BpmTimerBoundaryEventType bpmTimerBoundaryEventType = BpmTimerBoundaryEventType.typeOf(timerBoundaryEventType);
  75 + // 类型为用户任务超时未处理的情况
  76 + if (bpmTimerBoundaryEventType == BpmTimerBoundaryEventType.USER_TASK_TIMEOUT) {
  77 + ExtensionElement timeoutActionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION));
  78 + Integer timeoutAction = NumberUtils.parseInt(Optional.ofNullable(timeoutActionElement).map(ExtensionElement::getElementText).orElse(null));
  79 + processUserTaskTimeout(event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), timeoutAction);
  80 + }
  81 + }
  82 + }
  83 +
  84 + private void processUserTaskTimeout(String processInstanceId, String taskDefKey, Integer timeoutAction) {
  85 + BpmUserTaskTimeoutActionEnum userTaskTimeoutAction = BpmUserTaskTimeoutActionEnum.actionOf(timeoutAction);
  86 + if (userTaskTimeoutAction != null) {
  87 + // 查询超时未处理的任务
  88 + List<Task> taskList = bpmTaskService.getAssignedTaskListByConditions(processInstanceId, taskDefKey);
  89 + taskList.forEach(task -> {
  90 + // 自动提醒
  91 + if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_REMINDER) {
  92 + TodoTaskReminderMessage message = new TodoTaskReminderMessage().setTenantId(Long.parseLong(task.getTenantId()))
  93 + .setUserId(Long.parseLong(task.getAssignee())).setTaskName(task.getName());
  94 + todoTaskReminderProducer.sendReminderMessage(message);
  95 + }
  96 + // 自动同意
  97 + if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_APPROVE) {
  98 + // TODO @芋艿 这个上下文如何清除呢? 任务通过后, BpmProcessInstanceEventListener 会有回调
  99 + TenantContextHolder.setTenantId(Long.parseLong(task.getTenantId()));
  100 + TenantContextHolder.setIgnore(false);
  101 + BpmTaskApproveReqVO req = new BpmTaskApproveReqVO().setId(task.getId())
  102 + .setReason("超时系统自动同意");
  103 + bpmTaskService.approveTask(Long.parseLong(task.getAssignee()), req);
  104 +
  105 + }
  106 + // 自动拒绝
  107 + if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_REJECT) {
  108 + // TODO @芋艿 这个上下文如何清除呢? 任务拒绝后, BpmProcessInstanceEventListener 会有回调
  109 + TenantContextHolder.setTenantId(Long.parseLong(task.getTenantId()));
  110 + TenantContextHolder.setIgnore(false);
  111 + BpmTaskRejectReqVO req = new BpmTaskRejectReqVO().setId(task.getId()).setReason("超时系统自动拒绝");
  112 + bpmTaskService.rejectTask(Long.parseLong(task.getAssignee()), req);
  113 + }
  114 + });
  115 + }
  116 + }
  117 +}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/consumer/task/SysNotifyTodoTaskReminderConsumer.java 0 → 100644
  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 0 → 100644
  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 0 → 100644
  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 +/**
  11 + * 待办任务提醒 Producer
  12 + *
  13 + * @author jason
  14 + */
  15 +@Component
  16 +@Validated
  17 +public class TodoTaskReminderProducer {
  18 +
  19 + @Resource
  20 + private ApplicationContext applicationContext;
  21 +
  22 + public void sendReminderMessage(@Valid TodoTaskReminderMessage message) {
  23 + applicationContext.publishEvent(message);
  24 + }
  25 +}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.simple;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import java.util.List;
  6 +import java.util.Map;
  7 +
  8 +/**
  9 + * 仿钉钉流程设计器审批节点配置 Model
  10 + *
  11 + * @author jason
  12 + */
  13 +@Data
  14 +public class SimpleModelUserTaskConfig {
  15 +
  16 + /**
  17 + * 候选人策略
  18 + */
  19 + private Integer candidateStrategy;
  20 +
  21 + /**
  22 + * 候选人参数
  23 + */
  24 + private String candidateParam;
  25 +
  26 + /**
  27 + * 字段权限
  28 + */
  29 + private List<Map<String,String>> fieldsPermission;
  30 +
  31 + /**
  32 + * 审批方式
  33 + */
  34 + private Integer approveMethod;
  35 +
  36 +
  37 + /**
  38 + * 超时处理
  39 + */
  40 + private TimeoutHandler timeoutHandler;
  41 +
  42 +
  43 + @Data
  44 + public static class TimeoutHandler {
  45 +
  46 + /**
  47 + * 是否开启超时处理
  48 + */
  49 + private Boolean enable;
  50 +
  51 + /**
  52 + * 超时执行的动作
  53 + */
  54 + private Integer action;
  55 +
  56 + /**
  57 + * 超时时间设置
  58 + */
  59 + private String timeDuration;
  60 +
  61 + /**
  62 + * 如果执行动作是自动提醒, 最大提醒次数
  63 + */
  64 + private Integer maxRemindCount;
  65 +
  66 + }
  67 +
  68 +}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
@@ -5,27 +5,27 @@ import cn.hutool.core.collection.CollUtil; @@ -5,27 +5,27 @@ import cn.hutool.core.collection.CollUtil;
5 import cn.hutool.core.lang.Assert; 5 import cn.hutool.core.lang.Assert;
6 import cn.hutool.core.lang.TypeReference; 6 import cn.hutool.core.lang.TypeReference;
7 import cn.hutool.core.map.MapUtil; 7 import cn.hutool.core.map.MapUtil;
8 -import cn.hutool.core.util.ArrayUtil;  
9 -import cn.hutool.core.util.BooleanUtil;  
10 -import cn.hutool.core.util.NumberUtil;  
11 -import cn.hutool.core.util.StrUtil; 8 +import cn.hutool.core.util.*;
12 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 9 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
13 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; 10 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO;
14 import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; 11 import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum;
15 import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType; 12 import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType;
16 import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; 13 import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType;
17 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; 14 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
18 -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants;  
19 import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelConditionGroups; 15 import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelConditionGroups;
  16 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelUserTaskConfig;
20 import org.flowable.bpmn.BpmnAutoLayout; 17 import org.flowable.bpmn.BpmnAutoLayout;
21 import org.flowable.bpmn.model.Process; 18 import org.flowable.bpmn.model.Process;
22 import org.flowable.bpmn.model.*; 19 import org.flowable.bpmn.model.*;
23 20
24 import java.util.List; 21 import java.util.List;
25 import java.util.Map; 22 import java.util.Map;
  23 +import java.util.Objects;
26 24
27 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.END_EVENT; 25 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.END_EVENT;
28 -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.FORM_FIELD_PERMISSION_ELEMENT; 26 +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmTimerBoundaryEventType.USER_TASK_TIMEOUT;
  27 +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum.AUTO_REMINDER;
  28 +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
29 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*; 29 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*;
30 import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; 30 import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE;
31 import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX; 31 import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX;
@@ -205,8 +205,15 @@ public class SimpleModelUtils { @@ -205,8 +205,15 @@ public class SimpleModelUtils {
205 break; 205 break;
206 } 206 }
207 case USER_TASK: { 207 case USER_TASK: {
208 - UserTask userTask = buildBpmnUserTask(simpleModelNode); 208 + // 获取用户任务的配置
  209 + SimpleModelUserTaskConfig userTaskConfig = BeanUtil.toBean(simpleModelNode.getAttributes(), SimpleModelUserTaskConfig.class);
  210 + UserTask userTask = buildBpmnUserTask(simpleModelNode, userTaskConfig);
209 mainProcess.addFlowElement(userTask); 211 mainProcess.addFlowElement(userTask);
  212 + if (userTaskConfig.getTimeoutHandler() != null && userTaskConfig.getTimeoutHandler().getEnable()) {
  213 + // 添加用户任务的 Timer Boundary Event, 用于任务的超时处理
  214 + BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, userTaskConfig.getTimeoutHandler());
  215 + mainProcess.addFlowElement(boundaryEvent);
  216 + }
210 break; 217 break;
211 } 218 }
212 case COPY_TASK: { 219 case COPY_TASK: {
@@ -263,6 +270,28 @@ public class SimpleModelUtils { @@ -263,6 +270,28 @@ public class SimpleModelUtils {
263 } 270 }
264 } 271 }
265 272
  273 + private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, SimpleModelUserTaskConfig.TimeoutHandler timeoutHandler) {
  274 + // 定时器边界事件
  275 + BoundaryEvent boundaryEvent = new BoundaryEvent();
  276 + boundaryEvent.setId(IdUtil.fastUUID());
  277 + // 设置关联的任务为不会被中断
  278 + boundaryEvent.setCancelActivity(false);
  279 + boundaryEvent.setAttachedToRef(userTask);
  280 + TimerEventDefinition eventDefinition = new TimerEventDefinition();
  281 + eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration());
  282 + if (Objects.equals(AUTO_REMINDER.getAction(), timeoutHandler.getAction()) &&
  283 + timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) {
  284 + // 最大提醒次数
  285 + eventDefinition.setTimeCycle(String.format("R%d/%s", timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration()));
  286 + }
  287 + boundaryEvent.addEventDefinition(eventDefinition);
  288 + // 添加定时器边界事件类型
  289 + addExtensionElement(boundaryEvent, TIMER_BOUNDARY_EVENT_TYPE, USER_TASK_TIMEOUT.getType().toString());
  290 + // 添加超时执行动作元素
  291 + addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_ACTION, StrUtil.toStringOrNull(timeoutHandler.getAction()));
  292 + return boundaryEvent;
  293 + }
  294 +
266 private static ParallelGateway buildBpmnParallelGateway(BpmSimpleModelNodeVO node) { 295 private static ParallelGateway buildBpmnParallelGateway(BpmSimpleModelNodeVO node) {
267 ParallelGateway parallelGateway = new ParallelGateway(); 296 ParallelGateway parallelGateway = new ParallelGateway();
268 parallelGateway.setId(node.getId()); 297 parallelGateway.setId(node.getId());
@@ -278,9 +307,14 @@ public class SimpleModelUtils { @@ -278,9 +307,14 @@ public class SimpleModelUtils {
278 // TODO @jason:建议使用 ServiceTask,通过 executionListeners 实现; 307 // TODO @jason:建议使用 ServiceTask,通过 executionListeners 实现;
279 // @芋艿 ServiceTask 就可以了吧。 不需要 executionListeners 308 // @芋艿 ServiceTask 就可以了吧。 不需要 executionListeners
280 // 添加抄送候选人元素 309 // 添加抄送候选人元素
281 - addCandidateElements(node, serviceTask); 310 + addCandidateElements(MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY),
  311 + MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM),
  312 + serviceTask);
282 // 添加表单字段权限属性元素 313 // 添加表单字段权限属性元素
283 - addFormFieldsPermission(node, serviceTask); 314 + List<Map<String, String>> fieldsPermissions = MapUtil.get(node.getAttributes(),
  315 + FORM_FIELD_PERMISSION_ELEMENT, new TypeReference<>() {
  316 + });
  317 + addFormFieldsPermission(fieldsPermissions, serviceTask);
284 return serviceTask; 318 return serviceTask;
285 } 319 }
286 320
@@ -288,12 +322,10 @@ public class SimpleModelUtils { @@ -288,12 +322,10 @@ public class SimpleModelUtils {
288 /** 322 /**
289 * 给节点添加候选人元素 323 * 给节点添加候选人元素
290 */ 324 */
291 - private static void addCandidateElements(BpmSimpleModelNodeVO node, FlowElement flowElement) {  
292 - Integer candidateStrategy = MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY); 325 + private static void addCandidateElements(Integer candidateStrategy, String candidateParam, FlowElement flowElement) {
293 addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY, 326 addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY,
294 - candidateStrategy == null ? null : String.valueOf(candidateStrategy));  
295 - addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM,  
296 - MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)); 327 + candidateStrategy == null ? null : candidateStrategy.toString());
  328 + addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, candidateParam);
297 } 329 }
298 330
299 private static ExclusiveGateway buildBpmnExclusiveGateway(BpmSimpleModelNodeVO node) { 331 private static ExclusiveGateway buildBpmnExclusiveGateway(BpmSimpleModelNodeVO node) {
@@ -328,21 +360,25 @@ public class SimpleModelUtils { @@ -328,21 +360,25 @@ public class SimpleModelUtils {
328 return endEvent; 360 return endEvent;
329 } 361 }
330 362
331 - private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) { 363 + private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node, SimpleModelUserTaskConfig userTaskConfig) {
332 UserTask userTask = new UserTask(); 364 UserTask userTask = new UserTask();
333 userTask.setId(node.getId()); 365 userTask.setId(node.getId());
334 userTask.setName(node.getName()); 366 userTask.setName(node.getName());
  367 + // 设置审批任务的截止时间
  368 + if (userTaskConfig.getTimeoutHandler() != null && userTaskConfig.getTimeoutHandler().getEnable()) {
  369 + userTask.setDueDate(userTaskConfig.getTimeoutHandler().getTimeDuration());
  370 + }
  371 +
335 // 添加候选人元素 372 // 添加候选人元素
336 - addCandidateElements(node, userTask); 373 + addCandidateElements(userTaskConfig.getCandidateStrategy(), userTaskConfig.getCandidateParam(), userTask);
337 // 添加表单字段权限属性元素 374 // 添加表单字段权限属性元素
338 - addFormFieldsPermission(node, userTask); 375 + addFormFieldsPermission(userTaskConfig.getFieldsPermission(), userTask);
339 // 处理多实例 376 // 处理多实例
340 - processMultiInstanceLoopCharacteristics(node, userTask); 377 + processMultiInstanceLoopCharacteristics(userTaskConfig.getApproveMethod(), userTask);
341 return userTask; 378 return userTask;
342 } 379 }
343 380
344 - private static void processMultiInstanceLoopCharacteristics(BpmSimpleModelNodeVO node, UserTask userTask) {  
345 - Integer approveMethod = MapUtil.getInt(node.getAttributes(), SimpleModelConstants.APPROVE_METHOD_ATTRIBUTE); 381 + private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, UserTask userTask) {
346 BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod); 382 BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod);
347 if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.SINGLE_PERSON_APPROVE) { 383 if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.SINGLE_PERSON_APPROVE) {
348 return; 384 return;
@@ -369,10 +405,7 @@ public class SimpleModelUtils { @@ -369,10 +405,7 @@ public class SimpleModelUtils {
369 /** 405 /**
370 * 给节点添加表单字段权限元素 406 * 给节点添加表单字段权限元素
371 */ 407 */
372 - private static void addFormFieldsPermission(BpmSimpleModelNodeVO node, FlowElement flowElement) {  
373 - List<Map<String, String>> fieldsPermissions = MapUtil.get(node.getAttributes(),  
374 - FORM_FIELD_PERMISSION_ELEMENT, new TypeReference<>() {  
375 - }); 408 + private static void addFormFieldsPermission(List<Map<String, String>> fieldsPermissions, FlowElement flowElement) {
376 if (CollUtil.isNotEmpty(fieldsPermissions)) { 409 if (CollUtil.isNotEmpty(fieldsPermissions)) {
377 fieldsPermissions.forEach(item -> addExtensionElement(flowElement, FORM_FIELD_PERMISSION_ELEMENT, item)); 410 fieldsPermissions.forEach(item -> addExtensionElement(flowElement, FORM_FIELD_PERMISSION_ELEMENT, item));
378 } 411 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
@@ -128,6 +128,13 @@ public interface BpmTaskService { @@ -128,6 +128,13 @@ public interface BpmTaskService {
128 Task getTask(String id); 128 Task getTask(String id);
129 129
130 /** 130 /**
  131 + * 根据条件查询已经分配的用户任务列表
  132 + * @param processInstanceId 流程实例编号
  133 + * @param taskDefineKey 任务定义 Key
  134 + */
  135 + List<Task> getAssignedTaskListByConditions(String processInstanceId, String taskDefineKey);
  136 +
  137 + /**
131 * 获取当前任务的可回退的 UserTask 集合 138 * 获取当前任务的可回退的 UserTask 集合
132 * 139 *
133 * @param id 当前的任务 ID 140 * @param id 当前的任务 ID
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
@@ -433,6 +433,13 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -433,6 +433,13 @@ public class BpmTaskServiceImpl implements BpmTaskService {
433 return taskService.createTaskQuery().taskId(id).includeTaskLocalVariables().singleResult(); 433 return taskService.createTaskQuery().taskId(id).includeTaskLocalVariables().singleResult();
434 } 434 }
435 435
  436 + @Override
  437 + public List<Task> getAssignedTaskListByConditions(String processInstanceId, String defineKey) {
  438 + TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(processInstanceId)
  439 + .taskDefinitionKey(defineKey).active().taskAssigned().includeTaskLocalVariables();
  440 + return taskQuery.list();
  441 + }
  442 +
436 private HistoricTaskInstance getHistoricTask(String id) { 443 private HistoricTaskInstance getHistoricTask(String id) {
437 return historyService.createHistoricTaskInstanceQuery().taskId(id).includeTaskLocalVariables().singleResult(); 444 return historyService.createHistoricTaskInstanceQuery().taskId(id).includeTaskLocalVariables().singleResult();
438 } 445 }