diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/service/impl/ExtActProcessServiceImpl.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/service/impl/ExtActProcessServiceImpl.java index c06789c..502848f 100644 --- a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/service/impl/ExtActProcessServiceImpl.java +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/service/impl/ExtActProcessServiceImpl.java @@ -7,6 +7,11 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StreamUtils; +import org.flowable.bpmn.converter.BpmnXMLConverter; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.FlowElementsContainer; +import org.flowable.bpmn.model.Task; import org.flowable.common.engine.api.FlowableException; import org.flowable.engine.IdentityService; import org.flowable.engine.RepositoryService; @@ -51,6 +56,11 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import javax.servlet.http.HttpServletRequest; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamReader; +import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.sql.Blob; import java.sql.Clob; import java.util.*; @@ -183,6 +193,11 @@ public class ExtActProcessServiceImpl extends ServiceImpl processNodeInfoMap = parseProcessNodeInfoMap(processDescriptor); + DesUtils.checkNodeDuplicate(toDesignerNodeString(processNodeInfoMap.values())); ExtActProcess process = extActProcessMapper.selectById(processDefinitionId); //第一次进入设计流程的时候,processDefinitionId始终为零,导致流程保存多份 if ("0".equals(processDefinitionId) && process == null) { @@ -1351,19 +1368,16 @@ public class ExtActProcessServiceImpl extends ServiceImpl oldNodeList = extActProcessNodeMapper.queryAllNodesByProcessKey(processkey); - List newNodeList = new ArrayList<>(); - if (nodes != null && nodes.length() > 3) { - String[] temp = nodes.split("@@@"); - for (int i = 0; i < temp.length; i++) { - ExtActProcessNode processnode = null; - String[] fileds = temp[i].split("###"); - String tid = fileds[0].substring(3); - String name = fileds[1].substring(9); - newNodeList.add(tid); + Map oldNodeMap = oldNodeList.stream() + .collect(Collectors.toMap(ExtActProcessNode::getProcessNodeCode, node -> node, (left, right) -> left)); + // newNodeList 是 XML 中真实存在的可配置任务节点,后续只删除不在此清单中的旧配置。 + List newNodeList = new ArrayList<>(processNodeInfoMap.keySet()); + for (ProcessNodeInfo nodeInfo : processNodeInfoMap.values()) { + String tid = nodeInfo.getId(); + String name = nodeInfo.getName(); + ExtActProcessNode processnode = oldNodeMap.get(tid); // 2.查询节点是否存在 - processnode = oldNodeList.stream().filter(node -> node.getProcessNodeCode().equals(tid)).findFirst().orElse(null); - //processnode = extActProcessNodeMapper.queryByNodeCodeAndProcessKey(processkey, tid); if (processnode == null) { processnode = new ExtActProcessNode(); processnode.setProcessNodeCode(tid); @@ -1385,7 +1399,6 @@ public class ExtActProcessServiceImpl extends ServiceImpl needToDeleteNodes = oldNodeList.stream().filter(element -> !newNodeList.contains(element.getProcessNodeCode()) && !element.getProcessNodeCode().toLowerCase().contains("start")) + List needToDeleteNodes = oldNodeList.stream().filter(element -> !newNodeList.contains(element.getProcessNodeCode())) .map(ExtActProcessNode::getProcessNodeCode).collect(Collectors.toList()); if(!CollectionUtils.isEmpty(needToDeleteNodes)){ extActProcessNodeMapper.deleteNodeByProcessId(process.getId(), needToDeleteNodes); } - } //update-end---author:scott ---date::2023-11-18 for:【QQYUN-7020】流程设计器里面节点删除了,但是配置里面节点未删除 return j; } + + /** + * 从 BPMN XML 解析流程配置节点。XML 解析失败时直接中断保存,避免按不完整节点清单删除已有配置。 + */ + private Map parseProcessNodeInfoMap(String processDescriptor) throws Exception { + if (oConvertUtils.isEmpty(processDescriptor)) { + throw new Exception("Process XML cannot be empty"); + } + XMLInputFactory xif = XMLInputFactory.newInstance(); + xif.setProperty(XMLInputFactory.SUPPORT_DTD, false); + xif.setProperty("javax.xml.stream.isSupportingExternalEntities", false); + try (InputStreamReader xmlIn = new InputStreamReader(new ByteArrayInputStream(processDescriptor.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8)) { + XMLStreamReader xtr = xif.createXMLStreamReader(xmlIn); + BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr); + Map nodeInfoMap = new LinkedHashMap<>(); + for (org.flowable.bpmn.model.Process process : bpmnModel.getProcesses()) { + collectProcessNodeInfos(process, nodeInfoMap); + } + return nodeInfoMap; + } catch (Exception e) { + log.error("Parse BPMN XML process nodes failed", e); + throw new Exception("Parse BPMN XML process nodes failed, please check process designer XML", e); + } + } + + private void collectProcessNodeInfos(FlowElementsContainer container, Map nodeInfoMap) { + if (container == null || CollectionUtils.isEmpty(container.getFlowElements())) { + return; + } + for (FlowElement flowElement : container.getFlowElements()) { + if (isConfigurableProcessNode(flowElement) && oConvertUtils.isNotEmpty(flowElement.getId())) { + nodeInfoMap.put(flowElement.getId(), new ProcessNodeInfo(flowElement.getId(), oConvertUtils.getString(flowElement.getName(), ""))); + } + // 子流程本身不作为配置节点保存,但要递归进入子流程内部收集任务节点。 + if (flowElement instanceof FlowElementsContainer) { + collectProcessNodeInfos((FlowElementsContainer) flowElement, nodeInfoMap); + } + } + } + + private boolean isConfigurableProcessNode(FlowElement flowElement) { + // 只同步任务节点,排除开始、结束事件以及 bpmn:SubProcess 容器。 + return flowElement instanceof Task; + } + + private String toDesignerNodeString(Collection nodeInfos) { + if (CollectionUtils.isEmpty(nodeInfos)) { + return ""; + } + StringBuilder nodes = new StringBuilder(); + for (ProcessNodeInfo nodeInfo : nodeInfos) { + nodes.append("id=").append(nodeInfo.getId()).append("###nodeName=").append(nodeInfo.getName()).append("@@@"); + } + return nodes.toString(); + } + + private static class ProcessNodeInfo { + private final String id; + private final String name; + + private ProcessNodeInfo(String id, String name) { + this.id = id; + this.name = name; + } + + private String getId() { + return id; + } + + private String getName() { + return name; + } + } }