Commit d34fef67dae7259e7a05c5f19778d097467ec0e4
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 | 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 | 44 | * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限 |
| 35 | 45 | */ |
| 36 | 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 | 5 | import cn.hutool.core.lang.Assert; |
| 6 | 6 | import cn.hutool.core.lang.TypeReference; |
| 7 | 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 | 9 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 13 | 10 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; |
| 14 | 11 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; |
| 15 | 12 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType; |
| 16 | 13 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; |
| 17 | 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 | 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 | 17 | import org.flowable.bpmn.BpmnAutoLayout; |
| 21 | 18 | import org.flowable.bpmn.model.Process; |
| 22 | 19 | import org.flowable.bpmn.model.*; |
| 23 | 20 | |
| 24 | 21 | import java.util.List; |
| 25 | 22 | import java.util.Map; |
| 23 | +import java.util.Objects; | |
| 26 | 24 | |
| 27 | 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 | 29 | import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*; |
| 30 | 30 | import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; |
| 31 | 31 | import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX; |
| ... | ... | @@ -205,8 +205,15 @@ public class SimpleModelUtils { |
| 205 | 205 | break; |
| 206 | 206 | } |
| 207 | 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 | 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 | 217 | break; |
| 211 | 218 | } |
| 212 | 219 | case COPY_TASK: { |
| ... | ... | @@ -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 | 295 | private static ParallelGateway buildBpmnParallelGateway(BpmSimpleModelNodeVO node) { |
| 267 | 296 | ParallelGateway parallelGateway = new ParallelGateway(); |
| 268 | 297 | parallelGateway.setId(node.getId()); |
| ... | ... | @@ -278,9 +307,14 @@ public class SimpleModelUtils { |
| 278 | 307 | // TODO @jason:建议使用 ServiceTask,通过 executionListeners 实现; |
| 279 | 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 | 318 | return serviceTask; |
| 285 | 319 | } |
| 286 | 320 | |
| ... | ... | @@ -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 | 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 | 331 | private static ExclusiveGateway buildBpmnExclusiveGateway(BpmSimpleModelNodeVO node) { |
| ... | ... | @@ -328,21 +360,25 @@ public class SimpleModelUtils { |
| 328 | 360 | return endEvent; |
| 329 | 361 | } |
| 330 | 362 | |
| 331 | - private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) { | |
| 363 | + private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node, SimpleModelUserTaskConfig userTaskConfig) { | |
| 332 | 364 | UserTask userTask = new UserTask(); |
| 333 | 365 | userTask.setId(node.getId()); |
| 334 | 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 | 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 | 382 | BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod); |
| 347 | 383 | if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.SINGLE_PERSON_APPROVE) { |
| 348 | 384 | return; |
| ... | ... | @@ -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 | 409 | if (CollUtil.isNotEmpty(fieldsPermissions)) { |
| 377 | 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 | 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 | 138 | * 获取当前任务的可回退的 UserTask 集合 |
| 132 | 139 | * |
| 133 | 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 | 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 | 443 | private HistoricTaskInstance getHistoricTask(String id) { |
| 437 | 444 | return historyService.createHistoricTaskInstanceQuery().taskId(id).includeTaskLocalVariables().singleResult(); |
| 438 | 445 | } | ... | ... |