diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/constants/BpmnConstants.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/constants/BpmnConstants.java index 0a17b3c0..8bcf54ff 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/constants/BpmnConstants.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/constants/BpmnConstants.java @@ -12,6 +12,7 @@ public class BpmnConstants { public static final String START = "start"; public static final String END = "end"; public static final String EDGE = "edge"; + public static final String SHELL_TASK = "shellTask"; } /** @@ -25,6 +26,8 @@ public class BpmnConstants { public static final String TARGET = "target"; public static final String DATA = "data"; public static final String CONDITION = "condition"; + public static final String SERVICE_TASK = "serviceTask"; + public static final String FIELDS = "fields"; } /** @@ -53,6 +56,27 @@ public class BpmnConstants { public static final String SHELL_TASK = "${shellTaskDelegate}"; } + /** + * Shell任务配置 + */ + public static class ShellTaskConfig { + public static final String SCRIPT = "script"; + public static final String WORK_DIR = "workDir"; + public static final String TIMEOUT = "timeout"; + public static final String RETRY_COUNT = "retryCount"; + public static final String ENV = "env"; + public static final String PRIORITY = "priority"; + public static final String GROUP = "group"; + } + + /** + * 流程属性 + */ + public static class ProcessAttribute { + public static final String CELLS = "cells"; + public static final String EXECUTABLE = "true"; + } + private BpmnConstants() { // 防止实例化 } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/handler/impl/ShellTaskHandler.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/handler/impl/ShellTaskHandler.java index 640660be..e0b27357 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/handler/impl/ShellTaskHandler.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/handler/impl/ShellTaskHandler.java @@ -10,7 +10,10 @@ import org.flowable.bpmn.model.FieldExtension; import org.flowable.bpmn.model.ServiceTask; import org.springframework.stereotype.Component; +import java.util.Arrays; +import java.util.List; import java.util.Map; +import java.util.Optional; /** * Shell任务处理器 @@ -19,27 +22,25 @@ import java.util.Map; @Component public class ShellTaskHandler implements BpmnNodeHandler { + // Shell任务的必需字段 + private static final List REQUIRED_FIELDS = Arrays.asList( + BpmnConstants.ShellTaskConfig.SCRIPT, + BpmnConstants.ShellTaskConfig.WORK_DIR + ); + @Override public void handle(JsonNode nodeData, ServiceTask element, BpmnNodeConfig config) { - element.setImplementationType(BpmnConstants.DelegateExpression.TYPE); - element.setImplementation(BpmnConstants.DelegateExpression.SHELL_TASK); - element.setAsynchronous(true); // Shell任务默认异步执行 + // 设置任务委托表达式 + setupTaskDelegate(element); - // 从配置中获取字段 - Map properties = config.getProperties(); - properties.forEach((key, value) -> { - if (value != null) { - addFieldExtension(element, key, value.toString()); - } - }); + // 设置任务属性 + setupTaskProperties(element, config); + + // 验证必需字段 + validateRequiredFields(element); // 处理扩展字段 - Map extensions = config.getExtensions(); - extensions.forEach((key, value) -> { - if (value != null) { - addFieldExtension(element, key, value); - } - }); + handleExtensionFields(element, config); } @Override @@ -60,10 +61,72 @@ public class ShellTaskHandler implements BpmnNodeHandler { return ServiceTask.class; } + /** + * 设置任务委托表达式 + */ + private void setupTaskDelegate(ServiceTask element) { + element.setImplementationType(BpmnConstants.DelegateExpression.TYPE); + element.setImplementation(BpmnConstants.DelegateExpression.SHELL_TASK); + element.setAsynchronous(true); // Shell任务默认异步执行 + } + + /** + * 设置任务属性 + */ + private void setupTaskProperties(ServiceTask element, BpmnNodeConfig config) { + Map properties = config.getProperties(); + properties.forEach((key, value) -> { + if (value != null) { + addFieldExtension(element, key, value.toString()); + } + }); + } + + /** + * 验证必需字段 + */ + private void validateRequiredFields(ServiceTask element) { + List fields = element.getFieldExtensions(); + for (String requiredField : REQUIRED_FIELDS) { + boolean fieldExists = fields.stream() + .anyMatch(field -> field.getFieldName().equals(requiredField)); + if (!fieldExists) { + log.warn("Required field '{}' is missing for shell task '{}'", + requiredField, element.getId()); + } + } + } + + /** + * 处理扩展字段 + */ + private void handleExtensionFields(ServiceTask element, BpmnNodeConfig config) { + Map extensions = config.getExtensions(); + extensions.forEach((key, value) -> { + if (value != null) { + addFieldExtension(element, key, value); + } + }); + } + + /** + * 添加字段扩展 + */ private void addFieldExtension(ServiceTask serviceTask, String name, String value) { - FieldExtension field = new FieldExtension(); - field.setFieldName(name); - field.setStringValue(value); - serviceTask.getFieldExtensions().add(field); + // 检查字段是否已存在 + Optional existingField = serviceTask.getFieldExtensions().stream() + .filter(field -> field.getFieldName().equals(name)) + .findFirst(); + + if (existingField.isPresent()) { + // 更新现有字段 + existingField.get().setStringValue(value); + } else { + // 添加新字段 + FieldExtension field = new FieldExtension(); + field.setFieldName(name); + field.setStringValue(value); + serviceTask.getFieldExtensions().add(field); + } } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/BpmnConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/BpmnConverter.java index c90a107a..695a14e6 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/BpmnConverter.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/BpmnConverter.java @@ -17,6 +17,7 @@ import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; @@ -44,78 +45,121 @@ public class BpmnConverter { public String convertToBpmnXml(String x6Json, String processId) throws Exception { JsonNode jsonNode = objectMapper.readTree(x6Json); - - // 创建BPMN模型 - BpmnModel bpmnModel = new BpmnModel(); - Process process = new Process(); - process.setId(processId); - process.setName(processId); - process.setExecutable(true); - bpmnModel.addProcess(process); + BpmnModel bpmnModel = createBpmnModel(processId); + Process process = bpmnModel.getMainProcess(); // 处理节点 Map elementMap = new HashMap<>(); - JsonNode cells = jsonNode.get("cells"); + JsonNode cells = jsonNode.path(BpmnConstants.ProcessAttribute.CELLS); - if (cells != null) { - // 第一遍:处理所有节点 - for (JsonNode cell : cells) { - if (!isNode(cell)) { - continue; - } - - String shape = cell.path(BpmnConstants.NodeAttribute.SHAPE).asText(); - String id = cell.path(BpmnConstants.NodeAttribute.ID).asText(); - String label = cell.path(BpmnConstants.NodeAttribute.DATA) - .path(BpmnConstants.NodeAttribute.LABEL).asText(""); - - // 处理特殊节点类型 - if (BpmnConstants.NodeShape.START.equals(shape)) { - StartEvent startEvent = new StartEvent(); - startEvent.setId(id); - startEvent.setName(label); - process.addFlowElement(startEvent); - elementMap.put(id, startEvent); - continue; - } else if (BpmnConstants.NodeShape.END.equals(shape)) { - EndEvent endEvent = new EndEvent(); - endEvent.setId(id); - endEvent.setName(label); - process.addFlowElement(endEvent); - elementMap.put(id, endEvent); - continue; - } - - // 解析节点配置 - BpmnNodeConfig config = configParser.parse(cell); - if (config.getType() != null) { - // 使用对应的处理器处理节点 - BpmnNodeHandler handler = handlers.get(config.getType()); - if (handler != null) { - FlowElement element = createAndHandleElement(handler, cell, config); - process.addFlowElement(element); - elementMap.put(config.getId(), element); - } - } - } + if (!cells.isMissingNode()) { + // 处理所有节点 + processNodes(cells, process, elementMap); // 确保存在开始事件和结束事件(如果没有显式定义) ensureStartAndEndEvents(process, elementMap); - // 第二遍:处理所有连线 - for (JsonNode cell : cells) { - if (isEdge(cell)) { - handleSequenceFlow(cell, process, elementMap); - } - } + // 处理所有连线 + processSequenceFlows(cells, process, elementMap); } // 自动布局 new BpmnAutoLayout(bpmnModel).execute(); // 转换为XML - byte[] xmlBytes = bpmnXMLConverter.convertToXML(bpmnModel); - return new String(xmlBytes); + return new String(bpmnXMLConverter.convertToXML(bpmnModel)); + } + + /** + * 创建BPMN模型 + */ + private BpmnModel createBpmnModel(String processId) { + BpmnModel bpmnModel = new BpmnModel(); + Process process = new Process(); + process.setId(processId); + process.setName(processId); + process.setExecutable(Boolean.parseBoolean(BpmnConstants.ProcessAttribute.EXECUTABLE)); + bpmnModel.addProcess(process); + return bpmnModel; + } + + /** + * 处理所有节点 + */ + private void processNodes(JsonNode cells, Process process, Map elementMap) { + cells.forEach(cell -> { + if (isNode(cell)) { + processNode(cell, process, elementMap); + } + }); + } + + /** + * 处理单个节点 + */ + private void processNode(JsonNode cell, Process process, Map elementMap) { + String shape = cell.path(BpmnConstants.NodeAttribute.SHAPE).asText(); + String id = cell.path(BpmnConstants.NodeAttribute.ID).asText(); + String label = cell.path(BpmnConstants.NodeAttribute.DATA) + .path(BpmnConstants.NodeAttribute.LABEL).asText(""); + + // 处理特殊节点类型 + Optional specialElement = createSpecialElement(shape, id, label); + if (specialElement.isPresent()) { + process.addFlowElement(specialElement.get()); + elementMap.put(id, specialElement.get()); + return; + } + + // 处理普通节点 + processRegularNode(cell, process, elementMap); + } + + /** + * 创建特殊元素(开始/结束事件) + */ + private Optional createSpecialElement(String shape, String id, String label) { + FlowElement element = null; + + if (BpmnConstants.NodeShape.START.equals(shape)) { + StartEvent startEvent = new StartEvent(); + startEvent.setId(id); + startEvent.setName(label); + element = startEvent; + } else if (BpmnConstants.NodeShape.END.equals(shape)) { + EndEvent endEvent = new EndEvent(); + endEvent.setId(id); + endEvent.setName(label); + element = endEvent; + } + + return Optional.ofNullable(element); + } + + /** + * 处理普通节点 + */ + private void processRegularNode(JsonNode cell, Process process, Map elementMap) { + BpmnNodeConfig config = configParser.parse(cell); + if (config.getType() != null) { + BpmnNodeHandler handler = handlers.get(config.getType()); + if (handler != null) { + FlowElement element = createAndHandleElement(handler, cell, config); + process.addFlowElement(element); + elementMap.put(config.getId(), element); + } + } + } + + /** + * 处理所有连线 + */ + private void processSequenceFlows(JsonNode cells, Process process, Map elementMap) { + cells.forEach(cell -> { + if (isEdge(cell)) { + handleSequenceFlow(cell, process, elementMap); + } + }); } private void ensureStartAndEndEvents(Process process, Map elementMap) { @@ -141,37 +185,66 @@ public class BpmnConverter { // 如果没有开始事件,创建一个并连接到第一个任务 if (startEvent == null && firstElement != null) { - startEvent = new StartEvent(); - startEvent.setId(BpmnConstants.DefaultNodeId.START_EVENT); - startEvent.setName(BpmnConstants.DefaultNodeName.START_EVENT); + startEvent = createStartEvent(); process.addFlowElement(startEvent); elementMap.put(startEvent.getId(), startEvent); // 创建从开始事件到第一个任务的连线 - SequenceFlow startFlow = new SequenceFlow(); - startFlow.setId(BpmnConstants.DefaultNodeId.START_FLOW); - startFlow.setSourceRef(startEvent.getId()); - startFlow.setTargetRef(firstElement.getId()); + SequenceFlow startFlow = createSequenceFlow( + BpmnConstants.DefaultNodeId.START_FLOW, + startEvent.getId(), + firstElement.getId() + ); process.addFlowElement(startFlow); } // 如果没有结束事件,创建一个并从最后一个任务连接到它 if (endEvent == null && lastElement != null) { - endEvent = new EndEvent(); - endEvent.setId(BpmnConstants.DefaultNodeId.END_EVENT); - endEvent.setName(BpmnConstants.DefaultNodeName.END_EVENT); + endEvent = createEndEvent(); process.addFlowElement(endEvent); elementMap.put(endEvent.getId(), endEvent); // 创建从最后一个任务到结束事件的连线 - SequenceFlow endFlow = new SequenceFlow(); - endFlow.setId(BpmnConstants.DefaultNodeId.END_FLOW); - endFlow.setSourceRef(lastElement.getId()); - endFlow.setTargetRef(endEvent.getId()); + SequenceFlow endFlow = createSequenceFlow( + BpmnConstants.DefaultNodeId.END_FLOW, + lastElement.getId(), + endEvent.getId() + ); process.addFlowElement(endFlow); } } + /** + * 创建开始事件 + */ + private StartEvent createStartEvent() { + StartEvent startEvent = new StartEvent(); + startEvent.setId(BpmnConstants.DefaultNodeId.START_EVENT); + startEvent.setName(BpmnConstants.DefaultNodeName.START_EVENT); + return startEvent; + } + + /** + * 创建结束事件 + */ + private EndEvent createEndEvent() { + EndEvent endEvent = new EndEvent(); + endEvent.setId(BpmnConstants.DefaultNodeId.END_EVENT); + endEvent.setName(BpmnConstants.DefaultNodeName.END_EVENT); + return endEvent; + } + + /** + * 创建连线 + */ + private SequenceFlow createSequenceFlow(String id, String sourceRef, String targetRef) { + SequenceFlow flow = new SequenceFlow(); + flow.setId(id); + flow.setSourceRef(sourceRef); + flow.setTargetRef(targetRef); + return flow; + } + private boolean isNode(JsonNode cell) { return cell.has(BpmnConstants.NodeAttribute.SHAPE) && !BpmnConstants.NodeShape.EDGE.equals(cell.path(BpmnConstants.NodeAttribute.SHAPE).asText()); @@ -199,10 +272,7 @@ public class BpmnConverter { FlowElement targetElement = elementMap.get(targetId); if (sourceElement != null && targetElement != null) { - SequenceFlow sequenceFlow = new SequenceFlow(); - sequenceFlow.setId(id); - sequenceFlow.setSourceRef(sourceId); - sequenceFlow.setTargetRef(targetId); + SequenceFlow sequenceFlow = createSequenceFlow(id, sourceId, targetId); // 设置连线名称 JsonNode label = cell.path(BpmnConstants.NodeAttribute.DATA)