BPM:移除 flowable starter 模块,融合到 bpm 模块中

This commit is contained in:
YunaiV
2024-03-20 13:14:53 +08:00
parent 29a0fbfc43
commit 559bab571a
25 changed files with 148 additions and 160 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -1,6 +0,0 @@
/**
* 自定义 Event 实现,提供方便业务接入的 Listener
*
* @author 芋道源码
*/
package cn.iocoder.yudao.module.bpm.framework.bpm.core.event;

View File

@ -1,4 +0,0 @@
/**
* 占位
*/
package cn.iocoder.yudao.module.bpm.framework.bpm.core;

View File

@ -1,6 +0,0 @@
/**
* 提供给 Activiti 和 Flowable 的通用封装
*
* @author 芋道源码
*/
package cn.iocoder.yudao.module.bpm.framework.bpm;

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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)); // 保证有序!!!

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View File

@ -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;

View File

@ -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;