mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-10-31 18:28:43 +08:00 
			
		
		
		
	BPM:移除 flowable starter 模块,融合到 bpm 模块中
This commit is contained in:
		| @@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | ||||
| import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | ||||
| import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | ||||
| import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils; | ||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; | ||||
| import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageItemRespVO; | ||||
| import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; | ||||
| import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; | ||||
|   | ||||
| @@ -6,7 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | ||||
| import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | ||||
| import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | ||||
| import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils; | ||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; | ||||
| import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; | ||||
| import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; | ||||
| import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; | ||||
|   | ||||
| @@ -0,0 +1,41 @@ | ||||
| package cn.iocoder.yudao.module.bpm.framework; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import com.alibaba.ttl.TransmittableThreadLocal; | ||||
|  | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
|  * 工作流--用户用到的上下文相关信息 | ||||
|  */ | ||||
| @Deprecated // TODO 芋艿:找个方式,去掉这个上下文 | ||||
| public class FlowableContextHolder { | ||||
|  | ||||
|     private static final ThreadLocal<Map<String, List<Long>>> ASSIGNEE = new TransmittableThreadLocal<>(); | ||||
|  | ||||
|     /** | ||||
|      * 通过流程任务的定义 key ,拿到提前选好的审批人 | ||||
|      * 此方法目的:首次创建流程实例时,数据库中还查询不到 assignee 字段,所以存入上下文中获取 | ||||
|      * | ||||
|      * @param taskDefinitionKey 流程任务 key | ||||
|      * @return 审批人 ID 集合 | ||||
|      */ | ||||
|     public static List<Long> getAssigneeByTaskDefinitionKey(String taskDefinitionKey) { | ||||
|         if (CollUtil.isNotEmpty(ASSIGNEE.get())) { | ||||
|             return ASSIGNEE.get().get(taskDefinitionKey); | ||||
|         } | ||||
|         return Collections.emptyList(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 存入提前选好的审批人到上下文线程变量中 | ||||
|      * | ||||
|      * @param assignee 流程任务 key -> 审批人 ID 炅和 | ||||
|      */ | ||||
|     public static void setAssignee(Map<String, List<Long>> assignee) { | ||||
|         ASSIGNEE.set(assignee); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,19 +0,0 @@ | ||||
| package cn.iocoder.yudao.module.bpm.framework.bpm.config; | ||||
|  | ||||
| import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventPublisher; | ||||
| import org.springframework.context.ApplicationEventPublisher; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| /** | ||||
|  * BPM 通用的 Configuration 配置类,提供给 Activiti 和 Flowable | ||||
|  */ | ||||
| @Configuration(proxyBeanMethods = false) | ||||
| public class BpmCommonConfiguration { | ||||
|  | ||||
|     @Bean | ||||
|     public BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher(ApplicationEventPublisher publisher) { | ||||
|         return new BpmProcessInstanceResultEventPublisher(publisher); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,6 +0,0 @@ | ||||
| /** | ||||
|  * 自定义 Event 实现,提供方便业务接入的 Listener! | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| package cn.iocoder.yudao.module.bpm.framework.bpm.core.event; | ||||
| @@ -1,4 +0,0 @@ | ||||
| /** | ||||
|  * 占位 | ||||
|  */ | ||||
| package cn.iocoder.yudao.module.bpm.framework.bpm.core; | ||||
| @@ -1,6 +0,0 @@ | ||||
| /** | ||||
|  * 提供给 Activiti 和 Flowable 的通用封装 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| package cn.iocoder.yudao.module.bpm.framework.bpm; | ||||
| @@ -4,13 +4,18 @@ import cn.hutool.core.collection.ListUtil; | ||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.BpmActivityBehaviorFactory; | ||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; | ||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; | ||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceResultEventPublisher; | ||||
| import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | ||||
| import org.flowable.common.engine.api.delegate.event.FlowableEventListener; | ||||
| import org.flowable.spring.SpringProcessEngineConfiguration; | ||||
| import org.flowable.spring.boot.EngineConfigurationConfigurer; | ||||
| import org.springframework.beans.factory.ObjectProvider; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; | ||||
| import org.springframework.context.ApplicationEventPublisher; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.core.task.AsyncListenableTaskExecutor; | ||||
| import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| @@ -22,6 +27,26 @@ import java.util.List; | ||||
| @Configuration(proxyBeanMethods = false) | ||||
| public class BpmFlowableConfiguration { | ||||
|  | ||||
|     /** | ||||
|      * 参考 {@link org.flowable.spring.boot.FlowableJobConfiguration} 类,创建对应的 AsyncListenableTaskExecutor Bean | ||||
|      * | ||||
|      * 如果不创建,会导致项目启动时,Flowable 报错的问题 | ||||
|      */ | ||||
|     @Bean(name = "applicationTaskExecutor") | ||||
|     @ConditionalOnMissingBean(name = "applicationTaskExecutor") | ||||
|     public AsyncListenableTaskExecutor taskExecutor() { | ||||
|         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); | ||||
|         executor.setCorePoolSize(8); | ||||
|         executor.setMaxPoolSize(8); | ||||
|         executor.setQueueCapacity(100); | ||||
|         executor.setThreadNamePrefix("flowable-task-Executor-"); | ||||
|         executor.setAwaitTerminationSeconds(30); | ||||
|         executor.setWaitForTasksToCompleteOnShutdown(true); | ||||
|         executor.setAllowCoreThreadTimeOut(true); | ||||
|         executor.initialize(); | ||||
|         return executor; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * BPM 模块的 ProcessEngineConfigurationConfigurer 实现类: | ||||
|      * | ||||
| @@ -56,4 +81,11 @@ public class BpmFlowableConfiguration { | ||||
|         return new BpmTaskCandidateInvoker(strategyList, adminUserApi); | ||||
|     } | ||||
|  | ||||
|     // =========== 自己拓展的 Bean ========== | ||||
|  | ||||
|     @Bean | ||||
|     public BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher(ApplicationEventPublisher publisher) { | ||||
|         return new BpmProcessInstanceResultEventPublisher(publisher); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,6 +1,6 @@ | ||||
| package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils; | ||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; | ||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; | ||||
| import lombok.Setter; | ||||
| import org.flowable.bpmn.model.Activity; | ||||
| @@ -43,9 +43,9 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav | ||||
|         // 第一步,设置 collectionVariable 和 CollectionVariable | ||||
|         // 从  execution.getVariable() 读取所有任务处理人的 key | ||||
|         super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的 | ||||
|         super.collectionVariable = FlowableUtils.formatCollectionVariable(execution.getCurrentActivityId()); | ||||
|         super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId()); | ||||
|         // 从 execution.getVariable() 读取当前所有任务处理的人的 key | ||||
|         super.collectionElementVariable = FlowableUtils.formatCollectionElementVariable(execution.getCurrentActivityId()); | ||||
|         super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId()); | ||||
|  | ||||
|         // 第二步,获取任务的所有处理人 | ||||
|         Set<Long> assigneeUserIds = taskCandidateInvoker.calculateUsers(execution); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils; | ||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; | ||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; | ||||
| import lombok.Setter; | ||||
| import org.flowable.bpmn.model.Activity; | ||||
| @@ -37,9 +37,9 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB | ||||
|         // 第一步,设置 collectionVariable 和 CollectionVariable | ||||
|         // 从  execution.getVariable() 读取所有任务处理人的 key | ||||
|         super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的 | ||||
|         super.collectionVariable = FlowableUtils.formatCollectionVariable(execution.getCurrentActivityId()); | ||||
|         super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId()); | ||||
|         // 从 execution.getVariable() 读取当前所有任务处理的人的 key | ||||
|         super.collectionElementVariable = FlowableUtils.formatCollectionElementVariable(execution.getCurrentActivityId()); | ||||
|         super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId()); | ||||
|  | ||||
|         // 第二步,获取任务的所有处理人 | ||||
|         Set<Long> assigneeUserIds = new LinkedHashSet<>(taskCandidateInvoker.calculateUsers(execution)); // 保证有序!!! | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import cn.hutool.core.lang.Assert; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||||
| import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | ||||
| import cn.iocoder.yudao.framework.flowable.core.util.BpmnModelUtils; | ||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; | ||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; | ||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; | ||||
| import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils; | ||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; | ||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; | ||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; | ||||
| import org.dromara.hutool.core.convert.Convert; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| package cn.iocoder.yudao.module.bpm.framework.bpm.core.event; | ||||
| package cn.iocoder.yudao.module.bpm.framework.flowable.core.event; | ||||
| 
 | ||||
| import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceResultEvent; | ||||
| import lombok.AllArgsConstructor; | ||||
| @@ -0,0 +1,320 @@ | ||||
| package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.util.ArrayUtil; | ||||
| import org.flowable.bpmn.converter.BpmnXMLConverter; | ||||
| import org.flowable.bpmn.model.Process; | ||||
| import org.flowable.bpmn.model.*; | ||||
| import org.flowable.common.engine.impl.util.io.BytesStreamSource; | ||||
|  | ||||
| import java.util.*; | ||||
|  | ||||
| /** | ||||
|  * 流程模型转操作工具类 | ||||
|  */ | ||||
| public class BpmnModelUtils { | ||||
|  | ||||
|     /** | ||||
|      * 根据节点,获取入口连线 | ||||
|      * | ||||
|      * @param source 起始节点 | ||||
|      * @return 入口连线列表 | ||||
|      */ | ||||
|     public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) { | ||||
|         if (source instanceof FlowNode) { | ||||
|             return ((FlowNode) source).getIncomingFlows(); | ||||
|         } | ||||
|         return new ArrayList<>(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据节点,获取出口连线 | ||||
|      * | ||||
|      * @param source 起始节点 | ||||
|      * @return 出口连线列表 | ||||
|      */ | ||||
|     public static List<SequenceFlow> getElementOutgoingFlows(FlowElement source) { | ||||
|         if (source instanceof FlowNode) { | ||||
|             return ((FlowNode) source).getOutgoingFlows(); | ||||
|         } | ||||
|         return new ArrayList<>(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取流程元素信息 | ||||
|      * | ||||
|      * @param model         bpmnModel 对象 | ||||
|      * @param flowElementId 元素 ID | ||||
|      * @return 元素信息 | ||||
|      */ | ||||
|     public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) { | ||||
|         Process process = model.getMainProcess(); | ||||
|         return process.getFlowElement(flowElementId); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获得 BPMN 流程中,指定的元素们 | ||||
|      * | ||||
|      * @param model 模型 | ||||
|      * @param clazz 指定元素。例如说,{@link UserTask}、{@link Gateway} 等等 | ||||
|      * @return 元素们 | ||||
|      */ | ||||
|     public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) { | ||||
|         List<T> result = new ArrayList<>(); | ||||
|         model.getProcesses().forEach(process -> { | ||||
|             process.getFlowElements().forEach(flowElement -> { | ||||
|                 if (flowElement.getClass().isAssignableFrom(clazz)) { | ||||
|                     result.add((T) flowElement); | ||||
|                 } | ||||
|             }); | ||||
|         }); | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 比较 两个bpmnModel 是否相同 | ||||
|      * @param oldModel  老的bpmn model | ||||
|      * @param newModel 新的bpmn model | ||||
|      */ | ||||
|     public static boolean equals(BpmnModel oldModel, BpmnModel newModel) { | ||||
|         // 由于 BpmnModel 未提供 equals 方法,所以只能转成字节数组,进行比较 | ||||
|         return Arrays.equals(getBpmnBytes(oldModel), getBpmnBytes(newModel)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 把 bpmnModel 转换成 byte[] | ||||
|      * @param model  bpmnModel | ||||
|      */ | ||||
|     public static byte[] getBpmnBytes(BpmnModel model) { | ||||
|         if (model == null) { | ||||
|             return new byte[0]; | ||||
|         } | ||||
|         BpmnXMLConverter converter = new BpmnXMLConverter(); | ||||
|         return converter.convertToXML(model); | ||||
|     } | ||||
|  | ||||
|     public static BpmnModel getBpmnModel(byte[] bpmnBytes) { | ||||
|         if (ArrayUtil.isEmpty(bpmnBytes)) { | ||||
|             return null; | ||||
|         } | ||||
|         BpmnXMLConverter converter = new BpmnXMLConverter(); | ||||
|         // 补充说明:由于在 Flowable 中自定义了属性,所以 validateSchema 传递 false | ||||
|         return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), false, false); | ||||
|     } | ||||
|  | ||||
|     // ========== 遍历相关的方法 ========== | ||||
|  | ||||
|     /** | ||||
|      * 找到 source 节点之前的所有用户任务节点 | ||||
|      * | ||||
|      * @param source          起始节点 | ||||
|      * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 | ||||
|      * @param userTaskList    已找到的用户任务节点 | ||||
|      * @return 用户任务节点 数组 | ||||
|      */ | ||||
|     public static List<UserTask> getPreviousUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) { | ||||
|         userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; | ||||
|         hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; | ||||
|         // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 | ||||
|         if (source instanceof StartEvent && source.getSubProcess() != null) { | ||||
|             userTaskList = getPreviousUserTaskList(source.getSubProcess(), hasSequenceFlow, userTaskList); | ||||
|         } | ||||
|  | ||||
|         // 根据类型,获取入口连线 | ||||
|         List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source); | ||||
|         if (sequenceFlows == null) { | ||||
|             return userTaskList; | ||||
|         } | ||||
|         // 循环找到目标元素 | ||||
|         for (SequenceFlow sequenceFlow : sequenceFlows) { | ||||
|             // 如果发现连线重复,说明循环了,跳过这个循环 | ||||
|             if (hasSequenceFlow.contains(sequenceFlow.getId())) { | ||||
|                 continue; | ||||
|             } | ||||
|             // 添加已经走过的连线 | ||||
|             hasSequenceFlow.add(sequenceFlow.getId()); | ||||
|             // 类型为用户节点,则新增父级节点 | ||||
|             if (sequenceFlow.getSourceFlowElement() instanceof UserTask) { | ||||
|                 userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement()); | ||||
|             } | ||||
|             // 类型为子流程,则添加子流程开始节点出口处相连的节点 | ||||
|             if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) { | ||||
|                 // 获取子流程用户任务节点 | ||||
|                 List<UserTask> childUserTaskList = findChildProcessUserTaskList((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, null); | ||||
|                 // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 | ||||
|                 if (CollUtil.isNotEmpty(childUserTaskList)) { | ||||
|                     userTaskList.addAll(childUserTaskList); | ||||
|                 } | ||||
|             } | ||||
|             // 继续迭代 | ||||
|             userTaskList = getPreviousUserTaskList(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList); | ||||
|         } | ||||
|         return userTaskList; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 迭代获取子流程用户任务节点 | ||||
|      * | ||||
|      * @param source          起始节点 | ||||
|      * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 | ||||
|      * @param userTaskList    需要撤回的用户任务列表 | ||||
|      * @return 用户任务节点 | ||||
|      */ | ||||
|     public static List<UserTask> findChildProcessUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) { | ||||
|         hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; | ||||
|         userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; | ||||
|  | ||||
|         // 根据类型,获取出口连线 | ||||
|         List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source); | ||||
|         if (sequenceFlows == null) { | ||||
|             return userTaskList; | ||||
|         } | ||||
|         // 循环找到目标元素 | ||||
|         for (SequenceFlow sequenceFlow : sequenceFlows) { | ||||
|             // 如果发现连线重复,说明循环了,跳过这个循环 | ||||
|             if (hasSequenceFlow.contains(sequenceFlow.getId())) { | ||||
|                 continue; | ||||
|             } | ||||
|             // 添加已经走过的连线 | ||||
|             hasSequenceFlow.add(sequenceFlow.getId()); | ||||
|             // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加 | ||||
|             if (sequenceFlow.getTargetFlowElement() instanceof UserTask) { | ||||
|                 userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement()); | ||||
|                 continue; | ||||
|             } | ||||
|             // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 | ||||
|             if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { | ||||
|                 List<UserTask> childUserTaskList = findChildProcessUserTaskList((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, null); | ||||
|                 // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 | ||||
|                 if (CollUtil.isNotEmpty(childUserTaskList)) { | ||||
|                     userTaskList.addAll(childUserTaskList); | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
|             // 继续迭代 | ||||
|             userTaskList = findChildProcessUserTaskList(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, userTaskList); | ||||
|         } | ||||
|         return userTaskList; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行 | ||||
|      * 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况 | ||||
|      * | ||||
|      * @param source          起始节点 | ||||
|      * @param target          目标节点 | ||||
|      * @param visitedElements 已经经过的连线的 ID,用于判断线路是否重复 | ||||
|      * @return 结果 | ||||
|      */ | ||||
|     public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) { | ||||
|         visitedElements = visitedElements == null ? new HashSet<>() : visitedElements; | ||||
|         // 不能是开始事件和子流程 | ||||
|         if (source instanceof StartEvent && isInEventSubprocess(source)) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         // 根据类型,获取入口连线 | ||||
|         List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source); | ||||
|         if (CollUtil.isEmpty(sequenceFlows)) { | ||||
|             return true; | ||||
|         } | ||||
|         // 循环找到目标元素 | ||||
|         for (SequenceFlow sequenceFlow : sequenceFlows) { | ||||
|             // 如果发现连线重复,说明循环了,跳过这个循环 | ||||
|             if (visitedElements.contains(sequenceFlow.getId())) { | ||||
|                 continue; | ||||
|             } | ||||
|             // 添加已经走过的连线 | ||||
|             visitedElements.add(sequenceFlow.getId()); | ||||
|             // 这条线路存在目标节点,这条线路完成,进入下个线路 | ||||
|             FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement(); | ||||
|             if (target.getId().equals(sourceFlowElement.getId())) { | ||||
|                 continue; | ||||
|             } | ||||
|             // 如果目标节点为并行网关,则不继续 | ||||
|             if (sourceFlowElement instanceof ParallelGateway) { | ||||
|                 return false; | ||||
|             } | ||||
|             // 否则就继续迭代 | ||||
|             if (!isSequentialReachable(sourceFlowElement, target, visitedElements)) { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 判断当前节点是否属于不同的子流程 | ||||
|      * | ||||
|      * @param flowElement 被判断的节点 | ||||
|      * @return true 表示属于子流程 | ||||
|      */ | ||||
|     private static boolean isInEventSubprocess(FlowElement flowElement) { | ||||
|         FlowElementsContainer flowElementsContainer = flowElement.getParentContainer(); | ||||
|         while (flowElementsContainer != null) { | ||||
|             if (flowElementsContainer instanceof EventSubProcess) { | ||||
|                 return true; | ||||
|             } | ||||
|  | ||||
|             if (flowElementsContainer instanceof FlowElement) { | ||||
|                 flowElementsContainer = ((FlowElement) flowElementsContainer).getParentContainer(); | ||||
|             } else { | ||||
|                 flowElementsContainer = null; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据正在运行的任务节点,迭代获取子级任务节点列表,向后找 | ||||
|      * | ||||
|      * @param source          起始节点 | ||||
|      * @param runTaskKeyList  正在运行的任务 Key,用于校验任务节点是否是正在运行的节点 | ||||
|      * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 | ||||
|      * @param userTaskList    需要撤回的用户任务列表 | ||||
|      * @return 子级任务节点列表 | ||||
|      */ | ||||
|     public static List<UserTask> iteratorFindChildUserTasks(FlowElement source, List<String> runTaskKeyList, | ||||
|                                                             Set<String> hasSequenceFlow, List<UserTask> userTaskList) { | ||||
|         hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; | ||||
|         userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; | ||||
|         // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 | ||||
|         if (source instanceof StartEvent && source.getSubProcess() != null) { | ||||
|             userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, userTaskList); | ||||
|         } | ||||
|  | ||||
|         // 根据类型,获取出口连线 | ||||
|         List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source); | ||||
|         if (sequenceFlows == null) { | ||||
|             return userTaskList; | ||||
|         } | ||||
|         // 循环找到目标元素 | ||||
|         for (SequenceFlow sequenceFlow : sequenceFlows) { | ||||
|             // 如果发现连线重复,说明循环了,跳过这个循环 | ||||
|             if (hasSequenceFlow.contains(sequenceFlow.getId())) { | ||||
|                 continue; | ||||
|             } | ||||
|             // 添加已经走过的连线 | ||||
|             hasSequenceFlow.add(sequenceFlow.getId()); | ||||
|             // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加 | ||||
|             if (sequenceFlow.getTargetFlowElement() instanceof UserTask && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) { | ||||
|                 userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement()); | ||||
|                 continue; | ||||
|             } | ||||
|             // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 | ||||
|             if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { | ||||
|                 List<UserTask> childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runTaskKeyList, hasSequenceFlow, null); | ||||
|                 // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 | ||||
|                 if (CollUtil.isNotEmpty(childUserTaskList)) { | ||||
|                     userTaskList.addAll(childUserTaskList); | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
|             // 继续迭代 | ||||
|             userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList); | ||||
|         } | ||||
|         return userTaskList; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,160 @@ | ||||
| package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; | ||||
|  | ||||
| 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; | ||||
| import org.flowable.common.engine.impl.el.ExpressionManager; | ||||
| import org.flowable.common.engine.impl.identity.Authentication; | ||||
| import org.flowable.engine.history.HistoricProcessInstance; | ||||
| import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; | ||||
| import org.flowable.engine.impl.util.CommandContextUtil; | ||||
| import org.flowable.engine.runtime.ProcessInstance; | ||||
| import org.flowable.task.api.TaskInfo; | ||||
|  | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
|  * Flowable 相关的工具方法 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| public class FlowableUtils { | ||||
|  | ||||
|     // ========== User 相关的工具方法 ========== | ||||
|  | ||||
|     public static void setAuthenticatedUserId(Long userId) { | ||||
|         Authentication.setAuthenticatedUserId(String.valueOf(userId)); | ||||
|     } | ||||
|  | ||||
|     public static void clearAuthenticatedUserId() { | ||||
|         Authentication.setAuthenticatedUserId(null); | ||||
|     } | ||||
|  | ||||
|     // ========== Execution 相关的工具方法 ========== | ||||
|  | ||||
|     /** | ||||
|      * 格式化多实例(并签、或签)的 collectionVariable 变量(多实例对应的多审批人列表) | ||||
|      * | ||||
|      * @param activityId 活动编号 | ||||
|      * @return collectionVariable 变量 | ||||
|      */ | ||||
|     public static String formatExecutionCollectionVariable(String activityId) { | ||||
|         return activityId + "_assignees"; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 格式化多实例(并签、或签)的 collectionElementVariable 变量(当前实例对应的一个审批人) | ||||
|      * | ||||
|      * @param activityId 活动编号 | ||||
|      * @return collectionElementVariable 变量 | ||||
|      */ | ||||
|     public static String formatExecutionCollectionElementVariable(String activityId) { | ||||
|         return activityId + "_assignee"; | ||||
|     } | ||||
|  | ||||
|     // ========== ProcessInstance 相关的工具方法 ========== | ||||
|  | ||||
|     public static Integer getProcessInstanceStatus(ProcessInstance processInstance) { | ||||
|         return getProcessInstanceStatus(processInstance.getProcessVariables()); | ||||
|     } | ||||
|  | ||||
|     public static Integer getProcessInstanceStatus(HistoricProcessInstance processInstance) { | ||||
|         return getProcessInstanceStatus(processInstance.getProcessVariables()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获得流程实例的状态 | ||||
|      * | ||||
|      * @param processVariables 流程实例的 variables | ||||
|      * @return 状态 | ||||
|      */ | ||||
|     private static Integer getProcessInstanceStatus(Map<String, Object> processVariables) { | ||||
|         return (Integer) processVariables.get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获得流程实例的表单 | ||||
|      * | ||||
|      * @param processInstance 流程实例 | ||||
|      * @return 表单 | ||||
|      */ | ||||
|     public static Map<String, Object> getProcessInstanceFormVariable(ProcessInstance processInstance) { | ||||
|         Map<String, Object> formVariables = new HashMap<>(processInstance.getProcessVariables()); | ||||
|         filterProcessInstanceFormVariable(formVariables); | ||||
|         return formVariables; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 过滤流程实例的表单 | ||||
|      * | ||||
|      * 为什么要过滤?目前使用 processVariables 存储所有流程实例的拓展字段,需要过滤掉一部分的系统字段,从而实现表单的展示 | ||||
|      * | ||||
|      * @param processVariables 流程实例的 variables | ||||
|      * @return 过滤后的表单 | ||||
|      */ | ||||
|     public static Map<String, Object> filterProcessInstanceFormVariable(Map<String, Object> processVariables) { | ||||
|         processVariables.remove(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); | ||||
|         return processVariables; | ||||
|     } | ||||
|  | ||||
|     // ========== Task 相关的工具方法 ========== | ||||
|  | ||||
|     /** | ||||
|      * 获得任务的状态 | ||||
|      * | ||||
|      * @param task 任务 | ||||
|      * @return 状态 | ||||
|      */ | ||||
|     public static Integer getTaskStatus(TaskInfo task) { | ||||
|         return (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获得任务的审批原因 | ||||
|      * | ||||
|      * @param task 任务 | ||||
|      * @return 审批原因 | ||||
|      */ | ||||
|     public static String getTaskReason(TaskInfo task) { | ||||
|         return (String) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_REASON); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获得任务的表单 | ||||
|      * | ||||
|      * @param task 任务 | ||||
|      * @return 表单 | ||||
|      */ | ||||
|     public static Map<String, Object> getTaskFormVariable(TaskInfo task) { | ||||
|         Map<String, Object> formVariables = new HashMap<>(task.getTaskLocalVariables()); | ||||
|         filterTaskFormVariable(formVariables); | ||||
|         return formVariables; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 过滤任务的表单 | ||||
|      * | ||||
|      * 为什么要过滤?目前使用 taskLocalVariables 存储所有任务的拓展字段,需要过滤掉一部分的系统字段,从而实现表单的展示 | ||||
|      * | ||||
|      * @param taskLocalVariables 任务的 taskLocalVariables | ||||
|      * @return 过滤后的表单 | ||||
|      */ | ||||
|     public static Map<String, Object> filterTaskFormVariable(Map<String, Object> taskLocalVariables) { | ||||
|         taskLocalVariables.remove(BpmConstants.TASK_VARIABLE_STATUS); | ||||
|         taskLocalVariables.remove(BpmConstants.TASK_VARIABLE_REASON); | ||||
|         return taskLocalVariables; | ||||
|     } | ||||
|  | ||||
|     // ========== Expression 相关的工具方法 ========== | ||||
|  | ||||
|     public static Object getExpressionValue(VariableContainer variableContainer, String expressionString) { | ||||
|         ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); | ||||
|         assert processEngineConfiguration != null; | ||||
|         ExpressionManager expressionManager = processEngineConfiguration.getExpressionManager(); | ||||
|         assert expressionManager != null; | ||||
|         Expression expression = expressionManager.createExpression(expressionString); | ||||
|         return expression.getValue(variableContainer); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,7 +1,10 @@ | ||||
| package cn.iocoder.yudao.module.bpm.framework.web.config; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum; | ||||
| import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration; | ||||
| import cn.iocoder.yudao.module.bpm.framework.web.core.FlowableWebFilter; | ||||
| import org.springdoc.core.models.GroupedOpenApi; | ||||
| import org.springframework.boot.web.servlet.FilterRegistrationBean; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| @@ -21,4 +24,15 @@ public class BpmWebConfiguration { | ||||
|         return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("bpm"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 配置 Flowable Web 过滤器 | ||||
|      */ | ||||
|     @Bean | ||||
|     public FilterRegistrationBean<FlowableWebFilter> flowableWebFilter() { | ||||
|         FilterRegistrationBean<FlowableWebFilter> registrationBean = new FilterRegistrationBean<>(); | ||||
|         registrationBean.setFilter(new FlowableWebFilter()); | ||||
|         registrationBean.setOrder(WebFilterOrderEnum.FLOWABLE_FILTER); | ||||
|         return registrationBean; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,36 @@ | ||||
| package cn.iocoder.yudao.module.bpm.framework.web.core; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; | ||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; | ||||
| import jakarta.servlet.FilterChain; | ||||
| import jakarta.servlet.ServletException; | ||||
| import jakarta.servlet.http.HttpServletRequest; | ||||
| import jakarta.servlet.http.HttpServletResponse; | ||||
| import org.springframework.web.filter.OncePerRequestFilter; | ||||
|  | ||||
| import java.io.IOException; | ||||
|  | ||||
| /** | ||||
|  * Flowable Web 过滤器,将 userId 设置到 {@link org.flowable.common.engine.impl.identity.Authentication} 中 | ||||
|  * | ||||
|  * @author jason | ||||
|  */ | ||||
| public class FlowableWebFilter extends OncePerRequestFilter { | ||||
|  | ||||
|     @Override | ||||
|     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) | ||||
|             throws ServletException, IOException { | ||||
|         try { | ||||
|             // 设置工作流的用户 | ||||
|             Long userId = SecurityFrameworkUtils.getLoginUserId(); | ||||
|             if (userId != null) { | ||||
|                 FlowableUtils.setAuthenticatedUserId(userId); | ||||
|             } | ||||
|             // 过滤 | ||||
|             chain.doFilter(request, response); | ||||
|         } finally { | ||||
|             // 清理 | ||||
|             FlowableUtils.clearAuthenticatedUserId(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -9,8 +9,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| import cn.iocoder.yudao.framework.common.util.date.DateUtils; | ||||
| import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | ||||
| import cn.iocoder.yudao.framework.common.util.object.PageUtils; | ||||
| import cn.iocoder.yudao.framework.flowable.core.util.BpmnModelUtils; | ||||
| import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils; | ||||
| 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.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; | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils; | ||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; | ||||
| import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||||
| import org.flowable.engine.delegate.DelegateExecution; | ||||
| import org.junit.jupiter.api.Test; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV