Bladeren bron

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

jason 1 jaar geleden
bovenliggende
commit
d34fef67da
11 gewijzigde bestanden met toevoegingen van 417 en 24 verwijderingen
  1. 24 0
      yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTimerBoundaryEventType.java
  2. 26 0
      yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutActionEnum.java
  3. 10 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
  4. 117 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java
  5. 42 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/consumer/task/SysNotifyTodoTaskReminderConsumer.java
  6. 34 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/message/task/TodoTaskReminderMessage.java
  7. 25 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/producer/task/TodoTaskReminderProducer.java
  8. 68 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java
  9. 57 24
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
  10. 7 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
  11. 7 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java

+ 24 - 0
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTimerBoundaryEventType.java

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.bpm.enums.definition;
+
+import cn.hutool.core.util.ArrayUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 定时器边界事件类型枚举
+ *
+ * @author jason
+ */
+@Getter
+@AllArgsConstructor
+public enum BpmTimerBoundaryEventType {
+
+    USER_TASK_TIMEOUT(1,"用户任务超时");
+
+    private final Integer type;
+    private final String name;
+
+    public static BpmTimerBoundaryEventType typeOf(Integer type) {
+        return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values());
+    }
+}

+ 26 - 0
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutActionEnum.java

@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.bpm.enums.definition;
+
+import cn.hutool.core.util.ArrayUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 用户任务超时处理执行动作枚举
+ *
+ * @author jason
+ */
+@Getter
+@AllArgsConstructor
+public enum BpmUserTaskTimeoutActionEnum {
+
+    AUTO_REMINDER(1,"自动提醒"),
+    AUTO_APPROVE(2, "自动同意"),
+    AUTO_REJECT(3, "自动拒绝");
+
+    private final Integer action;
+    private final String name;
+
+    public static BpmUserTaskTimeoutActionEnum actionOf(Integer action) {
+        return ArrayUtil.firstMatch(item -> item.getAction().equals(action), values());
+    }
+}

+ 10 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java

@@ -30,6 +30,16 @@ public interface BpmnModelConstants {
      */
     String USER_TASK_CANDIDATE_PARAM = "candidateParam";
 
+    /**
+     * BPMN ExtensionElement 的扩展属性,用于标记用户任务超时执行动作
+     */
+    String USER_TASK_TIMEOUT_HANDLER_ACTION = "timeoutAction";
+
+    /**
+     * BPMN ExtensionElement 的扩展属性,用于标记定时边界事件类型
+     */
+    String TIMER_BOUNDARY_EVENT_TYPE = "timerBoundaryEventType";
+
     /**
      * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限
      */

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

@@ -0,0 +1,117 @@
+package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
+
+import cn.hutool.core.collection.CollUtil;
+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.BpmTimerBoundaryEventType;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum;
+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.ExtensionElement;
+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.Optional;
+import java.util.Set;
+
+/**
+ * 监听定时器触发事件
+ *
+ * @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;
+            ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(BpmnModelConstants.TIMER_BOUNDARY_EVENT_TYPE));
+            Integer timerBoundaryEventType = NumberUtils.parseInt(Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null));
+            BpmTimerBoundaryEventType bpmTimerBoundaryEventType = BpmTimerBoundaryEventType.typeOf(timerBoundaryEventType);
+            // 类型为用户任务超时未处理的情况
+            if (bpmTimerBoundaryEventType == BpmTimerBoundaryEventType.USER_TASK_TIMEOUT) {
+                ExtensionElement timeoutActionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION));
+                Integer timeoutAction = NumberUtils.parseInt(Optional.ofNullable(timeoutActionElement).map(ExtensionElement::getElementText).orElse(null));
+                processUserTaskTimeout(event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), timeoutAction);
+            }
+        }
+    }
+
+    private void processUserTaskTimeout(String processInstanceId, String taskDefKey, Integer timeoutAction) {
+        BpmUserTaskTimeoutActionEnum userTaskTimeoutAction = BpmUserTaskTimeoutActionEnum.actionOf(timeoutAction);
+        if (userTaskTimeoutAction != null) {
+            // 查询超时未处理的任务
+            List<Task> taskList = bpmTaskService.getAssignedTaskListByConditions(processInstanceId, taskDefKey);
+            taskList.forEach(task -> {
+                // 自动提醒
+                if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_REMINDER) {
+                    TodoTaskReminderMessage message = new TodoTaskReminderMessage().setTenantId(Long.parseLong(task.getTenantId()))
+                            .setUserId(Long.parseLong(task.getAssignee())).setTaskName(task.getName());
+                    todoTaskReminderProducer.sendReminderMessage(message);
+                }
+                // 自动同意
+                if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_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 == BpmUserTaskTimeoutActionEnum.AUTO_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);
+                }
+            });
+        }
+    }
+}

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

@@ -0,0 +1,42 @@
+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);
+        });
+    }
+}

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

@@ -0,0 +1,34 @@
+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 暂时只有站内信通知. 后面可以增加
+}

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

@@ -0,0 +1,25 @@
+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;
+
+/**
+ * 待办任务提醒 Producer
+ *
+ * @author jason
+ */
+@Component
+@Validated
+public class TodoTaskReminderProducer {
+
+    @Resource
+    private ApplicationContext applicationContext;
+
+    public void sendReminderMessage(@Valid TodoTaskReminderMessage message) {
+        applicationContext.publishEvent(message);
+    }
+}

+ 68 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java

@@ -0,0 +1,68 @@
+package cn.iocoder.yudao.module.bpm.framework.flowable.core.simple;
+
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 仿钉钉流程设计器审批节点配置 Model
+ *
+ * @author jason
+ */
+@Data
+public class SimpleModelUserTaskConfig {
+
+    /**
+     * 候选人策略
+     */
+    private  Integer candidateStrategy;
+
+    /**
+     * 候选人参数
+     */
+    private  String candidateParam;
+
+    /**
+     * 字段权限
+     */
+    private List<Map<String,String>> fieldsPermission;
+
+    /**
+     * 审批方式
+     */
+    private  Integer approveMethod;
+
+
+    /**
+     * 超时处理
+     */
+    private TimeoutHandler timeoutHandler;
+
+
+    @Data
+    public static class TimeoutHandler {
+
+        /**
+         * 是否开启超时处理
+         */
+        private Boolean enable;
+
+        /**
+         * 超时执行的动作
+         */
+        private Integer action;
+
+        /**
+         * 超时时间设置
+         */
+        private String timeDuration;
+
+        /**
+         * 如果执行动作是自动提醒, 最大提醒次数
+         */
+        private Integer maxRemindCount;
+
+    }
+
+}

+ 57 - 24
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;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.lang.TypeReference;
 import cn.hutool.core.map.MapUtil;
-import cn.hutool.core.util.ArrayUtil;
-import cn.hutool.core.util.BooleanUtil;
-import cn.hutool.core.util.NumberUtil;
-import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.util.*;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO;
 import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum;
 import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType;
 import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
-import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelConditionGroups;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelUserTaskConfig;
 import org.flowable.bpmn.BpmnAutoLayout;
 import org.flowable.bpmn.model.Process;
 import org.flowable.bpmn.model.*;
 
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.END_EVENT;
-import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.FORM_FIELD_PERMISSION_ELEMENT;
+import static cn.iocoder.yudao.module.bpm.enums.definition.BpmTimerBoundaryEventType.USER_TASK_TIMEOUT;
+import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum.AUTO_REMINDER;
+import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*;
 import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE;
 import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX;
@@ -205,8 +205,15 @@ public class SimpleModelUtils {
                 break;
             }
             case USER_TASK: {
-                UserTask userTask = buildBpmnUserTask(simpleModelNode);
+                // 获取用户任务的配置
+                SimpleModelUserTaskConfig userTaskConfig = BeanUtil.toBean(simpleModelNode.getAttributes(), SimpleModelUserTaskConfig.class);
+                UserTask userTask = buildBpmnUserTask(simpleModelNode, userTaskConfig);
                 mainProcess.addFlowElement(userTask);
+                if (userTaskConfig.getTimeoutHandler() != null && userTaskConfig.getTimeoutHandler().getEnable()) {
+                    // 添加用户任务的 Timer Boundary Event, 用于任务的超时处理
+                    BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, userTaskConfig.getTimeoutHandler());
+                    mainProcess.addFlowElement(boundaryEvent);
+                }
                 break;
             }
             case COPY_TASK: {
@@ -263,6 +270,28 @@ public class SimpleModelUtils {
         }
     }
 
+    private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, SimpleModelUserTaskConfig.TimeoutHandler timeoutHandler) {
+        // 定时器边界事件
+        BoundaryEvent boundaryEvent = new BoundaryEvent();
+        boundaryEvent.setId(IdUtil.fastUUID());
+        // 设置关联的任务为不会被中断
+        boundaryEvent.setCancelActivity(false);
+        boundaryEvent.setAttachedToRef(userTask);
+        TimerEventDefinition eventDefinition = new TimerEventDefinition();
+        eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration());
+        if (Objects.equals(AUTO_REMINDER.getAction(), timeoutHandler.getAction()) &&
+                timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) {
+            // 最大提醒次数
+            eventDefinition.setTimeCycle(String.format("R%d/%s", timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration()));
+        }
+        boundaryEvent.addEventDefinition(eventDefinition);
+        // 添加定时器边界事件类型
+        addExtensionElement(boundaryEvent, TIMER_BOUNDARY_EVENT_TYPE, USER_TASK_TIMEOUT.getType().toString());
+        // 添加超时执行动作元素
+        addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_ACTION, StrUtil.toStringOrNull(timeoutHandler.getAction()));
+        return boundaryEvent;
+    }
+
     private static ParallelGateway buildBpmnParallelGateway(BpmSimpleModelNodeVO node) {
         ParallelGateway parallelGateway = new ParallelGateway();
         parallelGateway.setId(node.getId());
@@ -278,9 +307,14 @@ public class SimpleModelUtils {
         // TODO @jason:建议使用 ServiceTask,通过 executionListeners 实现;
         // @芋艿 ServiceTask 就可以了吧。 不需要 executionListeners
         // 添加抄送候选人元素
-        addCandidateElements(node, serviceTask);
+        addCandidateElements(MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY),
+                MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM),
+                serviceTask);
         // 添加表单字段权限属性元素
-        addFormFieldsPermission(node, serviceTask);
+        List<Map<String, String>> fieldsPermissions = MapUtil.get(node.getAttributes(),
+                FORM_FIELD_PERMISSION_ELEMENT, new TypeReference<>() {
+                });
+        addFormFieldsPermission(fieldsPermissions, serviceTask);
         return serviceTask;
     }
 
@@ -288,12 +322,10 @@ public class SimpleModelUtils {
     /**
      * 给节点添加候选人元素
      */
-    private static void addCandidateElements(BpmSimpleModelNodeVO node, FlowElement flowElement) {
-        Integer candidateStrategy = MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY);
+    private static void addCandidateElements(Integer candidateStrategy, String candidateParam, FlowElement flowElement) {
         addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY,
-                candidateStrategy == null ? null : String.valueOf(candidateStrategy));
-        addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM,
-                MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM));
+                candidateStrategy == null ? null : candidateStrategy.toString());
+        addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, candidateParam);
     }
 
     private static ExclusiveGateway buildBpmnExclusiveGateway(BpmSimpleModelNodeVO node) {
@@ -328,21 +360,25 @@ public class SimpleModelUtils {
         return endEvent;
     }
 
-    private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) {
+    private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node, SimpleModelUserTaskConfig userTaskConfig) {
         UserTask userTask = new UserTask();
         userTask.setId(node.getId());
         userTask.setName(node.getName());
+        //  设置审批任务的截止时间
+        if (userTaskConfig.getTimeoutHandler() != null && userTaskConfig.getTimeoutHandler().getEnable()) {
+            userTask.setDueDate(userTaskConfig.getTimeoutHandler().getTimeDuration());
+        }
+
         // 添加候选人元素
-        addCandidateElements(node, userTask);
+        addCandidateElements(userTaskConfig.getCandidateStrategy(), userTaskConfig.getCandidateParam(), userTask);
         // 添加表单字段权限属性元素
-        addFormFieldsPermission(node, userTask);
+        addFormFieldsPermission(userTaskConfig.getFieldsPermission(), userTask);
         // 处理多实例
-        processMultiInstanceLoopCharacteristics(node, userTask);
+        processMultiInstanceLoopCharacteristics(userTaskConfig.getApproveMethod(), userTask);
         return userTask;
     }
 
-    private static void processMultiInstanceLoopCharacteristics(BpmSimpleModelNodeVO node, UserTask userTask) {
-        Integer approveMethod = MapUtil.getInt(node.getAttributes(), SimpleModelConstants.APPROVE_METHOD_ATTRIBUTE);
+    private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, UserTask userTask) {
         BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod);
         if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.SINGLE_PERSON_APPROVE) {
             return;
@@ -369,10 +405,7 @@ public class SimpleModelUtils {
     /**
      * 给节点添加表单字段权限元素
      */
-    private static void addFormFieldsPermission(BpmSimpleModelNodeVO node, FlowElement flowElement) {
-        List<Map<String, String>> fieldsPermissions = MapUtil.get(node.getAttributes(),
-                FORM_FIELD_PERMISSION_ELEMENT, new TypeReference<>() {
-                });
+    private static void addFormFieldsPermission(List<Map<String, String>> fieldsPermissions, FlowElement flowElement) {
         if (CollUtil.isNotEmpty(fieldsPermissions)) {
             fieldsPermissions.forEach(item -> addExtensionElement(flowElement, FORM_FIELD_PERMISSION_ELEMENT, item));
         }

+ 7 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java

@@ -127,6 +127,13 @@ public interface BpmTaskService {
      */
     Task getTask(String id);
 
+    /**
+     * 根据条件查询已经分配的用户任务列表
+     * @param processInstanceId 流程实例编号
+     * @param taskDefineKey 任务定义 Key
+     */
+    List<Task> getAssignedTaskListByConditions(String processInstanceId, String taskDefineKey);
+
     /**
      * 获取当前任务的可回退的 UserTask 集合
      *

+ 7 - 0
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 {
         return taskService.createTaskQuery().taskId(id).includeTaskLocalVariables().singleResult();
     }
 
+    @Override
+    public List<Task> getAssignedTaskListByConditions(String processInstanceId, String defineKey) {
+        TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(processInstanceId)
+                .taskDefinitionKey(defineKey).active().taskAssigned().includeTaskLocalVariables();
+        return taskQuery.list();
+    }
+
     private HistoricTaskInstance getHistoricTask(String id) {
         return historyService.createHistoricTaskInstanceQuery().taskId(id).includeTaskLocalVariables().singleResult();
     }