add - 新增流程推算执行顺序的节点信息

This commit is contained in:
wangli 2023-10-11 14:01:53 +08:00
parent 912bf296f4
commit 35cc172cf3
15 changed files with 163 additions and 91 deletions

View File

@ -55,6 +55,7 @@ public enum BpmErrorCode implements IProjectRespCode {
FORM_DEFINITION_PARSER_ERROR("08002", "表单定义内容解析出错"),
// ========== form Instance 09-001 ==========
// ========== flowable Engine 10-001 ==========
ENGINE_EXECUTION_LOST_ID_ERROR("10001", "Execution 丢失"),
// // ========== 流程模型 01-001 ==========

View File

@ -205,7 +205,7 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic
return true;
}
log.error("[updateProcessDefinitionState][流程定义({}) 修改未知状态({})]", processDefinitionId, status);
return null;
return false;
}

View File

@ -1,5 +1,6 @@
package cn.axzo.workflow.core.service.support;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import cn.azxo.framework.common.utils.StringUtils;
import org.flowable.common.engine.api.delegate.Expression;
import org.flowable.common.engine.impl.interceptor.Command;
@ -7,10 +8,10 @@ import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl;
import java.io.Serializable;
import java.util.Map;
import static cn.axzo.workflow.core.common.enums.BpmErrorCode.ENGINE_EXECUTION_LOST_ID_ERROR;
/**
* 流程定义内部的表达式评估命令器
@ -24,17 +25,13 @@ public class ExpressionConditionCmd implements Command<Boolean>, Serializable {
protected final String processInstanceId;
protected final String exp;
protected Map<String, Object> variableMap;
public ExpressionConditionCmd(RuntimeService runtimeService,
ProcessEngineConfigurationImpl processEngineConfiguration,
String processInstanceId, String exp,
Map<String, Object> variableMap) {
String processInstanceId, String exp) {
this.runtimeService = runtimeService;
this.processEngineConfiguration = processEngineConfiguration;
this.processInstanceId = processInstanceId;
this.exp = exp;
this.variableMap = variableMap;
}
@Override
@ -42,11 +39,12 @@ public class ExpressionConditionCmd implements Command<Boolean>, Serializable {
Expression expression = processEngineConfiguration.getExpressionManager().createExpression(this.exp);
ExecutionEntity executionEntity;
if (StringUtils.isNotBlank(this.processInstanceId)) {
executionEntity =
(ExecutionEntity) runtimeService.createProcessInstanceQuery().processInstanceId(this.processInstanceId).includeProcessVariables().singleResult();
executionEntity = (ExecutionEntity) runtimeService.createProcessInstanceQuery()
.processInstanceId(this.processInstanceId).includeProcessVariables().singleResult();
} else {
executionEntity = new ExecutionEntityImpl();
executionEntity.setVariables(variableMap);
// 不能单纯的 new ExecutionEntityImpl, 后续在调用 setVariables ,
// 引擎用了很多 ExecutionEntityImpl 的其他属性,这些属性都是在 new 不会自动生成的
throw new WorkflowEngineException(ENGINE_EXECUTION_LOST_ID_ERROR);
}
Object value = expression.getValue(executionEntity);
return value != null && "true".equals(value.toString());

View File

@ -1,15 +1,19 @@
package cn.axzo.workflow.core.service.support;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.service.support.forecast.AbstractForecast;
import cn.axzo.workflow.core.service.support.forecast.Forecast;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.StartEvent;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.ResolvableType;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.ArrayList;
@ -20,11 +24,13 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import static cn.axzo.workflow.core.common.enums.BpmErrorCode.PROCESS_INSTANCE_NOT_EXISTS;
/**
* 审批实例的运行节点预测/推断
* <p>
* 一个流程定义中所有的节点都是 FlowElement 类型, FlowElement 是引擎中的一个基类, 如果要完整推测审批流程执行顺序不亚于重写一套流程执行引擎
* 所以这里仅按照业务所需的节点类型进行处理.
* 所以这里仅按照业务所需的节点类型进行处理.并且每个节点都未处理出口是多个的情况,目前仅支持单出口!
* <p>
* 全量的接入类型如下:
* <pre>
@ -84,25 +90,33 @@ import java.util.Optional;
@Service
public class FlowNodeForecastService implements InitializingBean {
public final static Map<Class, AbstractForecast> FORECAST_MAP = new HashMap<>();
public final static Map<Class<?>, AbstractForecast<? extends FlowElement>> FORECAST_MAP = new HashMap<>();
@Resource
private RepositoryService repositoryService;
@Resource
private List<Forecast> forecasts;
private RuntimeService runtimeService;
@Resource
private List<Forecast<? extends FlowElement>> forecasts;
/**
* 执行流程预测
* 执行运行中的流程预测
* <p>
* 某个实例的 bpmnModel 可以通过该方式得到:
* <pre>
* HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
* BpmnModel bpmnModel = repositoryService.getBpmnModel(instance.getProcessDefinitionId());
* </pre>
* 已完成的流程可以直接查询流程审批记录就行
*
* @param bpmnModel 指定流程定义配置元数据
* @param variableMap 指定流程实例的全量变量表
* @param processInstanceId 指定运行时的流程实例 ID
*/
public List<FlowElement> performProcessForecasting(BpmnModel bpmnModel, Map<String, Object> variableMap) {
public List<FlowElement> performProcessForecasting(String processInstanceId) {
if (!StringUtils.hasLength(processInstanceId)) {
throw new WorkflowEngineException(PROCESS_INSTANCE_NOT_EXISTS);
}
ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId)
.includeProcessVariables().singleResult();
if (Objects.isNull(instance)) {
throw new WorkflowEngineException(PROCESS_INSTANCE_NOT_EXISTS);
}
BpmnModel bpmnModel = repositoryService.getBpmnModel(instance.getProcessDefinitionId());
// 保持推测出来的节点执行顺序的容器
List<FlowElement> orderNodes = new ArrayList<>();
// 流程定义中所有的FlowElement
@ -113,17 +127,17 @@ public class FlowNodeForecastService implements InitializingBean {
addOrderFlowNodes(orderNodes, startNode);
});
startForecasting(orderNodes, variableMap);
startForecasting(orderNodes, instance);
return orderNodes;
}
private void startForecasting(List<FlowElement> orderNodes, Map<String, Object> variableMap) {
private void startForecasting(List<FlowElement> orderNodes, ProcessInstance instance) {
getLastNode(orderNodes).ifPresent(flowElement -> forecasts.forEach(i -> {
if (i.support(flowElement)) {
List<FlowElement> list = i.nextFlowElement(flowElement, variableMap);
List<FlowElement> list = i.nextFlowElement(flowElement, instance);
if (!CollectionUtils.isEmpty(list)) {
addOrderFlowNodes(orderNodes, list.get(0));
startForecasting(orderNodes, variableMap);
startForecasting(orderNodes, instance);
}
}
}));
@ -162,7 +176,7 @@ public class FlowNodeForecastService implements InitializingBean {
forecasts.forEach(i -> {
Class<?> rawClass = ResolvableType.forClass(i.getClass(), Forecast.class)
.getSuperType().getGenerics()[0].getRawClass();
FORECAST_MAP.put(rawClass, (AbstractForecast) i);
FORECAST_MAP.put(rawClass, (AbstractForecast<? extends FlowElement>) i);
});
}
}

View File

@ -1,6 +1,9 @@
package cn.axzo.workflow.core.service.support.forecast;
import cn.axzo.workflow.core.service.support.forecast.impl.SequenceFlowForecasting;
import cn.axzo.workflow.core.service.support.spring.FlowableConditionEvaluatorAware;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
@ -13,7 +16,7 @@ import java.util.stream.Collectors;
import static cn.axzo.workflow.core.service.support.FlowNodeForecastService.FORECAST_MAP;
/**
* TODO
* 抽象的流程预测
*
* @author wangli
* @since 2023/10/10 10:55
@ -22,48 +25,33 @@ public abstract class AbstractForecast<T extends FlowElement> implements Forecas
private FlowableConditionEvaluator conditionEvaluator;
/**
* 将节点来源强制转换成目标节点类型
*
* @param sourceFlowElements
* @return
*/
private List<T> cast(List<FlowElement> sourceFlowElements) {
if (CollectionUtils.isEmpty(sourceFlowElements)) {
return Collections.emptyList();
}
List<T> convertList = new ArrayList<>(sourceFlowElements.size());
sourceFlowElements.forEach(i -> convertList.add((T) i));
return convertList;
}
@Override
public final List<FlowElement> nextFlowElement(FlowElement sourceFlowElement, Map<String, Object> variableMap) {
return forecastNextNodes(getOutgoing((T) sourceFlowElement), variableMap);
public final List<FlowElement> nextFlowElement(FlowElement sourceFlowElement, ProcessInstance instance) {
return forecastNextNodes(getOutgoing((T) sourceFlowElement), instance);
}
protected abstract List<? extends FlowElement> getOutgoing(T flowElement);
/**
* 计算出口节点,此时的入参 sourceFlowElement 已经不等泛型类
* 计算出口节点,此时的入参 sourceFlowElement 已经不等于泛型类
*
* @param sourceFlowElements 当前节点的出口节点集合
* @param variableMap 流程实例变量
* @param instance 流程实例
* @return
*/
protected final List<FlowElement> forecastNextNodes(List<? extends FlowElement> sourceFlowElements, Map<String
, Object> variableMap) {
private List<FlowElement> forecastNextNodes(List<? extends FlowElement> sourceFlowElements,
ProcessInstance instance) {
if (CollectionUtils.isEmpty(sourceFlowElements)) {
return Collections.emptyList();
}
Map<? extends Class<? extends FlowElement>, ? extends List<? extends FlowElement>> elementGroup =
Map<Class<? extends FlowElement>, List<FlowElement>> elementGroup =
sourceFlowElements.stream().collect(Collectors.groupingBy(FlowElement::getClass, Collectors.toList()));
List result = new ArrayList();
List<FlowElement> result = new ArrayList<>();
elementGroup.forEach((k, v) -> {
AbstractForecast forecast = FORECAST_MAP.get(k);
if (Objects.nonNull(forecast)) {
result.addAll(forecast.calcRealOutgoingNodes(v, variableMap));
result.addAll(forecast.calcRealOutgoingNodes(v, instance));
}
});
return result;
@ -73,10 +61,10 @@ public abstract class AbstractForecast<T extends FlowElement> implements Forecas
* 由于每个节点的下级节点是多个,且有可能是不同类型, 在预测下级节点时,需要将同类型的节点传递给对应类型的预测器来计算
*
* @param flowElements
* @param variableMap
* @param instance
* @return
*/
public List<? extends FlowElement> calcRealOutgoingNodes(List<T> flowElements, Map<String, Object> variableMap) {
public List<? extends FlowElement> calcRealOutgoingNodes(List<T> flowElements, ProcessInstance instance) {
return Collections.emptyList();
}
@ -85,7 +73,15 @@ public abstract class AbstractForecast<T extends FlowElement> implements Forecas
this.conditionEvaluator = conditionEvaluator;
}
protected final Boolean conditionOn(String exp, Map<String, Object> variableMap) {
return conditionEvaluator.conditionOn(exp, variableMap);
/**
* 提供给所有实现类的通用调用引擎评估节点可能存在的条件表达式
*
* @param exp 表达式
* @param processInstanceId 实例 ID
* @return
* @see SequenceFlowForecasting#calcRealOutgoingNodes(List, ProcessInstance) 参考这里的用法
*/
protected final Boolean conditionOn(String exp, String processInstanceId) {
return conditionEvaluator.conditionOn(exp, processInstanceId);
}
}

View File

@ -7,8 +7,6 @@ import org.flowable.engine.RuntimeService;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 基于 Flowable 引擎能力的表达式评估器
*
@ -26,13 +24,14 @@ public class FlowableConditionEvaluator {
/**
* 计算表达式真假, 需结合引擎能力
*
* @param exp 条件表达式
* @param variables 流程变量
* @param exp 条件表达式
* @param variables 流程变量
* @param processInstanceId 流程实例 ID (可与 variables 参入二选一, 如果都传,则直接用实例 ID 去引擎中查真实的变量表)
* @return true Or false
*/
public Boolean conditionOn(String exp, Map<String, Object> variables) {
public Boolean conditionOn(String exp, String processInstanceId) {
return managementService.executeCommand(new ExpressionConditionCmd(runtimeService,
processEngineConfiguration, null, exp, variables));
processEngineConfiguration, processInstanceId, exp));
}
}

View File

@ -1,9 +1,9 @@
package cn.axzo.workflow.core.service.support.forecast;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.engine.runtime.ProcessInstance;
import java.util.List;
import java.util.Map;
/**
* 流程节点顺序预测接口
@ -19,7 +19,7 @@ public interface Forecast<T extends FlowElement> {
* @param t Flowable 的子类
* @return
*/
Boolean support(FlowElement t);
Boolean support(FlowElement flowElement);
/**
* 通过给定的源 FlowElement 结合 Flowable 的条件评估器去计算符合条件的下一级节点
@ -27,9 +27,9 @@ public interface Forecast<T extends FlowElement> {
* 由于标准的 BPMN 协议中很多节点都是允许多条路并行走的, 所以接口返回模型定义为 List 集合,
* 但实际上, 按照现在的业务定义, 只允许存在 0 1 个下一级节点.
*
* @param sourceFlowElement 级节点
* @param variableMap 流程运行实例的完全变量
* @param sourceFlowElement 当前节点, 实现类中该方法则是通过 getOutgoing 获取出口节点集合, 并调用对应类型中的 calcRealOutgoingNodes 查找真正的下级节点
* @param instance 流程运行实例
* @return
*/
List<FlowElement> nextFlowElement(FlowElement sourceFlowElement, Map<String, Object> variableMap);
List<FlowElement> nextFlowElement(FlowElement sourceFlowElement, ProcessInstance instance);
}

View File

@ -3,15 +3,15 @@ package cn.axzo.workflow.core.service.support.forecast.impl;
import cn.axzo.workflow.core.service.support.forecast.AbstractForecast;
import org.flowable.bpmn.model.EndEvent;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* TODO
* 结束节点
*
* @author wangli
* @since 2023/10/10 18:58
@ -29,11 +29,10 @@ public class EndEventForecasting extends AbstractForecast<EndEvent> {
}
@Override
public List<? extends FlowElement> calcRealOutgoingNodes(List<EndEvent> flowElements,
Map<String, Object> variableMap) {
public List<? extends FlowElement> calcRealOutgoingNodes(List<EndEvent> flowElements, ProcessInstance instance) {
if (CollectionUtils.isEmpty(flowElements)) {
return Collections.emptyList();
}
return super.calcRealOutgoingNodes(flowElements, variableMap);
return super.calcRealOutgoingNodes(flowElements, instance);
}
}

View File

@ -0,0 +1,42 @@
package cn.axzo.workflow.core.service.support.forecast.impl;
import cn.axzo.workflow.core.service.support.forecast.AbstractForecast;
import org.flowable.bpmn.model.ExclusiveGateway;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.List;
/**
* 排他网关
*
* @author wangli
* @since 2023/10/11 09:57
*/
@Component
public class ExclusiveGatewayForecasting extends AbstractForecast<ExclusiveGateway> {
@Override
public Boolean support(FlowElement flowElement) {
return flowElement instanceof ExclusiveGateway;
}
@Override
protected List<? extends FlowElement> getOutgoing(ExclusiveGateway flowElement) {
return flowElement.getOutgoingFlows();
}
@Override
public List<? extends FlowElement> calcRealOutgoingNodes(List<ExclusiveGateway> flowElements,
ProcessInstance instance) {
if (CollectionUtils.isEmpty(flowElements)) {
return Collections.emptyList();
}
if (flowElements.size() == 1) {
return flowElements;
}
return super.calcRealOutgoingNodes(flowElements, instance);
}
}

View File

@ -4,13 +4,13 @@ import cn.axzo.workflow.core.service.support.forecast.AbstractForecast;
import com.google.common.collect.Lists;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.SequenceFlow;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
@ -34,7 +34,7 @@ public class SequenceFlowForecasting extends AbstractForecast<SequenceFlow> {
@Override
public List<? extends FlowElement> calcRealOutgoingNodes(List<SequenceFlow> flowElements,
Map<String, Object> variableMap) {
ProcessInstance instance) {
if (CollectionUtils.isEmpty(flowElements)) {
return Collections.emptyList();
}
@ -42,7 +42,7 @@ public class SequenceFlowForecasting extends AbstractForecast<SequenceFlow> {
// 评估顺序流集合中条件为 true 的顺序流
List<SequenceFlow> executableFlows =
flowElements.stream().filter(i -> StringUtils.hasLength(i.getConditionExpression()))
.filter(i -> conditionOn(i.getConditionExpression(), variableMap)).collect(Collectors.toList());
.filter(i -> conditionOn(i.getConditionExpression(), instance.getId())).collect(Collectors.toList());
if (!CollectionUtils.isEmpty(executableFlows) && executableFlows.size() == 1) {
return Lists.newArrayList(executableFlows.get(0));
}
@ -53,6 +53,6 @@ public class SequenceFlowForecasting extends AbstractForecast<SequenceFlow> {
if (!CollectionUtils.isEmpty(defaultFlows) && defaultFlows.size() == 1) {
return Lists.newArrayList(defaultFlows.get(0));
}
return super.calcRealOutgoingNodes(flowElements, variableMap);
return super.calcRealOutgoingNodes(flowElements, instance);
}
}

View File

@ -26,5 +26,4 @@ public class StartEventForecasting extends AbstractForecast<StartEvent> {
return flowElement.getOutgoingFlows();
}
}

View File

@ -3,12 +3,12 @@ package cn.axzo.workflow.core.service.support.forecast.impl;
import cn.axzo.workflow.core.service.support.forecast.AbstractForecast;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* 用户任务节点预测
@ -29,14 +29,13 @@ public class UserTaskForecasting extends AbstractForecast<UserTask> {
}
@Override
public List<? extends FlowElement> calcRealOutgoingNodes(List<UserTask> flowElements,
Map<String, Object> variableMap) {
public List<? extends FlowElement> calcRealOutgoingNodes(List<UserTask> flowElements, ProcessInstance instance) {
if (CollectionUtils.isEmpty(flowElements)) {
return Collections.emptyList();
}
if (flowElements.size() == 1) {
return flowElements;
}
return super.calcRealOutgoingNodes(flowElements, variableMap);
return super.calcRealOutgoingNodes(flowElements, instance);
}
}

View File

@ -1,4 +1,6 @@
package cn.axzo.workflow.core.service.support.forecast;
package cn.axzo.workflow.core.service.support.spring;
import cn.axzo.workflow.core.service.support.forecast.FlowableConditionEvaluator;
/**
* 按照 Spring 规范提供一个 Flowable Condition Evaluator Aware

View File

@ -0,0 +1,28 @@
package cn.axzo.workflow.core.service.support.spring;
import cn.axzo.workflow.core.service.support.forecast.FlowableConditionEvaluator;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
/**
* 自定义的 Aware 模式的接口处理类
*
* @author wangli
* @since 2023/10/11 10:23
*/
@Component
public class FlowableConditionEvaluatorAwareProcessor implements BeanPostProcessor {
private final FlowableConditionEvaluator conditionEvaluator;
public FlowableConditionEvaluatorAwareProcessor(FlowableConditionEvaluator conditionEvaluator) {this.conditionEvaluator = conditionEvaluator;}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof FlowableConditionEvaluatorAware) {
((FlowableConditionEvaluatorAware) bean).setConditionEvaluator(conditionEvaluator);
}
return bean;
}
}

View File

@ -2,11 +2,9 @@ package cn.axzo.workflow.server.controller.web;
import cn.axzo.workflow.core.service.support.FlowNodeForecastService;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
@ -37,11 +35,8 @@ public class TestController {
@GetMapping("/test")
public void test(@RequestParam String processInstanceId) {
HistoricProcessInstance instance =
historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
BpmnModel bpmnModel = repositoryService.getBpmnModel(instance.getProcessDefinitionId());
List<FlowElement> flowElements = forecastService.performProcessForecasting(bpmnModel,
instance.getProcessVariables());
List<FlowElement> flowElements = forecastService.performProcessForecasting(processInstanceId);
System.out.println("flowElements = " + flowElements);
}