Merge branch 'feature/REQ-1309' into dev
This commit is contained in:
commit
150737b67c
@ -102,7 +102,7 @@ public interface ProcessInstanceApi {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/api/process/instance/node/calc")
|
||||
CommonResponse<List<ProcessNodeDetailVO>> processInstanceNodeCalc(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam(required = false) String processInstanceId,
|
||||
@Nullable @RequestParam(required = false) String tenantId);
|
||||
@GetMapping("/api/process/instance/node/forecasting")
|
||||
CommonResponse<List<ProcessNodeDetailVO>> processInstanceNodeForecast(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam(required = false) String processInstanceId,
|
||||
@Nullable @RequestParam(required = false) String tenantId);
|
||||
}
|
||||
|
||||
@ -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 ==========
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
package cn.axzo.workflow.core.common.utils;
|
||||
|
||||
/**
|
||||
* @author wangli
|
||||
* @since 2023/10/9 15:10
|
||||
*/
|
||||
public class BpmnNativeQueryUtil {
|
||||
|
||||
public static String sqlConnectors(StringBuilder stringBuilder) {
|
||||
if (stringBuilder.indexOf("WHERE") < 0) {
|
||||
return " WHERE";
|
||||
} else if (stringBuilder.indexOf("LEFT") >= 0 && stringBuilder.indexOf("ON") < 0) {
|
||||
return " ON";
|
||||
}
|
||||
return " AND";
|
||||
}
|
||||
|
||||
public static String countSql(StringBuilder stringBuilder) {
|
||||
int start;
|
||||
if ((start = stringBuilder.indexOf("SELECT")) < 0) {
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
if (stringBuilder.indexOf("LEFT JOIN") < 0) {
|
||||
return stringBuilder.replace(start + 7, 8, "count(1)").toString();
|
||||
} else {
|
||||
return stringBuilder.replace(start + 7, 10, "count(1)").toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -98,5 +98,5 @@ public interface BpmnProcessInstanceService {
|
||||
|
||||
BpmPageResult<HistoricProcessInstanceVO> historicProcessInstancePage(HistoricProcessInstanceSearchDTO dto);
|
||||
|
||||
List<ProcessNodeDetailVO> getProcessNodes(String processInstanceId, @Nullable String tenantId);
|
||||
List<ProcessNodeDetailVO> getProcessInstanceNodeForecast(String processInstanceId, String tenantId);
|
||||
}
|
||||
|
||||
@ -18,40 +18,21 @@ import cn.axzo.workflow.common.model.response.bpmn.process.ProcessNodeDetailVO;
|
||||
import cn.axzo.workflow.core.common.enums.BpmnProcessTaskResultEnum;
|
||||
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
|
||||
import cn.axzo.workflow.core.common.utils.BpmCollectionUtils;
|
||||
import cn.axzo.workflow.core.repository.mapper.InfoMapper;
|
||||
import cn.axzo.workflow.core.service.BpmnProcessDefinitionService;
|
||||
import cn.axzo.workflow.core.service.BpmnProcessInstanceService;
|
||||
import cn.axzo.workflow.core.service.converter.BpmnHistoricProcessInstanceConverter;
|
||||
import cn.axzo.workflow.core.service.converter.BpmnProcessInstanceConverter;
|
||||
import cn.axzo.workflow.core.service.converter.BpmnProcessInstancePageItemConverter;
|
||||
import cn.axzo.workflow.core.service.support.FlowNodeForecastService;
|
||||
import cn.axzo.workflow.core.service.support.ProcessGraphicService;
|
||||
import cn.azxo.framework.common.utils.StringUtils;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.bpmn.model.Artifact;
|
||||
import org.flowable.bpmn.model.Association;
|
||||
import org.flowable.bpmn.model.BpmnModel;
|
||||
import org.flowable.bpmn.model.ConditionalEventDefinition;
|
||||
import org.flowable.bpmn.model.ErrorEventDefinition;
|
||||
import org.flowable.bpmn.model.EscalationEventDefinition;
|
||||
import org.flowable.bpmn.model.Event;
|
||||
import org.flowable.bpmn.model.EventDefinition;
|
||||
import org.flowable.bpmn.model.FlowElement;
|
||||
import org.flowable.bpmn.model.FlowNode;
|
||||
import org.flowable.bpmn.model.GraphicInfo;
|
||||
import org.flowable.bpmn.model.Lane;
|
||||
import org.flowable.bpmn.model.MessageEventDefinition;
|
||||
import org.flowable.bpmn.model.Pool;
|
||||
import org.flowable.bpmn.model.SequenceFlow;
|
||||
import org.flowable.bpmn.model.ServiceTask;
|
||||
import org.flowable.bpmn.model.SignalEventDefinition;
|
||||
import org.flowable.bpmn.model.StartEvent;
|
||||
import org.flowable.bpmn.model.SubProcess;
|
||||
import org.flowable.bpmn.model.TextAnnotation;
|
||||
import org.flowable.bpmn.model.TimerEventDefinition;
|
||||
import org.flowable.bpmn.model.UserTask;
|
||||
import org.flowable.bpmn.model.VariableListenerEventDefinition;
|
||||
import org.flowable.common.engine.impl.db.SuspensionState;
|
||||
import org.flowable.common.engine.impl.identity.Authentication;
|
||||
import org.flowable.engine.HistoryService;
|
||||
@ -75,7 +56,6 @@ import javax.annotation.Nullable;
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@ -103,6 +83,8 @@ import static cn.axzo.workflow.core.common.enums.BpmErrorCode.PROCESS_INSTANCE_C
|
||||
import static cn.axzo.workflow.core.common.enums.BpmErrorCode.PROCESS_INSTANCE_ID_NOT_EXISTS;
|
||||
import static cn.axzo.workflow.core.common.enums.BpmErrorCode.PROCESS_INSTANCE_NOT_EXISTS;
|
||||
import static cn.axzo.workflow.core.common.enums.BpmErrorCode.PROCESS_OPERATION_PARAM_VALID_ERROR;
|
||||
import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.countSql;
|
||||
import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.sqlConnectors;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@ -117,8 +99,6 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic
|
||||
@Resource
|
||||
private HistoryService historyService;
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
@Resource
|
||||
private BpmnProcessInstancePageItemConverter instancePageItemConverter;
|
||||
@Resource
|
||||
private BpmnProcessInstanceConverter instanceConverter;
|
||||
@ -126,10 +106,10 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic
|
||||
private BpmnHistoricProcessInstanceConverter historicProcessInstanceConverter;
|
||||
@Resource
|
||||
private ManagementService managementService;
|
||||
|
||||
protected List<String> eventElementTypes = new ArrayList<>();
|
||||
protected Map<String, InfoMapper> propertyMappers = new HashMap<>();
|
||||
|
||||
@Resource
|
||||
private ProcessGraphicService graphicService;
|
||||
@Resource
|
||||
private FlowNodeForecastService forecastService;
|
||||
|
||||
@Override
|
||||
public HistoricProcessInstance getProcessInstanceByBusinessKey(String businessKey, @Nullable String tenantId,
|
||||
@ -228,7 +208,7 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic
|
||||
return true;
|
||||
}
|
||||
log.error("[updateProcessDefinitionState][流程定义({}) 修改未知状态({})]", processDefinitionId, status);
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -444,12 +424,13 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic
|
||||
}
|
||||
}
|
||||
// Gather completed flows
|
||||
List<String> completedFlows = gatherCompletedFlows(completedActivityInstances, currentElements, bpmnModel);
|
||||
List<String> completedFlows = graphicService.gatherCompletedFlows(completedActivityInstances, currentElements
|
||||
, bpmnModel);
|
||||
|
||||
Set<String> completedElements = new HashSet<>(completedActivityInstances);
|
||||
completedElements.addAll(completedFlows);
|
||||
|
||||
ObjectNode displayNode = processProcessElements(bpmnModel, completedElements, currentElements,
|
||||
ObjectNode displayNode = graphicService.processProcessElements(bpmnModel, completedElements, currentElements,
|
||||
new ArrayList<>(), processInstanceId);
|
||||
if (!CollectionUtils.isEmpty(completedActivityInstances)) {
|
||||
ArrayNode completedActivities = displayNode.putArray("completedActivities");
|
||||
@ -553,7 +534,6 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProcessNodeDetailVO> getProcessNodes(String processInstanceId, @Nullable String tenantId) {
|
||||
HistoricProcessInstance instance =
|
||||
historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
|
||||
@ -594,392 +574,43 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic
|
||||
return resultList;
|
||||
}
|
||||
|
||||
private List<String> gatherCompletedFlows(Set<String> completedActivityInstances,
|
||||
Set<String> currentActivityInstances, BpmnModel pojoModel) {
|
||||
@Override
|
||||
public List<ProcessNodeDetailVO> getProcessInstanceNodeForecast(String processInstanceId,
|
||||
@Nullable String tenantId) {
|
||||
ProcessInstance instance = runtimeService.createProcessInstanceQuery()
|
||||
.processInstanceId(processInstanceId).processInstanceTenantId(tenantId).singleResult();
|
||||
if (Objects.isNull(instance)) {
|
||||
throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, processInstanceId);
|
||||
}
|
||||
List<FlowElement> flowElements = forecastService.performProcessForecasting(processInstanceId, instance);
|
||||
|
||||
List<String> completedFlows = new ArrayList<>();
|
||||
List<String> activities = new ArrayList<>(completedActivityInstances);
|
||||
activities.addAll(currentActivityInstances);
|
||||
|
||||
// TODO: not a robust way of checking when parallel paths are active, should be revisited
|
||||
// Go over all activities and check if it's possible to match any outgoing paths against the activities
|
||||
for (FlowElement activity : pojoModel.getMainProcess().getFlowElements()) {
|
||||
if (activity instanceof FlowNode) {
|
||||
int index = activities.indexOf(activity.getId());
|
||||
if (index >= 0 && index + 1 < activities.size()) {
|
||||
List<SequenceFlow> outgoingFlows = ((FlowNode) activity).getOutgoingFlows();
|
||||
for (SequenceFlow flow : outgoingFlows) {
|
||||
String destinationFlowId = flow.getTargetRef();
|
||||
if (destinationFlowId.equals(activities.get(index + 1))) {
|
||||
completedFlows.add(flow.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
List<ProcessNodeDetailVO> resultList = new ArrayList<>(flowElements.size() + 1);
|
||||
flowElements.stream().filter(UserTask.class::isInstance).forEach(i -> {
|
||||
UserTask userTask = (UserTask) i;
|
||||
ProcessNodeDetailVO node = new ProcessNodeDetailVO().setId(userTask.getId())
|
||||
.setName(userTask.getName())
|
||||
.setFormKey(userTask.getFormKey());
|
||||
if (userTask.getBehavior() instanceof MultiInstanceActivityBehavior) {
|
||||
MultiInstanceActivityBehavior behavior = (MultiInstanceActivityBehavior) userTask.getBehavior();
|
||||
node.setNodeMode(Objects.equals(AND_SIGN_EXPRESSION, behavior.getCompletionCondition()) ? AND : OR);
|
||||
} else if (userTask.getBehavior() instanceof UserTaskActivityBehavior) {
|
||||
node.setNodeMode(BpmnFlowNodeMode.GENERAL);
|
||||
}
|
||||
}
|
||||
return completedFlows;
|
||||
resultList.add(node);
|
||||
});
|
||||
|
||||
// 处理发起节点
|
||||
List<FlowElement> startNodes =
|
||||
flowElements.stream().filter(StartEvent.class::isInstance).collect(Collectors.toList());
|
||||
startNodes.forEach(i -> {
|
||||
StartEvent startEvent = (StartEvent) i;
|
||||
ProcessNodeDetailVO node = new ProcessNodeDetailVO()
|
||||
.setId(startEvent.getId())
|
||||
.setName(startEvent.getName())
|
||||
.setFormKey(startEvent.getFormKey())
|
||||
.setNodeMode(BpmnFlowNodeMode.STARTNODE);
|
||||
resultList.add(0, node);
|
||||
});
|
||||
return resultList;
|
||||
}
|
||||
|
||||
private ObjectNode processProcessElements(BpmnModel pojoModel, Set<String> completedElements,
|
||||
Set<String> currentElements, Collection<String> breakpoints,
|
||||
String processInstanceId) {
|
||||
ObjectNode displayNode = objectMapper.createObjectNode();
|
||||
GraphicInfo diagramInfo = new GraphicInfo();
|
||||
|
||||
ArrayNode elementArray = objectMapper.createArrayNode();
|
||||
ArrayNode flowArray = objectMapper.createArrayNode();
|
||||
ArrayNode collapsedArray = objectMapper.createArrayNode();
|
||||
|
||||
if (!CollectionUtils.isEmpty(pojoModel.getPools())) {
|
||||
ArrayNode poolArray = objectMapper.createArrayNode();
|
||||
boolean firstElement = true;
|
||||
for (Pool pool : pojoModel.getPools()) {
|
||||
ObjectNode poolNode = objectMapper.createObjectNode();
|
||||
poolNode.put("id", pool.getId());
|
||||
poolNode.put("name", pool.getName());
|
||||
GraphicInfo poolInfo = pojoModel.getGraphicInfo(pool.getId());
|
||||
fillGraphicInfo(poolNode, poolInfo, true);
|
||||
org.flowable.bpmn.model.Process process = pojoModel.getProcess(pool.getId());
|
||||
if (process != null && !CollectionUtils.isEmpty(process.getLanes())) {
|
||||
ArrayNode laneArray = objectMapper.createArrayNode();
|
||||
for (Lane lane : process.getLanes()) {
|
||||
ObjectNode laneNode = objectMapper.createObjectNode();
|
||||
laneNode.put("id", lane.getId());
|
||||
laneNode.put("name", lane.getName());
|
||||
fillGraphicInfo(laneNode, pojoModel.getGraphicInfo(lane.getId()), true);
|
||||
laneArray.add(laneNode);
|
||||
}
|
||||
poolNode.set("lanes", laneArray);
|
||||
}
|
||||
poolArray.add(poolNode);
|
||||
|
||||
double rightX = poolInfo.getX() + poolInfo.getWidth();
|
||||
double bottomY = poolInfo.getY() + poolInfo.getHeight();
|
||||
double middleX = poolInfo.getX() + (poolInfo.getWidth() / 2);
|
||||
if (firstElement || middleX < diagramInfo.getX()) {
|
||||
diagramInfo.setX(middleX);
|
||||
}
|
||||
if (firstElement || poolInfo.getY() < diagramInfo.getY()) {
|
||||
diagramInfo.setY(poolInfo.getY());
|
||||
}
|
||||
if (rightX > diagramInfo.getWidth()) {
|
||||
diagramInfo.setWidth(rightX);
|
||||
}
|
||||
if (bottomY > diagramInfo.getHeight()) {
|
||||
diagramInfo.setHeight(bottomY);
|
||||
}
|
||||
firstElement = false;
|
||||
}
|
||||
displayNode.set("pools", poolArray);
|
||||
|
||||
} else {
|
||||
// in initialize with fake x and y to make sure the minimal
|
||||
// values are set
|
||||
diagramInfo.setX(9999);
|
||||
diagramInfo.setY(1000);
|
||||
}
|
||||
|
||||
for (org.flowable.bpmn.model.Process process : pojoModel.getProcesses()) {
|
||||
processElements(process.getFlowElements(), pojoModel, elementArray, flowArray, collapsedArray,
|
||||
diagramInfo, completedElements, currentElements, breakpoints, null, processInstanceId);
|
||||
processArtifacts(process.getArtifacts(), pojoModel, elementArray, flowArray, diagramInfo);
|
||||
}
|
||||
|
||||
displayNode.set("elements", elementArray);
|
||||
displayNode.set("flows", flowArray);
|
||||
displayNode.set("collapsed", collapsedArray);
|
||||
|
||||
displayNode.put("diagramBeginX", diagramInfo.getX());
|
||||
displayNode.put("diagramBeginY", diagramInfo.getY());
|
||||
displayNode.put("diagramWidth", diagramInfo.getWidth());
|
||||
displayNode.put("diagramHeight", diagramInfo.getHeight());
|
||||
return displayNode;
|
||||
}
|
||||
|
||||
private void fillGraphicInfo(ObjectNode elementNode, GraphicInfo graphicInfo, boolean includeWidthAndHeight) {
|
||||
commonFillGraphicInfo(elementNode, graphicInfo.getX(), graphicInfo.getY(), graphicInfo.getWidth(),
|
||||
graphicInfo.getHeight(), includeWidthAndHeight);
|
||||
}
|
||||
|
||||
private void commonFillGraphicInfo(ObjectNode elementNode, double x, double y, double width, double height,
|
||||
boolean includeWidthAndHeight) {
|
||||
|
||||
elementNode.put("x", x);
|
||||
elementNode.put("y", y);
|
||||
if (includeWidthAndHeight) {
|
||||
elementNode.put("width", width);
|
||||
elementNode.put("height", height);
|
||||
}
|
||||
}
|
||||
|
||||
private void processElements(Collection<FlowElement> elementList, BpmnModel model, ArrayNode elementArray,
|
||||
ArrayNode flowArray,
|
||||
ArrayNode collapsedArray, GraphicInfo diagramInfo, Set<String> completedElements,
|
||||
Set<String> currentElements, Collection<String> breakpoints,
|
||||
ObjectNode collapsedNode, String processInstanceId) {
|
||||
|
||||
for (FlowElement element : elementList) {
|
||||
|
||||
ObjectNode elementNode = objectMapper.createObjectNode();
|
||||
if (completedElements != null) {
|
||||
elementNode.put("completed", completedElements.contains(element.getId()));
|
||||
}
|
||||
if (!breakpoints.isEmpty()) {
|
||||
elementNode.put("breakpoint", breakpoints.contains(element.getId()));
|
||||
}
|
||||
|
||||
if (currentElements != null) {
|
||||
elementNode.put("current", currentElements.contains(element.getId()));
|
||||
}
|
||||
|
||||
if (element instanceof SequenceFlow) {
|
||||
SequenceFlow flow = (SequenceFlow) element;
|
||||
elementNode.put("id", flow.getId());
|
||||
elementNode.put("type", "sequenceFlow");
|
||||
elementNode.put("sourceRef", flow.getSourceRef());
|
||||
elementNode.put("targetRef", flow.getTargetRef());
|
||||
elementNode.put("name", flow.getName());
|
||||
List<GraphicInfo> flowInfo = model.getFlowLocationGraphicInfo(flow.getId());
|
||||
if (!CollectionUtils.isEmpty(flowInfo)) {
|
||||
ArrayNode waypointArray = objectMapper.createArrayNode();
|
||||
for (GraphicInfo graphicInfo : flowInfo) {
|
||||
ObjectNode pointNode = objectMapper.createObjectNode();
|
||||
fillGraphicInfo(pointNode, graphicInfo, false);
|
||||
waypointArray.add(pointNode);
|
||||
fillDiagramInfo(graphicInfo, diagramInfo);
|
||||
}
|
||||
elementNode.set("waypoints", waypointArray);
|
||||
|
||||
String className = element.getClass().getSimpleName();
|
||||
if (propertyMappers.containsKey(className)) {
|
||||
elementNode.set("properties", propertyMappers.get(className).map(element));
|
||||
}
|
||||
|
||||
if (collapsedNode != null) {
|
||||
((ArrayNode) collapsedNode.get("flows")).add(elementNode);
|
||||
} else {
|
||||
flowArray.add(elementNode);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
elementNode.put("id", element.getId());
|
||||
elementNode.put("name", element.getName());
|
||||
|
||||
GraphicInfo graphicInfo = model.getGraphicInfo(element.getId());
|
||||
if (graphicInfo != null) {
|
||||
fillGraphicInfo(elementNode, graphicInfo, true);
|
||||
fillDiagramInfo(graphicInfo, diagramInfo);
|
||||
}
|
||||
|
||||
String className = element.getClass().getSimpleName();
|
||||
elementNode.put("type", className);
|
||||
fillEventTypes(className, element, elementNode);
|
||||
|
||||
if (element instanceof ServiceTask) {
|
||||
ServiceTask serviceTask = (ServiceTask) element;
|
||||
if (ServiceTask.MAIL_TASK.equals(serviceTask.getType())) {
|
||||
elementNode.put("taskType", "mail");
|
||||
|
||||
} else if ("camel".equals(serviceTask.getType())) {
|
||||
elementNode.put("taskType", "camel");
|
||||
|
||||
} else if ("mule".equals(serviceTask.getType())) {
|
||||
elementNode.put("taskType", "mule");
|
||||
|
||||
} else if (ServiceTask.HTTP_TASK.equals(serviceTask.getType())) {
|
||||
elementNode.put("taskType", "http");
|
||||
} else if (ServiceTask.SHELL_TASK.equals(serviceTask.getType())) {
|
||||
elementNode.put("taskType", "shell");
|
||||
}
|
||||
}
|
||||
|
||||
if (propertyMappers.containsKey(className)) {
|
||||
elementNode.set("properties", propertyMappers.get(className).map(element));
|
||||
}
|
||||
|
||||
if (collapsedNode != null) {
|
||||
((ArrayNode) collapsedNode.get("elements")).add(elementNode);
|
||||
} else {
|
||||
elementArray.add(elementNode);
|
||||
}
|
||||
|
||||
if (element instanceof SubProcess) {
|
||||
SubProcess subProcess = (SubProcess) element;
|
||||
ObjectNode newCollapsedNode = collapsedNode;
|
||||
// skip collapsed sub processes
|
||||
if (graphicInfo != null && graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) {
|
||||
elementNode.put("collapsed", "true");
|
||||
newCollapsedNode = objectMapper.createObjectNode();
|
||||
newCollapsedNode.put("id", subProcess.getId());
|
||||
newCollapsedNode.putArray("elements");
|
||||
newCollapsedNode.putArray("flows");
|
||||
collapsedArray.add(newCollapsedNode);
|
||||
}
|
||||
|
||||
processElements(subProcess.getFlowElements(), model, elementArray, flowArray, collapsedArray,
|
||||
diagramInfo, completedElements, currentElements, breakpoints, newCollapsedNode,
|
||||
processInstanceId);
|
||||
processArtifacts(subProcess.getArtifacts(), model, elementArray, flowArray, diagramInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processArtifacts(Collection<Artifact> artifactList, BpmnModel model, ArrayNode elementArray,
|
||||
ArrayNode flowArray, GraphicInfo diagramInfo) {
|
||||
|
||||
for (Artifact artifact : artifactList) {
|
||||
|
||||
if (artifact instanceof Association) {
|
||||
ObjectNode elementNode = objectMapper.createObjectNode();
|
||||
Association flow = (Association) artifact;
|
||||
elementNode.put("id", flow.getId());
|
||||
elementNode.put("type", "association");
|
||||
elementNode.put("sourceRef", flow.getSourceRef());
|
||||
elementNode.put("targetRef", flow.getTargetRef());
|
||||
fillWaypoints(flow.getId(), model, elementNode, diagramInfo);
|
||||
flowArray.add(elementNode);
|
||||
|
||||
} else {
|
||||
|
||||
ObjectNode elementNode = objectMapper.createObjectNode();
|
||||
elementNode.put("id", artifact.getId());
|
||||
|
||||
if (artifact instanceof TextAnnotation) {
|
||||
TextAnnotation annotation = (TextAnnotation) artifact;
|
||||
elementNode.put("text", annotation.getText());
|
||||
}
|
||||
|
||||
GraphicInfo graphicInfo = model.getGraphicInfo(artifact.getId());
|
||||
if (graphicInfo != null) {
|
||||
fillGraphicInfo(elementNode, graphicInfo, true);
|
||||
fillDiagramInfo(graphicInfo, diagramInfo);
|
||||
}
|
||||
|
||||
String className = artifact.getClass().getSimpleName();
|
||||
elementNode.put("type", className);
|
||||
|
||||
elementArray.add(elementNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fillDiagramInfo(GraphicInfo graphicInfo, GraphicInfo diagramInfo) {
|
||||
double rightX = graphicInfo.getX() + graphicInfo.getWidth();
|
||||
double bottomY = graphicInfo.getY() + graphicInfo.getHeight();
|
||||
double middleX = graphicInfo.getX() + (graphicInfo.getWidth() / 2);
|
||||
if (middleX < diagramInfo.getX()) {
|
||||
diagramInfo.setX(middleX);
|
||||
}
|
||||
if (graphicInfo.getY() < diagramInfo.getY()) {
|
||||
diagramInfo.setY(graphicInfo.getY());
|
||||
}
|
||||
if (rightX > diagramInfo.getWidth()) {
|
||||
diagramInfo.setWidth(rightX);
|
||||
}
|
||||
if (bottomY > diagramInfo.getHeight()) {
|
||||
diagramInfo.setHeight(bottomY);
|
||||
}
|
||||
}
|
||||
|
||||
private void fillWaypoints(String id, BpmnModel model, ObjectNode elementNode, GraphicInfo diagramInfo) {
|
||||
List<GraphicInfo> flowInfo = model.getFlowLocationGraphicInfo(id);
|
||||
ArrayNode waypointArray = objectMapper.createArrayNode();
|
||||
for (GraphicInfo graphicInfo : flowInfo) {
|
||||
ObjectNode pointNode = objectMapper.createObjectNode();
|
||||
fillGraphicInfo(pointNode, graphicInfo, false);
|
||||
waypointArray.add(pointNode);
|
||||
fillDiagramInfo(graphicInfo, diagramInfo);
|
||||
}
|
||||
elementNode.set("waypoints", waypointArray);
|
||||
}
|
||||
|
||||
private void fillEventTypes(String className, FlowElement element, ObjectNode elementNode) {
|
||||
if (eventElementTypes.contains(className)) {
|
||||
Event event = (Event) element;
|
||||
if (!CollectionUtils.isEmpty(event.getEventDefinitions())) {
|
||||
EventDefinition eventDef = event.getEventDefinitions().get(0);
|
||||
ObjectNode eventNode = objectMapper.createObjectNode();
|
||||
if (eventDef instanceof TimerEventDefinition) {
|
||||
TimerEventDefinition timerDef = (TimerEventDefinition) eventDef;
|
||||
eventNode.put("type", "timer");
|
||||
if (org.apache.commons.lang3.StringUtils.isNotEmpty(timerDef.getTimeCycle())) {
|
||||
eventNode.put("timeCycle", timerDef.getTimeCycle());
|
||||
}
|
||||
if (org.apache.commons.lang3.StringUtils.isNotEmpty(timerDef.getTimeDate())) {
|
||||
eventNode.put("timeDate", timerDef.getTimeDate());
|
||||
}
|
||||
if (org.apache.commons.lang3.StringUtils.isNotEmpty(timerDef.getTimeDuration())) {
|
||||
eventNode.put("timeDuration", timerDef.getTimeDuration());
|
||||
}
|
||||
|
||||
} else if (eventDef instanceof ConditionalEventDefinition) {
|
||||
ConditionalEventDefinition conditionalDef = (ConditionalEventDefinition) eventDef;
|
||||
eventNode.put("type", "conditional");
|
||||
if (org.apache.commons.lang3.StringUtils.isNotEmpty(conditionalDef.getConditionExpression())) {
|
||||
eventNode.put("condition", conditionalDef.getConditionExpression());
|
||||
}
|
||||
|
||||
} else if (eventDef instanceof ErrorEventDefinition) {
|
||||
ErrorEventDefinition errorDef = (ErrorEventDefinition) eventDef;
|
||||
eventNode.put("type", "error");
|
||||
if (org.apache.commons.lang3.StringUtils.isNotEmpty(errorDef.getErrorCode())) {
|
||||
eventNode.put("errorCode", errorDef.getErrorCode());
|
||||
}
|
||||
|
||||
} else if (eventDef instanceof EscalationEventDefinition) {
|
||||
EscalationEventDefinition escalationDef = (EscalationEventDefinition) eventDef;
|
||||
eventNode.put("type", "escalation");
|
||||
if (org.apache.commons.lang3.StringUtils.isNotEmpty(escalationDef.getEscalationCode())) {
|
||||
eventNode.put("escalationCode", escalationDef.getEscalationCode());
|
||||
}
|
||||
|
||||
} else if (eventDef instanceof SignalEventDefinition) {
|
||||
SignalEventDefinition signalDef = (SignalEventDefinition) eventDef;
|
||||
eventNode.put("type", "signal");
|
||||
if (org.apache.commons.lang3.StringUtils.isNotEmpty(signalDef.getSignalRef())) {
|
||||
eventNode.put("signalRef", signalDef.getSignalRef());
|
||||
}
|
||||
|
||||
} else if (eventDef instanceof MessageEventDefinition) {
|
||||
MessageEventDefinition messageDef = (MessageEventDefinition) eventDef;
|
||||
eventNode.put("type", "message");
|
||||
if (org.apache.commons.lang3.StringUtils.isNotEmpty(messageDef.getMessageRef())) {
|
||||
eventNode.put("messageRef", messageDef.getMessageRef());
|
||||
}
|
||||
|
||||
} else if (eventDef instanceof VariableListenerEventDefinition) {
|
||||
VariableListenerEventDefinition variableDef = (VariableListenerEventDefinition) eventDef;
|
||||
eventNode.put("type", "variable");
|
||||
if (org.apache.commons.lang3.StringUtils.isNotEmpty(variableDef.getVariableName())) {
|
||||
eventNode.put("variableName", variableDef.getVariableName());
|
||||
}
|
||||
if (org.apache.commons.lang3.StringUtils.isNotEmpty(variableDef.getVariableChangeType())) {
|
||||
eventNode.put("variableChangeType", variableDef.getVariableChangeType());
|
||||
}
|
||||
}
|
||||
elementNode.set("eventDefinition", eventNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String sqlConnectors(StringBuilder stringBuilder) {
|
||||
if (stringBuilder.indexOf("WHERE") < 0) {
|
||||
return " WHERE";
|
||||
} else if (stringBuilder.indexOf("LEFT") >= 0 && stringBuilder.indexOf("ON") < 0) {
|
||||
return " ON";
|
||||
}
|
||||
return " AND";
|
||||
}
|
||||
|
||||
public static String countSql(StringBuilder stringBuilder) {
|
||||
int start = 0;
|
||||
if ((start = stringBuilder.indexOf("SELECT")) < 0) {
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
return stringBuilder.replace(start + 7, 10, "count(1)").toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -16,7 +16,12 @@ import cn.axzo.workflow.core.service.converter.FormModelConverter;
|
||||
import org.flowable.common.engine.impl.db.SuspensionState;
|
||||
import org.flowable.engine.ManagementService;
|
||||
import org.flowable.engine.RepositoryService;
|
||||
import org.flowable.engine.repository.*;
|
||||
import org.flowable.engine.repository.Deployment;
|
||||
import org.flowable.engine.repository.Model;
|
||||
import org.flowable.engine.repository.ModelQuery;
|
||||
import org.flowable.engine.repository.NativeModelQuery;
|
||||
import org.flowable.engine.repository.ProcessDefinition;
|
||||
import org.flowable.engine.repository.ProcessDefinitionQuery;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@ -32,10 +37,16 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.axzo.workflow.common.constant.MetaInfoConstants.*;
|
||||
import static cn.axzo.workflow.core.common.enums.BpmErrorCode.*;
|
||||
import static cn.axzo.workflow.core.service.impl.FormModelServiceImpl.countSql;
|
||||
import static cn.axzo.workflow.core.service.impl.FormModelServiceImpl.sqlConnectors;
|
||||
import static cn.axzo.workflow.common.constant.MetaInfoConstants.MODEL_DESCRIPTION;
|
||||
import static cn.axzo.workflow.common.constant.MetaInfoConstants.MODEL_TYPE;
|
||||
import static cn.axzo.workflow.common.constant.MetaInfoConstants.MODEL_TYPE_PROCESS;
|
||||
import static cn.axzo.workflow.core.common.enums.BpmErrorCode.MODEL_ID_NOT_EXISTS;
|
||||
import static cn.axzo.workflow.core.common.enums.BpmErrorCode.MODEL_KEY_EXISTS;
|
||||
import static cn.axzo.workflow.core.common.enums.BpmErrorCode.MODEL_KEY_NOT_EXISTS;
|
||||
import static cn.axzo.workflow.core.common.enums.BpmErrorCode.MODEL_NOT_EXISTS;
|
||||
import static cn.axzo.workflow.core.common.enums.BpmErrorCode.PROCESS_DEFINITION_BPMN_NOT_EXISTS;
|
||||
import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.countSql;
|
||||
import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.sqlConnectors;
|
||||
|
||||
@Service
|
||||
public class BpmnProcessModelServiceImpl implements BpmnProcessModelService {
|
||||
|
||||
@ -85,8 +85,8 @@ import static cn.axzo.workflow.core.common.enums.BpmErrorCode.PROCESS_INSTANCE_N
|
||||
import static cn.axzo.workflow.core.common.enums.BpmErrorCode.TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF;
|
||||
import static cn.axzo.workflow.core.common.enums.BpmErrorCode.TASK_COMPLETE_FAIL_NOT_EXISTS;
|
||||
import static cn.axzo.workflow.core.common.utils.BpmCollectionUtils.convertSet;
|
||||
import static cn.axzo.workflow.core.service.impl.BpmnProcessInstanceServiceImpl.countSql;
|
||||
import static cn.axzo.workflow.core.service.impl.BpmnProcessInstanceServiceImpl.sqlConnectors;
|
||||
import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.countSql;
|
||||
import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.sqlConnectors;
|
||||
import static org.flowable.engine.impl.persistence.entity.CommentEntity.TYPE_COMMENT;
|
||||
|
||||
@Service
|
||||
|
||||
@ -32,9 +32,11 @@ import static cn.axzo.workflow.common.constant.BpmConstants.FORM_FILE_SUFFIX;
|
||||
import static cn.axzo.workflow.common.constant.MetaInfoConstants.MODEL_TYPE;
|
||||
import static cn.axzo.workflow.common.constant.MetaInfoConstants.MODEL_TYPE_FORM;
|
||||
import static cn.axzo.workflow.core.common.enums.BpmErrorCode.FORM_MODEL_NOT_EXISTS;
|
||||
import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.countSql;
|
||||
import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.sqlConnectors;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* 表单模型 Service
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2023/7/25 10:13
|
||||
@ -217,18 +219,4 @@ public class FormModelServiceImpl implements FormModelService {
|
||||
repositoryService.deleteModel(model.getId());
|
||||
}
|
||||
|
||||
public static String sqlConnectors(StringBuilder stringBuilder) {
|
||||
if (stringBuilder.indexOf("where") < 0) {
|
||||
return " where";
|
||||
}
|
||||
return " and";
|
||||
}
|
||||
|
||||
public static String countSql(StringBuilder stringBuilder) {
|
||||
int start = 0;
|
||||
if ((start = stringBuilder.indexOf("SELECT")) < 0) {
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
return stringBuilder.replace(start + 7, 8, "count(1)").toString();
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
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;
|
||||
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 java.io.Serializable;
|
||||
|
||||
import static cn.axzo.workflow.core.common.enums.BpmErrorCode.ENGINE_EXECUTION_LOST_ID_ERROR;
|
||||
|
||||
/**
|
||||
* 流程定义内部的表达式评估命令器
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2023/10/9 19:30
|
||||
*/
|
||||
public class ExpressionConditionCmd implements Command<Boolean>, Serializable {
|
||||
protected final RuntimeService runtimeService;
|
||||
protected final ProcessEngineConfigurationImpl processEngineConfiguration;
|
||||
protected final String processInstanceId;
|
||||
protected final String exp;
|
||||
|
||||
public ExpressionConditionCmd(RuntimeService runtimeService,
|
||||
ProcessEngineConfigurationImpl processEngineConfiguration,
|
||||
String processInstanceId, String exp) {
|
||||
this.runtimeService = runtimeService;
|
||||
this.processEngineConfiguration = processEngineConfiguration;
|
||||
this.processInstanceId = processInstanceId;
|
||||
this.exp = exp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean execute(CommandContext commandContext) {
|
||||
Expression expression = processEngineConfiguration.getExpressionManager().createExpression(this.exp);
|
||||
ExecutionEntity executionEntity;
|
||||
if (StringUtils.isNotBlank(this.processInstanceId)) {
|
||||
executionEntity = (ExecutionEntity) runtimeService.createProcessInstanceQuery()
|
||||
.processInstanceId(this.processInstanceId).includeProcessVariables().singleResult();
|
||||
} else {
|
||||
// 不能单纯的 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());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,184 @@
|
||||
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.Nullable;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
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>
|
||||
* FlowElement (org.flowable.bpmn.model)
|
||||
* |-- SequenceFlow (org.flowable.bpmn.model)
|
||||
* |-- DataStoreReference (org.flowable.bpmn.model)
|
||||
* |-- DataObject (org.flowable.bpmn.model)
|
||||
* |-- ValuedDataObject (org.flowable.bpmn.model)
|
||||
* |-- StringDataObject (org.flowable.bpmn.model)
|
||||
* |-- LongDataObject (org.flowable.bpmn.model)
|
||||
* |-- BooleanDataObject (org.flowable.bpmn.model)
|
||||
* |-- DoubleDataObject (org.flowable.bpmn.model)
|
||||
* |-- JsonDataObject (org.flowable.bpmn.model)
|
||||
* |-- DateDataObject (org.flowable.bpmn.model)
|
||||
* |-- IntegerDataObject (org.flowable.bpmn.model)
|
||||
* |-- FlowNode (org.flowable.bpmn.model)
|
||||
* |-- Activity (org.flowable.bpmn.model)
|
||||
* |-- CallActivity (org.flowable.bpmn.model)
|
||||
* |-- SubProcess (org.flowable.bpmn.model)
|
||||
* |-- Transaction (org.flowable.bpmn.model)
|
||||
* |-- EventSubProcess (org.flowable.bpmn.model)
|
||||
* |-- AdhocSubProcess (org.flowable.bpmn.model)
|
||||
* |-- Task (org.flowable.bpmn.model)
|
||||
* |-- ScriptTask (org.flowable.bpmn.model)
|
||||
* |-- ManualTask (org.flowable.bpmn.model)
|
||||
* |-- ReceiveTask (org.flowable.bpmn.model)
|
||||
* |-- BusinessRuleTask (org.flowable.bpmn.model)
|
||||
* |-- TaskWithFieldExtensions (org.flowable.bpmn.model)
|
||||
* |-- SendTask (org.flowable.bpmn.model)
|
||||
* |-- ServiceTask (org.flowable.bpmn.model)
|
||||
* |-- AlfrescoScriptTask (org.flowable.bpmn.model.alfresco)
|
||||
* |-- HttpServiceTask (org.flowable.bpmn.model)
|
||||
* |-- CaseServiceTask (org.flowable.bpmn.model)
|
||||
* |-- ExternalWorkerServiceTask (org.flowable.bpmn.model)
|
||||
* |-- SendEventServiceTask (org.flowable.bpmn.model)
|
||||
* |-- AlfrescoMailTask (org.flowable.bpmn.model.alfresco)
|
||||
* |-- UserTask (org.flowable.bpmn.model)
|
||||
* |-- AlfrescoUserTask (org.flowable.bpmn.model.alfresco)
|
||||
* |-- Gateway (org.flowable.bpmn.model)
|
||||
* |-- ExclusiveGateway (org.flowable.bpmn.model)
|
||||
* |-- ComplexGateway (org.flowable.bpmn.model)
|
||||
* |-- ParallelGateway (org.flowable.bpmn.model)
|
||||
* |-- InclusiveGateway (org.flowable.bpmn.model)
|
||||
* |-- EventGateway (org.flowable.bpmn.model)
|
||||
* |-- Event (org.flowable.bpmn.model)
|
||||
* |-- EndEvent (org.flowable.bpmn.model)
|
||||
* |-- BoundaryEvent (org.flowable.bpmn.model)
|
||||
* |-- ThrowEvent (org.flowable.bpmn.model)
|
||||
* |-- StartEvent (org.flowable.bpmn.model)
|
||||
* |-- AlfrescoStartEvent (org.flowable.bpmn.model.alfresco)
|
||||
* |-- IntermediateCatchEvent (org.flowable.bpmn.model)
|
||||
* </pre>
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2023/10/9 17:38
|
||||
*/
|
||||
@Service
|
||||
public class FlowNodeForecastService implements InitializingBean {
|
||||
|
||||
public final static Map<Class<?>, AbstractForecast<? extends FlowElement>> FORECAST_MAP = new HashMap<>();
|
||||
@Resource
|
||||
private RepositoryService repositoryService;
|
||||
@Resource
|
||||
private RuntimeService runtimeService;
|
||||
@Resource
|
||||
private List<Forecast<? extends FlowElement>> forecasts;
|
||||
|
||||
/**
|
||||
* 执行运行中的流程预测
|
||||
* <p>
|
||||
* 已完成的流程可以直接查询流程审批记录就行
|
||||
*
|
||||
* @param processInstanceId 指定运行时的流程实例 ID
|
||||
* @param instance 外部传入流程实例 (与另外一个参数必须二选一)
|
||||
*/
|
||||
public List<FlowElement> performProcessForecasting(@Nullable String processInstanceId,
|
||||
@Nullable ProcessInstance instance) {
|
||||
if (Objects.nonNull(instance)) {
|
||||
// nothing to do
|
||||
} else if (!StringUtils.hasLength(processInstanceId)) {
|
||||
throw new WorkflowEngineException(PROCESS_INSTANCE_NOT_EXISTS);
|
||||
} else {
|
||||
instance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId)
|
||||
.includeProcessVariables().singleResult();
|
||||
}
|
||||
BpmnModel bpmnModel = repositoryService.getBpmnModel(instance.getProcessDefinitionId());
|
||||
|
||||
// 保持推测出来的节点执行顺序的容器
|
||||
List<FlowElement> orderNodes = new ArrayList<>();
|
||||
// 流程定义中所有的FlowElement
|
||||
Collection<FlowElement> flowElements = bpmnModel.getMainProcess().getFlowElements();
|
||||
|
||||
// 开始节点
|
||||
findStartNode(flowElements).ifPresent(startNode -> {
|
||||
addOrderFlowNodes(orderNodes, startNode);
|
||||
});
|
||||
|
||||
startForecasting(orderNodes, instance);
|
||||
return orderNodes;
|
||||
}
|
||||
|
||||
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, instance);
|
||||
if (!CollectionUtils.isEmpty(list)) {
|
||||
addOrderFlowNodes(orderNodes, list.get(0));
|
||||
startForecasting(orderNodes, instance);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找开始节点
|
||||
* <p>
|
||||
* 由于一个定义中, 是允许存在多个 StartEvent, 但至少有一个 StartEvent 节点. 但我们目前的业务还未用到多个开始事件, 所以未做过多的筛选动作
|
||||
*
|
||||
* @param flowElements 全量的 FlowElement
|
||||
* @return
|
||||
*/
|
||||
private Optional<StartEvent> findStartNode(Collection<FlowElement> flowElements) {
|
||||
return Optional.ofNullable(flowElements.stream().filter(StartEvent.class::isInstance).findFirst()
|
||||
.map(StartEvent.class::cast).orElseThrow(() -> new IllegalArgumentException("非法的参数, 正确的流程定义一定有一个 " +
|
||||
"StartEvent 节点")));
|
||||
}
|
||||
|
||||
|
||||
private void addOrderFlowNodes(List<FlowElement> orderFlowNodes, FlowElement flowElement) {
|
||||
if (Objects.nonNull(flowElement)) {
|
||||
orderFlowNodes.add(flowElement);
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<FlowElement> getLastNode(List<FlowElement> orderFlowNodes) {
|
||||
if (CollectionUtils.isEmpty(orderFlowNodes)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(orderFlowNodes.get(orderFlowNodes.size() - 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
forecasts.forEach(i -> {
|
||||
Class<?> rawClass = ResolvableType.forClass(i.getClass(), Forecast.class)
|
||||
.getSuperType().getGenerics()[0].getRawClass();
|
||||
FORECAST_MAP.put(rawClass, (AbstractForecast<? extends FlowElement>) i);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,424 @@
|
||||
package cn.axzo.workflow.core.service.support;
|
||||
|
||||
import cn.axzo.workflow.core.repository.mapper.InfoMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.flowable.bpmn.model.Artifact;
|
||||
import org.flowable.bpmn.model.Association;
|
||||
import org.flowable.bpmn.model.BpmnModel;
|
||||
import org.flowable.bpmn.model.ConditionalEventDefinition;
|
||||
import org.flowable.bpmn.model.ErrorEventDefinition;
|
||||
import org.flowable.bpmn.model.EscalationEventDefinition;
|
||||
import org.flowable.bpmn.model.Event;
|
||||
import org.flowable.bpmn.model.EventDefinition;
|
||||
import org.flowable.bpmn.model.FlowElement;
|
||||
import org.flowable.bpmn.model.FlowNode;
|
||||
import org.flowable.bpmn.model.GraphicInfo;
|
||||
import org.flowable.bpmn.model.Lane;
|
||||
import org.flowable.bpmn.model.MessageEventDefinition;
|
||||
import org.flowable.bpmn.model.Pool;
|
||||
import org.flowable.bpmn.model.SequenceFlow;
|
||||
import org.flowable.bpmn.model.ServiceTask;
|
||||
import org.flowable.bpmn.model.SignalEventDefinition;
|
||||
import org.flowable.bpmn.model.SubProcess;
|
||||
import org.flowable.bpmn.model.TextAnnotation;
|
||||
import org.flowable.bpmn.model.TimerEventDefinition;
|
||||
import org.flowable.bpmn.model.VariableListenerEventDefinition;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 流程节点图形化工具服务类
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2023/10/9 17:23
|
||||
*/
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class ProcessGraphicService {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private List<String> eventElementTypes = new ArrayList<>();
|
||||
private Map<String, InfoMapper> propertyMappers = new HashMap<>();
|
||||
|
||||
public List<String> gatherCompletedFlows(Set<String> completedActivityInstances,
|
||||
Set<String> currentActivityInstances, BpmnModel pojoModel) {
|
||||
|
||||
List<String> completedFlows = new ArrayList<>();
|
||||
List<String> activities = new ArrayList<>(completedActivityInstances);
|
||||
activities.addAll(currentActivityInstances);
|
||||
|
||||
// TODO: not a robust way of checking when parallel paths are active, should be revisited
|
||||
// Go over all activities and check if it's possible to match any outgoing paths against the activities
|
||||
for (FlowElement activity : pojoModel.getMainProcess().getFlowElements()) {
|
||||
if (activity instanceof FlowNode) {
|
||||
int index = activities.indexOf(activity.getId());
|
||||
if (index >= 0 && index + 1 < activities.size()) {
|
||||
List<SequenceFlow> outgoingFlows = ((FlowNode) activity).getOutgoingFlows();
|
||||
for (SequenceFlow flow : outgoingFlows) {
|
||||
String destinationFlowId = flow.getTargetRef();
|
||||
if (destinationFlowId.equals(activities.get(index + 1))) {
|
||||
completedFlows.add(flow.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return completedFlows;
|
||||
}
|
||||
|
||||
public ObjectNode processProcessElements(BpmnModel pojoModel, Set<String> completedElements,
|
||||
Set<String> currentElements, Collection<String> breakpoints,
|
||||
String processInstanceId) {
|
||||
ObjectNode displayNode = objectMapper.createObjectNode();
|
||||
GraphicInfo diagramInfo = new GraphicInfo();
|
||||
|
||||
ArrayNode elementArray = objectMapper.createArrayNode();
|
||||
ArrayNode flowArray = objectMapper.createArrayNode();
|
||||
ArrayNode collapsedArray = objectMapper.createArrayNode();
|
||||
|
||||
if (!CollectionUtils.isEmpty(pojoModel.getPools())) {
|
||||
ArrayNode poolArray = objectMapper.createArrayNode();
|
||||
boolean firstElement = true;
|
||||
for (Pool pool : pojoModel.getPools()) {
|
||||
ObjectNode poolNode = objectMapper.createObjectNode();
|
||||
poolNode.put("id", pool.getId());
|
||||
poolNode.put("name", pool.getName());
|
||||
GraphicInfo poolInfo = pojoModel.getGraphicInfo(pool.getId());
|
||||
fillGraphicInfo(poolNode, poolInfo, true);
|
||||
org.flowable.bpmn.model.Process process = pojoModel.getProcess(pool.getId());
|
||||
if (process != null && !CollectionUtils.isEmpty(process.getLanes())) {
|
||||
ArrayNode laneArray = objectMapper.createArrayNode();
|
||||
for (Lane lane : process.getLanes()) {
|
||||
ObjectNode laneNode = objectMapper.createObjectNode();
|
||||
laneNode.put("id", lane.getId());
|
||||
laneNode.put("name", lane.getName());
|
||||
fillGraphicInfo(laneNode, pojoModel.getGraphicInfo(lane.getId()), true);
|
||||
laneArray.add(laneNode);
|
||||
}
|
||||
poolNode.set("lanes", laneArray);
|
||||
}
|
||||
poolArray.add(poolNode);
|
||||
|
||||
double rightX = poolInfo.getX() + poolInfo.getWidth();
|
||||
double bottomY = poolInfo.getY() + poolInfo.getHeight();
|
||||
double middleX = poolInfo.getX() + (poolInfo.getWidth() / 2);
|
||||
if (firstElement || middleX < diagramInfo.getX()) {
|
||||
diagramInfo.setX(middleX);
|
||||
}
|
||||
if (firstElement || poolInfo.getY() < diagramInfo.getY()) {
|
||||
diagramInfo.setY(poolInfo.getY());
|
||||
}
|
||||
if (rightX > diagramInfo.getWidth()) {
|
||||
diagramInfo.setWidth(rightX);
|
||||
}
|
||||
if (bottomY > diagramInfo.getHeight()) {
|
||||
diagramInfo.setHeight(bottomY);
|
||||
}
|
||||
firstElement = false;
|
||||
}
|
||||
displayNode.set("pools", poolArray);
|
||||
|
||||
} else {
|
||||
// in initialize with fake x and y to make sure the minimal
|
||||
// values are set
|
||||
diagramInfo.setX(9999);
|
||||
diagramInfo.setY(1000);
|
||||
}
|
||||
|
||||
for (org.flowable.bpmn.model.Process process : pojoModel.getProcesses()) {
|
||||
processElements(process.getFlowElements(), pojoModel, elementArray, flowArray, collapsedArray,
|
||||
diagramInfo, completedElements, currentElements, breakpoints, null, processInstanceId);
|
||||
processArtifacts(process.getArtifacts(), pojoModel, elementArray, flowArray, diagramInfo);
|
||||
}
|
||||
|
||||
displayNode.set("elements", elementArray);
|
||||
displayNode.set("flows", flowArray);
|
||||
displayNode.set("collapsed", collapsedArray);
|
||||
|
||||
displayNode.put("diagramBeginX", diagramInfo.getX());
|
||||
displayNode.put("diagramBeginY", diagramInfo.getY());
|
||||
displayNode.put("diagramWidth", diagramInfo.getWidth());
|
||||
displayNode.put("diagramHeight", diagramInfo.getHeight());
|
||||
return displayNode;
|
||||
}
|
||||
|
||||
private void fillGraphicInfo(ObjectNode elementNode, GraphicInfo graphicInfo, boolean includeWidthAndHeight) {
|
||||
commonFillGraphicInfo(elementNode, graphicInfo.getX(), graphicInfo.getY(), graphicInfo.getWidth(),
|
||||
graphicInfo.getHeight(), includeWidthAndHeight);
|
||||
}
|
||||
|
||||
private void commonFillGraphicInfo(ObjectNode elementNode, double x, double y, double width, double height,
|
||||
boolean includeWidthAndHeight) {
|
||||
|
||||
elementNode.put("x", x);
|
||||
elementNode.put("y", y);
|
||||
if (includeWidthAndHeight) {
|
||||
elementNode.put("width", width);
|
||||
elementNode.put("height", height);
|
||||
}
|
||||
}
|
||||
|
||||
private void processElements(Collection<FlowElement> elementList, BpmnModel model, ArrayNode elementArray,
|
||||
ArrayNode flowArray,
|
||||
ArrayNode collapsedArray, GraphicInfo diagramInfo, Set<String> completedElements,
|
||||
Set<String> currentElements, Collection<String> breakpoints,
|
||||
ObjectNode collapsedNode, String processInstanceId) {
|
||||
|
||||
for (FlowElement element : elementList) {
|
||||
|
||||
ObjectNode elementNode = objectMapper.createObjectNode();
|
||||
if (completedElements != null) {
|
||||
elementNode.put("completed", completedElements.contains(element.getId()));
|
||||
}
|
||||
if (!breakpoints.isEmpty()) {
|
||||
elementNode.put("breakpoint", breakpoints.contains(element.getId()));
|
||||
}
|
||||
|
||||
if (currentElements != null) {
|
||||
elementNode.put("current", currentElements.contains(element.getId()));
|
||||
}
|
||||
|
||||
if (element instanceof SequenceFlow) {
|
||||
SequenceFlow flow = (SequenceFlow) element;
|
||||
elementNode.put("id", flow.getId());
|
||||
elementNode.put("type", "sequenceFlow");
|
||||
elementNode.put("sourceRef", flow.getSourceRef());
|
||||
elementNode.put("targetRef", flow.getTargetRef());
|
||||
elementNode.put("name", flow.getName());
|
||||
List<GraphicInfo> flowInfo = model.getFlowLocationGraphicInfo(flow.getId());
|
||||
if (!CollectionUtils.isEmpty(flowInfo)) {
|
||||
ArrayNode waypointArray = objectMapper.createArrayNode();
|
||||
for (GraphicInfo graphicInfo : flowInfo) {
|
||||
ObjectNode pointNode = objectMapper.createObjectNode();
|
||||
fillGraphicInfo(pointNode, graphicInfo, false);
|
||||
waypointArray.add(pointNode);
|
||||
fillDiagramInfo(graphicInfo, diagramInfo);
|
||||
}
|
||||
elementNode.set("waypoints", waypointArray);
|
||||
|
||||
String className = element.getClass().getSimpleName();
|
||||
if (propertyMappers.containsKey(className)) {
|
||||
elementNode.set("properties", propertyMappers.get(className).map(element));
|
||||
}
|
||||
|
||||
if (collapsedNode != null) {
|
||||
((ArrayNode) collapsedNode.get("flows")).add(elementNode);
|
||||
} else {
|
||||
flowArray.add(elementNode);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
elementNode.put("id", element.getId());
|
||||
elementNode.put("name", element.getName());
|
||||
|
||||
GraphicInfo graphicInfo = model.getGraphicInfo(element.getId());
|
||||
if (graphicInfo != null) {
|
||||
fillGraphicInfo(elementNode, graphicInfo, true);
|
||||
fillDiagramInfo(graphicInfo, diagramInfo);
|
||||
}
|
||||
|
||||
String className = element.getClass().getSimpleName();
|
||||
elementNode.put("type", className);
|
||||
fillEventTypes(className, element, elementNode);
|
||||
|
||||
if (element instanceof ServiceTask) {
|
||||
ServiceTask serviceTask = (ServiceTask) element;
|
||||
if (ServiceTask.MAIL_TASK.equals(serviceTask.getType())) {
|
||||
elementNode.put("taskType", "mail");
|
||||
|
||||
} else if ("camel".equals(serviceTask.getType())) {
|
||||
elementNode.put("taskType", "camel");
|
||||
|
||||
} else if ("mule".equals(serviceTask.getType())) {
|
||||
elementNode.put("taskType", "mule");
|
||||
|
||||
} else if (ServiceTask.HTTP_TASK.equals(serviceTask.getType())) {
|
||||
elementNode.put("taskType", "http");
|
||||
} else if (ServiceTask.SHELL_TASK.equals(serviceTask.getType())) {
|
||||
elementNode.put("taskType", "shell");
|
||||
}
|
||||
}
|
||||
|
||||
if (propertyMappers.containsKey(className)) {
|
||||
elementNode.set("properties", propertyMappers.get(className).map(element));
|
||||
}
|
||||
|
||||
if (collapsedNode != null) {
|
||||
((ArrayNode) collapsedNode.get("elements")).add(elementNode);
|
||||
} else {
|
||||
elementArray.add(elementNode);
|
||||
}
|
||||
|
||||
if (element instanceof SubProcess) {
|
||||
SubProcess subProcess = (SubProcess) element;
|
||||
ObjectNode newCollapsedNode = collapsedNode;
|
||||
// skip collapsed sub processes
|
||||
if (graphicInfo != null && graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) {
|
||||
elementNode.put("collapsed", "true");
|
||||
newCollapsedNode = objectMapper.createObjectNode();
|
||||
newCollapsedNode.put("id", subProcess.getId());
|
||||
newCollapsedNode.putArray("elements");
|
||||
newCollapsedNode.putArray("flows");
|
||||
collapsedArray.add(newCollapsedNode);
|
||||
}
|
||||
|
||||
processElements(subProcess.getFlowElements(), model, elementArray, flowArray, collapsedArray,
|
||||
diagramInfo, completedElements, currentElements, breakpoints, newCollapsedNode,
|
||||
processInstanceId);
|
||||
processArtifacts(subProcess.getArtifacts(), model, elementArray, flowArray, diagramInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processArtifacts(Collection<Artifact> artifactList, BpmnModel model, ArrayNode elementArray,
|
||||
ArrayNode flowArray, GraphicInfo diagramInfo) {
|
||||
|
||||
for (Artifact artifact : artifactList) {
|
||||
|
||||
if (artifact instanceof Association) {
|
||||
ObjectNode elementNode = objectMapper.createObjectNode();
|
||||
Association flow = (Association) artifact;
|
||||
elementNode.put("id", flow.getId());
|
||||
elementNode.put("type", "association");
|
||||
elementNode.put("sourceRef", flow.getSourceRef());
|
||||
elementNode.put("targetRef", flow.getTargetRef());
|
||||
fillWaypoints(flow.getId(), model, elementNode, diagramInfo);
|
||||
flowArray.add(elementNode);
|
||||
|
||||
} else {
|
||||
|
||||
ObjectNode elementNode = objectMapper.createObjectNode();
|
||||
elementNode.put("id", artifact.getId());
|
||||
|
||||
if (artifact instanceof TextAnnotation) {
|
||||
TextAnnotation annotation = (TextAnnotation) artifact;
|
||||
elementNode.put("text", annotation.getText());
|
||||
}
|
||||
|
||||
GraphicInfo graphicInfo = model.getGraphicInfo(artifact.getId());
|
||||
if (graphicInfo != null) {
|
||||
fillGraphicInfo(elementNode, graphicInfo, true);
|
||||
fillDiagramInfo(graphicInfo, diagramInfo);
|
||||
}
|
||||
|
||||
String className = artifact.getClass().getSimpleName();
|
||||
elementNode.put("type", className);
|
||||
|
||||
elementArray.add(elementNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fillDiagramInfo(GraphicInfo graphicInfo, GraphicInfo diagramInfo) {
|
||||
double rightX = graphicInfo.getX() + graphicInfo.getWidth();
|
||||
double bottomY = graphicInfo.getY() + graphicInfo.getHeight();
|
||||
double middleX = graphicInfo.getX() + (graphicInfo.getWidth() / 2);
|
||||
if (middleX < diagramInfo.getX()) {
|
||||
diagramInfo.setX(middleX);
|
||||
}
|
||||
if (graphicInfo.getY() < diagramInfo.getY()) {
|
||||
diagramInfo.setY(graphicInfo.getY());
|
||||
}
|
||||
if (rightX > diagramInfo.getWidth()) {
|
||||
diagramInfo.setWidth(rightX);
|
||||
}
|
||||
if (bottomY > diagramInfo.getHeight()) {
|
||||
diagramInfo.setHeight(bottomY);
|
||||
}
|
||||
}
|
||||
|
||||
private void fillWaypoints(String id, BpmnModel model, ObjectNode elementNode, GraphicInfo diagramInfo) {
|
||||
List<GraphicInfo> flowInfo = model.getFlowLocationGraphicInfo(id);
|
||||
ArrayNode waypointArray = objectMapper.createArrayNode();
|
||||
for (GraphicInfo graphicInfo : flowInfo) {
|
||||
ObjectNode pointNode = objectMapper.createObjectNode();
|
||||
fillGraphicInfo(pointNode, graphicInfo, false);
|
||||
waypointArray.add(pointNode);
|
||||
fillDiagramInfo(graphicInfo, diagramInfo);
|
||||
}
|
||||
elementNode.set("waypoints", waypointArray);
|
||||
}
|
||||
|
||||
private void fillEventTypes(String className, FlowElement element, ObjectNode elementNode) {
|
||||
if (eventElementTypes.contains(className)) {
|
||||
Event event = (Event) element;
|
||||
if (!CollectionUtils.isEmpty(event.getEventDefinitions())) {
|
||||
EventDefinition eventDef = event.getEventDefinitions().get(0);
|
||||
ObjectNode eventNode = objectMapper.createObjectNode();
|
||||
if (eventDef instanceof TimerEventDefinition) {
|
||||
TimerEventDefinition timerDef = (TimerEventDefinition) eventDef;
|
||||
eventNode.put("type", "timer");
|
||||
if (org.apache.commons.lang3.StringUtils.isNotEmpty(timerDef.getTimeCycle())) {
|
||||
eventNode.put("timeCycle", timerDef.getTimeCycle());
|
||||
}
|
||||
if (org.apache.commons.lang3.StringUtils.isNotEmpty(timerDef.getTimeDate())) {
|
||||
eventNode.put("timeDate", timerDef.getTimeDate());
|
||||
}
|
||||
if (org.apache.commons.lang3.StringUtils.isNotEmpty(timerDef.getTimeDuration())) {
|
||||
eventNode.put("timeDuration", timerDef.getTimeDuration());
|
||||
}
|
||||
|
||||
} else if (eventDef instanceof ConditionalEventDefinition) {
|
||||
ConditionalEventDefinition conditionalDef = (ConditionalEventDefinition) eventDef;
|
||||
eventNode.put("type", "conditional");
|
||||
if (org.apache.commons.lang3.StringUtils.isNotEmpty(conditionalDef.getConditionExpression())) {
|
||||
eventNode.put("condition", conditionalDef.getConditionExpression());
|
||||
}
|
||||
|
||||
} else if (eventDef instanceof ErrorEventDefinition) {
|
||||
ErrorEventDefinition errorDef = (ErrorEventDefinition) eventDef;
|
||||
eventNode.put("type", "error");
|
||||
if (org.apache.commons.lang3.StringUtils.isNotEmpty(errorDef.getErrorCode())) {
|
||||
eventNode.put("errorCode", errorDef.getErrorCode());
|
||||
}
|
||||
|
||||
} else if (eventDef instanceof EscalationEventDefinition) {
|
||||
EscalationEventDefinition escalationDef = (EscalationEventDefinition) eventDef;
|
||||
eventNode.put("type", "escalation");
|
||||
if (org.apache.commons.lang3.StringUtils.isNotEmpty(escalationDef.getEscalationCode())) {
|
||||
eventNode.put("escalationCode", escalationDef.getEscalationCode());
|
||||
}
|
||||
|
||||
} else if (eventDef instanceof SignalEventDefinition) {
|
||||
SignalEventDefinition signalDef = (SignalEventDefinition) eventDef;
|
||||
eventNode.put("type", "signal");
|
||||
if (org.apache.commons.lang3.StringUtils.isNotEmpty(signalDef.getSignalRef())) {
|
||||
eventNode.put("signalRef", signalDef.getSignalRef());
|
||||
}
|
||||
|
||||
} else if (eventDef instanceof MessageEventDefinition) {
|
||||
MessageEventDefinition messageDef = (MessageEventDefinition) eventDef;
|
||||
eventNode.put("type", "message");
|
||||
if (org.apache.commons.lang3.StringUtils.isNotEmpty(messageDef.getMessageRef())) {
|
||||
eventNode.put("messageRef", messageDef.getMessageRef());
|
||||
}
|
||||
|
||||
} else if (eventDef instanceof VariableListenerEventDefinition) {
|
||||
VariableListenerEventDefinition variableDef = (VariableListenerEventDefinition) eventDef;
|
||||
eventNode.put("type", "variable");
|
||||
if (org.apache.commons.lang3.StringUtils.isNotEmpty(variableDef.getVariableName())) {
|
||||
eventNode.put("variableName", variableDef.getVariableName());
|
||||
}
|
||||
if (org.apache.commons.lang3.StringUtils.isNotEmpty(variableDef.getVariableChangeType())) {
|
||||
eventNode.put("variableChangeType", variableDef.getVariableChangeType());
|
||||
}
|
||||
}
|
||||
elementNode.set("eventDefinition", eventNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
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;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.axzo.workflow.core.service.support.FlowNodeForecastService.FORECAST_MAP;
|
||||
|
||||
/**
|
||||
* 抽象的流程预测
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2023/10/10 10:55
|
||||
*/
|
||||
public abstract class AbstractForecast<T extends FlowElement> implements Forecast<T>, FlowableConditionEvaluatorAware {
|
||||
|
||||
private FlowableConditionEvaluator conditionEvaluator;
|
||||
|
||||
@Override
|
||||
public final List<FlowElement> nextFlowElement(FlowElement sourceFlowElement, ProcessInstance instance) {
|
||||
return forecastNextNodes(getOutgoing((T) sourceFlowElement), instance);
|
||||
}
|
||||
|
||||
protected abstract List<? extends FlowElement> getOutgoing(T flowElement);
|
||||
|
||||
/**
|
||||
* 计算出口节点,此时的入参 sourceFlowElement 已经不等于泛型类
|
||||
*
|
||||
* @param sourceFlowElements 当前节点的出口节点集合
|
||||
* @param instance 流程实例
|
||||
* @return
|
||||
*/
|
||||
private List<FlowElement> forecastNextNodes(List<? extends FlowElement> sourceFlowElements,
|
||||
ProcessInstance instance) {
|
||||
if (CollectionUtils.isEmpty(sourceFlowElements)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Map<Class<? extends FlowElement>, List<FlowElement>> elementGroup =
|
||||
sourceFlowElements.stream().collect(Collectors.groupingBy(FlowElement::getClass, Collectors.toList()));
|
||||
|
||||
List<FlowElement> result = new ArrayList<>();
|
||||
elementGroup.forEach((k, v) -> {
|
||||
AbstractForecast forecast = FORECAST_MAP.get(k);
|
||||
if (Objects.nonNull(forecast)) {
|
||||
result.addAll(forecast.calcRealOutgoingNodes(v, instance));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 由于每个节点的下级节点是多个,且有可能是不同类型, 在预测下级节点时,需要将同类型的节点传递给对应类型的预测器来计算
|
||||
*
|
||||
* @param flowElements
|
||||
* @param instance
|
||||
* @return
|
||||
*/
|
||||
public List<? extends FlowElement> calcRealOutgoingNodes(List<T> flowElements, ProcessInstance instance) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConditionEvaluator(FlowableConditionEvaluator conditionEvaluator) {
|
||||
this.conditionEvaluator = conditionEvaluator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提供给所有实现类的通用调用引擎评估节点可能存在的条件表达式
|
||||
*
|
||||
* @param exp 表达式
|
||||
* @param processInstanceId 实例 ID
|
||||
* @return
|
||||
* @see SequenceFlowForecasting#calcRealOutgoingNodes(List, ProcessInstance) 参考这里的用法
|
||||
*/
|
||||
protected final Boolean conditionOn(String exp, String processInstanceId) {
|
||||
return conditionEvaluator.conditionOn(exp, processInstanceId);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package cn.axzo.workflow.core.service.support.forecast;
|
||||
|
||||
import cn.axzo.workflow.core.service.support.ExpressionConditionCmd;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.flowable.engine.ManagementService;
|
||||
import org.flowable.engine.RuntimeService;
|
||||
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 基于 Flowable 引擎能力的表达式评估器
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2023/10/10 10:41
|
||||
*/
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class FlowableConditionEvaluator {
|
||||
|
||||
private final ManagementService managementService;
|
||||
private final RuntimeService runtimeService;
|
||||
private final ProcessEngineConfigurationImpl processEngineConfiguration;
|
||||
|
||||
/**
|
||||
* 计算表达式真假, 需结合引擎能力
|
||||
*
|
||||
* @param exp 条件表达式
|
||||
* @param variables 流程变量
|
||||
* @param processInstanceId 流程实例 ID (可与 variables 参入二选一, 如果都传,则直接用实例 ID 去引擎中查真实的变量表)
|
||||
* @return true Or false
|
||||
*/
|
||||
public Boolean conditionOn(String exp, String processInstanceId) {
|
||||
return managementService.executeCommand(new ExpressionConditionCmd(runtimeService,
|
||||
processEngineConfiguration, processInstanceId, exp));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package cn.axzo.workflow.core.service.support.forecast;
|
||||
|
||||
import org.flowable.bpmn.model.FlowElement;
|
||||
import org.flowable.engine.runtime.ProcessInstance;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 流程节点顺序预测接口
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2023/10/10 10:29
|
||||
*/
|
||||
public interface Forecast<T extends FlowElement> {
|
||||
|
||||
/**
|
||||
* 用于判断是否支持处理指定节点类型
|
||||
*
|
||||
* @param t Flowable 的子类
|
||||
* @return
|
||||
*/
|
||||
Boolean support(FlowElement flowElement);
|
||||
|
||||
/**
|
||||
* 通过给定的源 FlowElement 结合 Flowable 的条件评估器去计算符合条件的下一级节点
|
||||
* <p>
|
||||
* 由于标准的 BPMN 协议中很多节点都是允许多条路并行走的, 所以接口返回模型定义为 List 集合,
|
||||
* 但实际上, 按照现在的业务定义, 只允许存在 0 或 1 个下一级节点.
|
||||
*
|
||||
* @param sourceFlowElement 当前节点, 实现类中该方法则是通过 getOutgoing 获取出口节点集合, 并调用对应类型中的 calcRealOutgoingNodes 查找真正的下级节点
|
||||
* @param instance 流程运行实例
|
||||
* @return
|
||||
*/
|
||||
List<FlowElement> nextFlowElement(FlowElement sourceFlowElement, ProcessInstance instance);
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* 结束节点
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2023/10/10 18:58
|
||||
*/
|
||||
@Component
|
||||
public class EndEventForecasting extends AbstractForecast<EndEvent> {
|
||||
@Override
|
||||
public Boolean support(FlowElement flowElement) {
|
||||
return flowElement instanceof EndEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<? extends FlowElement> getOutgoing(EndEvent flowElement) {
|
||||
return flowElement.getOutgoingFlows();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends FlowElement> calcRealOutgoingNodes(List<EndEvent> flowElements, ProcessInstance instance) {
|
||||
if (CollectionUtils.isEmpty(flowElements)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return super.calcRealOutgoingNodes(flowElements, instance);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
package cn.axzo.workflow.core.service.support.forecast.impl;
|
||||
|
||||
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.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 顺序流节点预测唯一一条通路
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2023/10/10 10:28
|
||||
*/
|
||||
@Component
|
||||
public class SequenceFlowForecasting extends AbstractForecast<SequenceFlow> {
|
||||
|
||||
@Override
|
||||
public Boolean support(FlowElement flowElement) {
|
||||
return flowElement instanceof SequenceFlow;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<? extends FlowElement> getOutgoing(SequenceFlow flowElement) {
|
||||
return Lists.newArrayList(flowElement.getTargetFlowElement());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends FlowElement> calcRealOutgoingNodes(List<SequenceFlow> flowElements,
|
||||
ProcessInstance instance) {
|
||||
if (CollectionUtils.isEmpty(flowElements)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// 评估顺序流集合中条件为 true 的顺序流
|
||||
List<SequenceFlow> executableFlows =
|
||||
flowElements.stream().filter(i -> StringUtils.hasLength(i.getConditionExpression()))
|
||||
.filter(i -> conditionOn(i.getConditionExpression(), instance.getId())).collect(Collectors.toList());
|
||||
if (!CollectionUtils.isEmpty(executableFlows) && executableFlows.size() == 1) {
|
||||
return Lists.newArrayList(executableFlows.get(0));
|
||||
}
|
||||
|
||||
// 如果 conditionExpression 为空, 则认为是默认流
|
||||
List<SequenceFlow> defaultFlows =
|
||||
flowElements.stream().filter(i -> !StringUtils.hasLength(i.getConditionExpression())).collect(Collectors.toList());
|
||||
if (!CollectionUtils.isEmpty(defaultFlows) && defaultFlows.size() == 1) {
|
||||
return Lists.newArrayList(defaultFlows.get(0));
|
||||
}
|
||||
return super.calcRealOutgoingNodes(flowElements, instance);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
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.StartEvent;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 开始节点
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2023/10/10 13:55
|
||||
*/
|
||||
@Component
|
||||
public class StartEventForecasting extends AbstractForecast<StartEvent> {
|
||||
|
||||
@Override
|
||||
public Boolean support(FlowElement flowElement) {
|
||||
return flowElement instanceof StartEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<? extends FlowElement> getOutgoing(StartEvent flowElement) {
|
||||
return flowElement.getOutgoingFlows();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* 用户任务节点预测
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2023/10/10 11:29
|
||||
*/
|
||||
@Component
|
||||
public class UserTaskForecasting extends AbstractForecast<UserTask> {
|
||||
@Override
|
||||
public Boolean support(FlowElement flowElement) {
|
||||
return flowElement instanceof UserTask;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<? extends FlowElement> getOutgoing(UserTask flowElement) {
|
||||
return flowElement.getOutgoingFlows();
|
||||
}
|
||||
|
||||
@Override
|
||||
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, instance);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package cn.axzo.workflow.core.service.support.spring;
|
||||
|
||||
import cn.axzo.workflow.core.service.support.forecast.FlowableConditionEvaluator;
|
||||
|
||||
/**
|
||||
* 按照 Spring 规范提供一个 Flowable Condition Evaluator 的 Aware
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2023/10/10 10:44
|
||||
*/
|
||||
public interface FlowableConditionEvaluatorAware {
|
||||
|
||||
/**
|
||||
* 注入条件评估器
|
||||
*
|
||||
* @param conditionEvaluator {@link FlowableConditionEvaluator}
|
||||
*/
|
||||
void setConditionEvaluator(FlowableConditionEvaluator conditionEvaluator);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
package cn.axzo.workflow.server.controller.web;
|
||||
|
||||
import cn.axzo.workflow.common.model.response.bpmn.process.ProcessNodeDetailVO;
|
||||
import cn.axzo.workflow.core.service.BpmnProcessInstanceService;
|
||||
import cn.axzo.workflow.core.service.support.FlowNodeForecastService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.bpmn.model.FlowElement;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2023/10/10 13:59
|
||||
*/
|
||||
@Slf4j
|
||||
@RequestMapping("/web/v1/api")
|
||||
@RestController
|
||||
@Validated
|
||||
public class TestController {
|
||||
|
||||
@Autowired
|
||||
private FlowNodeForecastService forecastService;
|
||||
@Autowired
|
||||
private BpmnProcessInstanceService instanceService;
|
||||
@GetMapping("/test")
|
||||
public void test(@RequestParam String processInstanceId) {
|
||||
List<FlowElement> flowElements = forecastService.performProcessForecasting(processInstanceId, null);
|
||||
System.out.println("flowElements = " + flowElements);
|
||||
}
|
||||
|
||||
@GetMapping("/test2")
|
||||
public void test2(@RequestParam String processInstanceId) {
|
||||
List<ProcessNodeDetailVO> detailVOS = instanceService.getProcessInstanceNodeForecast(processInstanceId, "296");
|
||||
System.out.println("detailVOS = " + detailVOS);
|
||||
}
|
||||
|
||||
}
|
||||
@ -48,7 +48,6 @@ public class BpmnProcessInstanceController implements ProcessInstanceApi {
|
||||
@Resource
|
||||
private BpmnProcessInstanceService bpmnProcessInstanceService;
|
||||
|
||||
|
||||
/**
|
||||
* 我发起的审批列表
|
||||
*/
|
||||
@ -141,9 +140,10 @@ public class BpmnProcessInstanceController implements ProcessInstanceApi {
|
||||
*/
|
||||
@GetMapping("/node/calc")
|
||||
@Override
|
||||
public CommonResponse<List<ProcessNodeDetailVO>> processInstanceNodeCalc(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId,
|
||||
@Nullable String tenantId) {
|
||||
return success(bpmnProcessInstanceService.getProcessNodes(processInstanceId, tenantId));
|
||||
public CommonResponse<List<ProcessNodeDetailVO>> processInstanceNodeForecast(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId,
|
||||
@Nullable String tenantId) {
|
||||
// return success(bpmnProcessInstanceService.getProcessNodes(processInstanceId, tenantId));
|
||||
return success(bpmnProcessInstanceService.getProcessInstanceNodeForecast(processInstanceId, tenantId));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user