Browse Source

!322 feature Node实例sql插件存储
Merge pull request !322 from jay-li/instacne-spi

铂赛东 4 months ago
parent
commit
028f1e0287
37 changed files with 1815 additions and 135 deletions
  1. 12 71
      liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/LiteFlowChainELBuilder.java
  2. 50 0
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/entity/InstanceInfoDto.java
  3. 246 0
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/instanceId/BaseNodeInstanceIdManageSpi.java
  4. 50 0
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/instanceId/DefaultNodeInstanceIdManageSpiImpl.java
  5. 39 0
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/instanceId/NodeInstanceIdManageSpi.java
  6. 42 0
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/instanceId/NodeInstanceIdManageSpiHolder.java
  7. 14 0
      liteflow-core/src/main/java/com/yomahub/liteflow/property/LiteflowConfig.java
  8. 2 2
      liteflow-core/src/main/java/com/yomahub/liteflow/util/JsonUtil.java
  9. 3 1
      liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/constant/ReadType.java
  10. 15 0
      liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/constant/SqlReadConstant.java
  11. 57 0
      liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/spi/instanceId/SqlNodeInstanceIdManageSpiImpl.java
  12. 6 0
      liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/SQLXmlELParser.java
  13. 21 1
      liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/read/AbstractSqlRead.java
  14. 10 0
      liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/read/SqlRead.java
  15. 3 0
      liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/read/SqlReadFactory.java
  16. 14 0
      liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/read/impl/ChainRead.java
  17. 85 0
      liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/read/impl/InstanceIdRead.java
  18. 20 5
      liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/read/impl/ScriptRead.java
  19. 39 0
      liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/read/vo/InstanceIdVO.java
  20. 68 0
      liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/util/JDBCHelper.java
  21. 24 0
      liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/util/LiteFlowJdbcUtil.java
  22. 64 0
      liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/vo/SQLParserVO.java
  23. 1 0
      liteflow-rule-plugin/liteflow-rule-sql/src/main/resources/META-INF/services/com.yomahub.liteflow.flow.instanceId.NodeInstanceIdManageSpi
  24. 1 1
      liteflow-solon-plugin/src/main/resources/META-INF/liteflow-default.properties
  25. 334 53
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/instanceIds/InstanceIdELSpringTest.java
  26. 21 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/instanceIds/cmp/ESwitchCmp.java
  27. 21 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/instanceIds/cmp/X1Cmp.java
  28. 26 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/instanceIds/cmp/X2Cmp.java
  29. 17 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/instanceIds/cmp/XCmp.java
  30. 20 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/instanceIds/flow.el.xml
  31. 21 0
      liteflow-testcase-el/liteflow-testcase-el-sql-springboot/src/test/java/com/yomahub/liteflow/test/sql/cmp/ESwitchCmp.java
  32. 17 0
      liteflow-testcase-el/liteflow-testcase-el-sql-springboot/src/test/java/com/yomahub/liteflow/test/sql/cmp/XCmp.java
  33. 401 0
      liteflow-testcase-el/liteflow-testcase-el-sql-springboot/src/test/java/com/yomahub/liteflow/test/sqlInstanceId/SQLWithXmlELInstanceIdSpringbootTest.java
  34. 21 0
      liteflow-testcase-el/liteflow-testcase-el-sql-springboot/src/test/java/com/yomahub/liteflow/test/sqlroute/cmp/ESwitchCmp.java
  35. 26 0
      liteflow-testcase-el/liteflow-testcase-el-sql-springboot/src/test/resources/application-instanceId-xml.properties
  36. 3 0
      liteflow-testcase-el/liteflow-testcase-el-sql-springboot/src/test/resources/sql/data.sql
  37. 1 1
      liteflow-testcase-el/liteflow-testcase-el-sql-springboot/src/test/resources/sql/schema.sql

+ 12 - 71
liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/LiteFlowChainELBuilder.java

@@ -1,11 +1,8 @@
 package com.yomahub.liteflow.builder.el;
 
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.util.*;
-import cn.hutool.crypto.digest.MD5;
 import com.fasterxml.jackson.databind.JsonMappingException;
-import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.ql.util.express.DefaultContext;
 import com.ql.util.express.ExpressRunner;
@@ -23,20 +20,14 @@ import com.yomahub.liteflow.flow.element.Executable;
 import com.yomahub.liteflow.flow.element.Node;
 import com.yomahub.liteflow.flow.element.condition.AndOrCondition;
 import com.yomahub.liteflow.flow.element.condition.NotCondition;
+import com.yomahub.liteflow.flow.instanceId.NodeInstanceIdManageSpi;
+import com.yomahub.liteflow.flow.instanceId.NodeInstanceIdManageSpiHolder;
 import com.yomahub.liteflow.log.LFLog;
 import com.yomahub.liteflow.log.LFLoggerManager;
 import com.yomahub.liteflow.property.LiteflowConfig;
 import com.yomahub.liteflow.property.LiteflowConfigGetter;
 
-import java.io.File;
 import java.util.*;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import static com.yomahub.liteflow.common.ChainConstant.NODE_INSTANCE_PATH;
-import static com.yomahub.liteflow.common.ChainConstant.USER_DIR;
-import static com.yomahub.liteflow.util.JsonUtil.parseObject;
-import static com.yomahub.liteflow.util.JsonUtil.toJsonString;
-import static com.yomahub.liteflow.util.SerialsUtil.generateShortUUID;
 
 
 /**
@@ -249,70 +240,13 @@ public class LiteFlowChainELBuilder {
 		}
 	}
 
+	// 往condition里设置instanceId
     private void setNodesInstanceId(Condition condition) {
-        File nodeDir = new File(System.getProperty(USER_DIR)+  File.separator + NODE_INSTANCE_PATH + File.separator + this.chain.getChainId());
-        String elMd5 = MD5.create().digestHex(chain.getEl());
-
-        // 如果文件不存在,或者文件内容不是当前el,则写入
-        if (FileUtil.isEmpty(nodeDir) || !FileUtil.readLines(nodeDir.getPath(), CharsetUtil.UTF_8).get(0).equals(elMd5)) {
-            writeNodeInstanceId(nodeDir, condition, elMd5);
-        } else {
-            // 文件存在,则直接读取
-            List<String> nodeList = FileUtil.readLines(nodeDir.getPath(), CharsetUtil.UTF_8);
-
-            Map<String, List<String>> executableMap = new HashMap<>();
-            for (int i = 1; i < nodeList.size(); i++) {
-                JsonNode groupKeyAndInstanceIds = parseObject(nodeList.get(i));
-
-				Iterator<String> fieldNames = groupKeyAndInstanceIds.fieldNames();
-				while (fieldNames.hasNext()) {
-					String key = fieldNames.next();
-					JsonNode valueNode = groupKeyAndInstanceIds.get(key);
-					if (valueNode.isArray()) {
-						List<String> valueList = new ArrayList<>();
-						for (JsonNode item : valueNode) {
-							valueList.add(item.asText());
-						}
-						executableMap.put(key, valueList);
-					}
-	            }
-			}
+		NodeInstanceIdManageSpi nodeInstanceIdManageSpi = NodeInstanceIdManageSpiHolder.getInstance().getNodeInstanceIdManageSpi();
 
-            condition.getExecutableGroup().forEach((key, executables) -> {
-                AtomicInteger index = new AtomicInteger(0);
-                executables.forEach(executable -> {
-                    if (executableMap.containsKey(key)) {
-                        if (executable instanceof Node) {
-                            ((Node) executable).setInstanceId((executableMap.get(key).get(index.getAndIncrement())));
-                        }
-                    }
-                });
-            });
-        }
+		nodeInstanceIdManageSpi.setNodesInstanceId(condition, chain);
     }
 
-    // 写入时第一行为el的md5,第二行为json格式的groupKey和对应的实例id
-    private void writeNodeInstanceId(File nodeDir, Condition condition, String elMd5) {
-        ArrayList<String> writeList = new ArrayList<>();
-        writeList.add(elMd5);
-
-        condition.getExecutableGroup().forEach((key, executables) -> {
-            Map<String, List<String>> groupKeyAndInstanceIds = new HashMap<>();
-            List<String> instanceIds = new ArrayList<>();
-
-            executables.forEach(executable -> {
-                if (executable instanceof Node) {
-                    ((Node) executable).setInstanceId(generateShortUUID());
-                    instanceIds.add(((Node) executable).getInstanceId());
-                }
-            });
-
-			groupKeyAndInstanceIds.put(key, instanceIds);
-			writeList.add(toJsonString(groupKeyAndInstanceIds));
-		});
-
-        FileUtil.writeLines(writeList, nodeDir.getPath(), CharsetUtil.UTF_8);
-    }
 
 	public LiteFlowChainELBuilder setNamespace(String nameSpace){
 		if (StrUtil.isBlank(nameSpace)) {
@@ -452,6 +386,7 @@ public class LiteFlowChainELBuilder {
 		if (StrUtil.isBlank(chain.getEl())){
 			throw new FlowSystemException(StrUtil.format("no el content in this unCompile chain[{}]", chain.getChainId()));
 		}
+		LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
 
 		// 如果chain已经有Condition了,那说明已经解析过了,这里只对未解析的chain进行解析
 		if (CollUtil.isNotEmpty(chain.getConditionList())){
@@ -496,6 +431,12 @@ public class LiteFlowChainELBuilder {
 				throw new QLException(StrUtil.format("parse el fail,el:[{}]", chain.getEl()));
 			}
 
+			// 设置实例id
+			if (liteflowConfig.getEnableNodeInstanceId()) {
+				NodeInstanceIdManageSpi nodeInstanceIdManageSpi = NodeInstanceIdManageSpiHolder.getInstance().getNodeInstanceIdManageSpi();
+				nodeInstanceIdManageSpi.setNodesInstanceId(condition, chain);
+			}
+
 			// 把主要的condition加入
 			chain.setConditionList(CollUtil.toList(condition));
 

+ 50 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/flow/entity/InstanceInfoDto.java

@@ -0,0 +1,50 @@
+package com.yomahub.liteflow.flow.entity;
+
+/**
+ * InstanceInfo Dto
+ * {"chainId":"chain1","nodeId":"a","instanceId":"XXXX","index":0}
+ * @author jay li
+ * @since 2.13.0
+ */
+public class InstanceInfoDto {
+
+    private String chainId;
+
+    private String nodeId;
+
+    private String instanceId;
+
+    private Integer index;
+
+    public String getChainId() {
+        return chainId;
+    }
+
+    public void setChainId(String chainId) {
+        this.chainId = chainId;
+    }
+
+    public String getNodeId() {
+        return nodeId;
+    }
+
+    public void setNodeId(String nodeId) {
+        this.nodeId = nodeId;
+    }
+
+    public String getInstanceId() {
+        return instanceId;
+    }
+
+    public void setInstanceId(String instanceId) {
+        this.instanceId = instanceId;
+    }
+
+    public Integer getIndex() {
+        return index;
+    }
+
+    public void setIndex(Integer index) {
+        this.index = index;
+    }
+}

+ 246 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/flow/instanceId/BaseNodeInstanceIdManageSpi.java

@@ -0,0 +1,246 @@
+package com.yomahub.liteflow.flow.instanceId;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.crypto.digest.MD5;
+import com.yomahub.liteflow.flow.FlowBus;
+import com.yomahub.liteflow.flow.element.Chain;
+import com.yomahub.liteflow.flow.element.Condition;
+import com.yomahub.liteflow.flow.element.Executable;
+import com.yomahub.liteflow.flow.element.Node;
+import com.yomahub.liteflow.flow.entity.InstanceInfoDto;
+import org.apache.commons.lang.StringUtils;
+
+import java.util.*;
+
+import static com.yomahub.liteflow.util.JsonUtil.*;
+import static com.yomahub.liteflow.util.SerialsUtil.generateShortUUID;
+
+/**
+ * @author lhh
+ * @since 2.13.0
+ */
+public abstract class BaseNodeInstanceIdManageSpi implements NodeInstanceIdManageSpi {
+
+
+    /**
+     * 根据chainId instanceId返回Node节点信息
+     */
+    @Override
+    public Node getNodeByIdAndInstanceId(String chainId, String instanceId) {
+        if (StringUtils.isBlank(chainId) || StringUtils.isBlank(instanceId)) {
+            return null;
+        }
+        Chain chain = FlowBus.getChain(chainId);
+        if (chain == null) {
+            return null;
+        }
+
+        List<Condition> conditionList = chain.getConditionList();
+
+        return getNodeFromConditions(conditionList, instanceId);
+    }
+
+
+    /**
+     * 根据nodeId和index返回Node节点信息
+     */
+    @Override
+    public Node getNodeByIdAndIndex(String chainId, String nodeId, Integer index) {
+        if (StringUtils.isBlank(chainId) || index == null) {
+            return null;
+        }
+        Chain chain = FlowBus.getChain(chainId);
+        if (chain == null) {
+            return null;
+        }
+
+        List<Condition> conditionList = chain.getConditionList();
+
+        return getNodeFromConditions(conditionList, nodeId, index);
+    }
+
+
+    /**
+     * 根据nodeId返回instanceId list
+     */
+    @Override
+    public List<String> getNodeInstanceIds(String chainId, String nodeId) {
+        if (StringUtils.isBlank(chainId) || StringUtils.isBlank(nodeId)) {
+            return Collections.emptyList();
+        }
+        // 第一行为elMd5 第二行为实例id json格式信息
+        List<String> instanceIdFile = readInstanceIdFile(chainId);
+
+        List<String> instanceIds = new ArrayList<>();
+        for (int i = 1; i < instanceIdFile.size(); i++) {
+            List<InstanceInfoDto> instanceInfos = parseList(instanceIdFile.get(i), InstanceInfoDto.class);
+
+            for (InstanceInfoDto dto : instanceInfos) {
+                if (Objects.equals(dto.getNodeId(), nodeId)) {
+                    instanceIds.add(dto.getInstanceId());
+                }
+            }
+        }
+
+        return instanceIds;
+    }
+
+
+    /**
+     * 从conditions 根据instanceId获取node节点
+     */
+    private Node getNodeFromConditions(List<Condition> conditionList, String instanceId) {
+        if (CollUtil.isEmpty(conditionList)) {
+            return null;
+        }
+
+        for (Condition condition : conditionList) {
+            List<Node> allNodeInCondition = condition.getAllNodeInCondition();
+
+            for (Node node : allNodeInCondition) {
+                if (Objects.equals(node.getInstanceId(), instanceId)) {
+                    return node;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 根据nodeId和index获取node节点
+     */
+    private Node getNodeFromConditions(List<Condition> conditionList, String nodeId, Integer index) {
+        if (CollUtil.isEmpty(conditionList)) {
+            return null;
+        }
+
+        Map<String, Integer> idCntMap = new HashMap<>();
+        for (Condition condition : conditionList) {
+            List<Node> allNodeInCondition = condition.getAllNodeInCondition();
+
+            for (Node node : allNodeInCondition) {
+                idCntMap.put(node.getId(), idCntMap.getOrDefault(node.getId(), -1) + 1);
+                if (Objects.equals(node.getId(), nodeId) && Objects.equals(idCntMap.get(node.getId()), index)) {
+                    return node;
+                }
+            }
+        }
+        return null;
+    }
+
+
+    /**
+     * 根据实例id获取 节点实例定位
+     */
+    @Override
+    public String getNodeLocationById(String chainId, String instanceId) {
+        if (StringUtils.isBlank(chainId) || StringUtils.isBlank(instanceId)) {
+            return "";
+        }
+        // 第一行为elMd5 第二行为实例id json格式信息
+        List<String> instanceIdFile = readInstanceIdFile(chainId);
+
+        for (int i = 1; i < instanceIdFile.size(); i++) {
+            List<InstanceInfoDto> instanceInfos = parseList(instanceIdFile.get(i), InstanceInfoDto.class);
+
+            for (InstanceInfoDto dto : instanceInfos) {
+                if (Objects.equals(dto.getInstanceId(), instanceId)) {
+                    return dto.getNodeId() + "(" + dto.getIndex() + ")";
+                }
+            }
+        }
+
+        return "";
+    }
+
+    /**
+     * 往condition里设置instanceId
+     */
+    @Override
+    public void setNodesInstanceId(Condition condition, Chain chain) {
+        NodeInstanceIdManageSpi nodeInstanceIdManageSpi = NodeInstanceIdManageSpiHolder.getInstance().getNodeInstanceIdManageSpi();
+
+        String elMd5 = MD5.create().digestHex(chain.getEl());
+        String chainId = chain.getChainId();
+        List<String> instanceIdFile = nodeInstanceIdManageSpi.readInstanceIdFile(chainId);
+
+        // 如果文件不存在,或者文件内容不是当前el,则写入
+        if (CollUtil.isEmpty(instanceIdFile) || !instanceIdFile.get(0).equals(elMd5)) {
+            nodeInstanceIdManageSpi.writeInstanceIdFile(writeNodeInstanceId(condition, chainId), elMd5, chainId);
+        } else {
+            // 文件存在,则直接读取
+            List<InstanceInfoDto> instanceInfos = new ArrayList<>();
+            for (int i = 1; i < instanceIdFile.size(); i++) {
+                instanceInfos = parseList(instanceIdFile.get(i), InstanceInfoDto.class);
+            }
+            List<InstanceInfoDto> finalInstanceInfos = instanceInfos;
+
+            setInstanceIdFromFile(finalInstanceInfos, chainId, condition.getAllNodeInCondition());
+        }
+    }
+
+    /**
+     * 从instanceIdFile里设置instanceId
+     */
+    private void setInstanceIdFromFile(List<InstanceInfoDto> finalInstanceInfos, String chainId, List<Node> nodeList) {
+        if (CollUtil.isEmpty(nodeList)) {
+            return;
+        }
+        Map<String, Integer> idCntMap = new HashMap<>();
+
+        nodeList.forEach(node -> {
+            idCntMap.put(node.getId(), idCntMap.getOrDefault(node.getId(), -1) + 1);
+
+            for (InstanceInfoDto dto : finalInstanceInfos) {
+                if (Objects.equals(dto.getNodeId(), node.getId())
+                        && Objects.equals(dto.getChainId(), chainId)
+                        && Objects.equals(dto.getIndex(), idCntMap.get(node.getId()))) {
+                    node.setInstanceId(dto.getInstanceId());
+                    break;
+                }
+            }
+        });
+    }
+
+    /**
+     * 写入时第一行为el的md5,第二行为json格式的groupKey和对应的nodeId 和实例id
+     * instanceId  a_XXX_0
+     * {"chainId":"chain1","nodeId":"a","instanceId":"XXXX","index":0},
+     */
+    private List<InstanceInfoDto> writeNodeInstanceId(Condition condition, String chainId) {
+        ArrayList<InstanceInfoDto> instanceInfos = new ArrayList<>();
+
+        addInstanceIdFromExecutableGroup(instanceInfos, condition.getAllNodeInCondition(), chainId);
+
+        return instanceInfos;
+    }
+
+    // 往instanceInfos里添加实例id
+    private void addInstanceIdFromExecutableGroup(List<InstanceInfoDto> instanceInfos, List<Node> nodeList,
+                                                  String chainId) {
+        if (CollUtil.isEmpty(nodeList)) {
+            return;
+        }
+
+        Map<String, Integer> idCntMap = new HashMap<>();
+
+        nodeList.forEach(node -> {
+            InstanceInfoDto instanceInfoDto = new InstanceInfoDto();
+
+            instanceInfoDto.setChainId(chainId);
+            instanceInfoDto.setNodeId(node.getId());
+
+            String shortUUID = generateShortUUID();
+
+            idCntMap.put(node.getId(), idCntMap.getOrDefault(node.getId(), -1) + 1);
+
+            String instanceId = node.getId() + "_" + shortUUID + "_" + idCntMap.get(node.getId());
+
+            node.setInstanceId(instanceId);
+            instanceInfoDto.setInstanceId(instanceId);
+            instanceInfoDto.setIndex(idCntMap.get(node.getId()));
+
+            instanceInfos.add(instanceInfoDto);
+        });
+    }
+}

+ 50 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/flow/instanceId/DefaultNodeInstanceIdManageSpiImpl.java

@@ -0,0 +1,50 @@
+package com.yomahub.liteflow.flow.instanceId;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.CharsetUtil;
+import com.yomahub.liteflow.flow.entity.InstanceInfoDto;
+import com.yomahub.liteflow.util.JsonUtil;
+import org.apache.commons.lang.StringUtils;
+
+import java.io.File;
+import java.util.*;
+
+import static com.yomahub.liteflow.common.ChainConstant.NODE_INSTANCE_PATH;
+import static com.yomahub.liteflow.common.ChainConstant.USER_DIR;
+
+/**
+ * @author Jay li
+ * @since 2.13.0
+ */
+public class DefaultNodeInstanceIdManageSpiImpl extends BaseNodeInstanceIdManageSpi {
+
+    private final String basePath = System.getProperty(USER_DIR) + File.separator + NODE_INSTANCE_PATH + File.separator;
+
+    @Override
+    public List<String> readInstanceIdFile(String chainId) {
+        if (StringUtils.isBlank(chainId)) {
+            return Collections.emptyList();
+        }
+
+        File nodeDir = new File(basePath + chainId);
+        if (FileUtil.isEmpty(nodeDir)) {
+            return Collections.emptyList();
+        }
+        return FileUtil.readLines(nodeDir.getPath(), CharsetUtil.UTF_8);
+    }
+
+    @Override
+    public void writeInstanceIdFile(List<InstanceInfoDto> instanceIdList, String elMd5, String chainId) {
+        if (StringUtils.isBlank(chainId) || CollUtil.isEmpty(instanceIdList)) {
+            return;
+        }
+        File nodeDir = new File(basePath + chainId);
+        List<String> writeContent = new ArrayList<>();
+        writeContent.add(elMd5);
+        writeContent.add(JsonUtil.toJsonString(instanceIdList));
+
+        FileUtil.writeLines(writeContent, nodeDir.getPath(), CharsetUtil.UTF_8);
+    }
+
+}

+ 39 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/flow/instanceId/NodeInstanceIdManageSpi.java

@@ -0,0 +1,39 @@
+package com.yomahub.liteflow.flow.instanceId;
+
+import com.yomahub.liteflow.flow.element.Chain;
+import com.yomahub.liteflow.flow.element.Condition;
+import com.yomahub.liteflow.flow.element.Node;
+import com.yomahub.liteflow.flow.entity.InstanceInfoDto;
+
+import java.util.List;
+
+
+/**
+ * @author Jay li
+ * @since 2.13.0
+ */
+public interface NodeInstanceIdManageSpi {
+
+    // 拿文件保存路径, 不同插件不同实现
+    // 读取文件内容 核对数据
+    List<String> readInstanceIdFile(String chainId);
+
+    // 写入文件保存
+    void writeInstanceIdFile(List<InstanceInfoDto> instanceIdList, String elMd5, String chainId);
+
+    // 根据实例id获取 节点实例定位
+    String getNodeLocationById(String chainId, String instanceId);
+
+    // 根据chainId instanceId返回Node节点信息
+    Node getNodeByIdAndInstanceId(String chainId, String instanceId);
+
+    // 根据nodeId和index返回Node节点信息
+    Node getNodeByIdAndIndex(String chainId, String nodeId, Integer index);
+
+    // 根据nodeId返回instanceId list
+    List<String> getNodeInstanceIds(String chainId, String nodeId);
+
+    // 设置node节点的实例id
+    void setNodesInstanceId(Condition condition, Chain chain);
+
+}

+ 42 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/flow/instanceId/NodeInstanceIdManageSpiHolder.java

@@ -0,0 +1,42 @@
+package com.yomahub.liteflow.flow.instanceId;
+
+import cn.hutool.core.util.ObjectUtil;
+
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+/**
+ * @author Jay li
+ * @since 2.13.0
+ */
+public class NodeInstanceIdManageSpiHolder {
+    private NodeInstanceIdManageSpi nodeInstanceIdManageSpi;
+
+    private static final NodeInstanceIdManageSpiHolder INSTANCE = new NodeInstanceIdManageSpiHolder();
+
+    public static void init() {
+        ServiceLoader<NodeInstanceIdManageSpi> loader = ServiceLoader.load(NodeInstanceIdManageSpi.class);
+        Iterator<NodeInstanceIdManageSpi> iterator = loader.iterator();
+        if (iterator.hasNext()) {
+            INSTANCE.setNodeInstanceIdManageSpi(iterator.next());
+        } else {
+            INSTANCE.setNodeInstanceIdManageSpi(new DefaultNodeInstanceIdManageSpiImpl());
+        }
+    }
+
+    public static NodeInstanceIdManageSpiHolder getInstance() {
+        return INSTANCE;
+    }
+
+    public NodeInstanceIdManageSpi getNodeInstanceIdManageSpi() {
+        if (ObjectUtil.isNull(nodeInstanceIdManageSpi)) {
+            init();
+        }
+        return nodeInstanceIdManageSpi;
+    }
+
+    public void setNodeInstanceIdManageSpi(NodeInstanceIdManageSpi nodeInstanceIdManageSpi) {
+        this.nodeInstanceIdManageSpi = nodeInstanceIdManageSpi;
+    }
+
+}

+ 14 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/property/LiteflowConfig.java

@@ -113,6 +113,9 @@ public class LiteflowConfig {
 	//是否启用节点实例ID
 	private Boolean enableNodeInstanceId;
 
+	// instance id 生成器
+	private String instanceIdGeneratorClass;
+
 	public Boolean getEnableMonitorFile() {
 		return enableMonitorFile;
 	}
@@ -481,4 +484,15 @@ public class LiteflowConfig {
 	public void setEnableNodeInstanceId(Boolean enableNodeInstanceId) {
 		this.enableNodeInstanceId = enableNodeInstanceId;
 	}
+
+	public String getInstanceIdGeneratorClass() {
+		if (StrUtil.isBlank(this.instanceIdGeneratorClass)) {
+			return "com.yomahub.liteflow.flow.id.DefaultRequestIdGenerator";
+		}
+		return instanceIdGeneratorClass;
+	}
+
+	public void setInstanceIdGeneratorClass(String instanceIdGeneratorClass) {
+		this.instanceIdGeneratorClass = instanceIdGeneratorClass;
+	}
 }

+ 2 - 2
liteflow-core/src/main/java/com/yomahub/liteflow/util/JsonUtil.java

@@ -3,7 +3,6 @@ package com.yomahub.liteflow.util;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -13,6 +12,7 @@ import com.yomahub.liteflow.log.LFLog;
 import com.yomahub.liteflow.log.LFLoggerManager;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.TimeZone;
 
@@ -80,7 +80,7 @@ public class JsonUtil {
 
 	public static <T> List<T> parseList(String json, Class<T> clazz) {
 		if (StrUtil.isEmpty(json)) {
-			return null;
+			return Collections.emptyList();
 		}
 		try {
 			CollectionType listType = objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, clazz);

+ 3 - 1
liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/constant/ReadType.java

@@ -5,9 +5,11 @@ package com.yomahub.liteflow.parser.constant;
  *
  * @author tangkc
  * @author houxinyu
+ * @author Jay li
  * @since 2.11.1
  */
 public enum ReadType {
     CHAIN,
-    SCRIPT;
+    SCRIPT,
+    INSTANCE_ID;
 }

+ 15 - 0
liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/constant/SqlReadConstant.java

@@ -10,9 +10,24 @@ package com.yomahub.liteflow.parser.constant;
  * @since 2.11.1
  */
 public class SqlReadConstant {
+    public static final String INSTANT_SELECT_SQL = "SELECT count(*) FROM {} where {} = '{}' and {} = '{}' ";
+
+    public static final String INSTANT_UPDATE_SQL = "UPDATE {} SET {} = '{}',{} = '{}' WHERE {} = '{}' and {} = '{}'";
+
+    public static final String INSTANT_INSERT_SQL = "INSERT INTO {} ({},{},{},{}) VALUES ('{}','{}','{}','{}')";
+
+    public static final String INSTANT_CREATE_TABLE_SQL = "create table IF NOT EXISTS `node_instance_id_table`\n" +
+            "(\n" +
+            "    `application_name`     varchar(32)   NOT NULL,\n" +
+            "    `chain_id`           varchar(32)   NOT NULL,\n" +
+            "    `el_data_md5`          varchar(128)   NOT NULL,\n" +
+            "    `node_instance_id_map_json`     varchar(1024)   NOT NULL\n" +
+            ");";
 
     public static final String SQL_PATTERN = "SELECT * FROM {} WHERE {}='{}'";
 
+    public static final String SQL_PATTERN_WITH_CHAIN_ID = "SELECT * FROM {} WHERE {}='{}' and  {}='{}'";
+
     public static final String SCRIPT_SQL_CHECK_PATTERN = "SELECT 1 FROM {} ";
 
     public static final String SCRIPT_SQL_PATTERN = "SELECT * FROM {} WHERE {}='{}'";

+ 57 - 0
liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/spi/instanceId/SqlNodeInstanceIdManageSpiImpl.java

@@ -0,0 +1,57 @@
+package com.yomahub.liteflow.parser.spi.instanceId;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.StrUtil;
+import com.yomahub.liteflow.flow.entity.InstanceInfoDto;
+import com.yomahub.liteflow.flow.instanceId.BaseNodeInstanceIdManageSpi;
+import com.yomahub.liteflow.parser.constant.ReadType;
+import com.yomahub.liteflow.parser.constant.SqlReadConstant;
+import com.yomahub.liteflow.parser.sql.read.SqlRead;
+import com.yomahub.liteflow.parser.sql.read.SqlReadFactory;
+import com.yomahub.liteflow.parser.sql.read.vo.InstanceIdVO;
+import com.yomahub.liteflow.parser.sql.util.JDBCHelper;
+import com.yomahub.liteflow.parser.sql.vo.SQLParserVO;
+import com.yomahub.liteflow.util.JsonUtil;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * sql方式生成instanceId
+ *
+ * @author Jay li
+ * @since 2.12.4
+ */
+
+public class SqlNodeInstanceIdManageSpiImpl extends BaseNodeInstanceIdManageSpi {
+
+    @Override
+    public List<String> readInstanceIdFile(String chainId) {
+        SqlRead<InstanceIdVO> insIdRead = SqlReadFactory.getSqlRead(ReadType.INSTANCE_ID);
+        List<InstanceIdVO> readResult = insIdRead.read(chainId);
+
+        if (CollectionUtil.isNotEmpty(readResult)) {
+            return Arrays.asList(readResult.get(0).getElDataMd5(), readResult.get(0).getNodeInstanceIdMapJson());
+        }
+
+        return Collections.emptyList();
+    }
+
+    @Override
+    public void writeInstanceIdFile(List<InstanceInfoDto> instanceIdList, String elMd5, String chainId) {
+        JDBCHelper jdbcHelper = JDBCHelper.getInstance();
+        SQLParserVO conf = jdbcHelper.getSqlParserVO();
+
+        String insertSql = StrUtil.format(SqlReadConstant.INSTANT_INSERT_SQL, conf.getInstanceIdTableName(), conf.getInstanceIdApplicationNameField(),
+                conf.getNodeInstanceIdMapJsonField(), conf.getElDataMd5Field(), conf.getInstanceChainIdField(), conf.getApplicationName(), JsonUtil.toJsonString(instanceIdList),
+                elMd5, chainId);
+        String updateSql = StrUtil.format(SqlReadConstant.INSTANT_UPDATE_SQL, conf.getInstanceIdTableName(), conf.getElDataMd5Field(), elMd5,
+                conf.getNodeInstanceIdMapJsonField(), JsonUtil.toJsonString(instanceIdList), conf.getInstanceChainIdField(), chainId, conf.getInstanceIdApplicationNameField(), conf.getApplicationName());
+        String selectSql = StrUtil.format(SqlReadConstant.INSTANT_SELECT_SQL, conf.getInstanceIdTableName(), conf.getInstanceChainIdField(), chainId,
+                conf.getInstanceIdApplicationNameField(), conf.getApplicationName());
+
+        jdbcHelper.executeUpsert(selectSql, insertSql, updateSql);
+    }
+
+}

+ 6 - 0
liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/SQLXmlELParser.java

@@ -65,6 +65,12 @@ public class SQLXmlELParser extends ClassXmlFlowELParser {
             // 注册轮询任务
             SqlReadFactory.registerSqlReadPollTask(ReadType.CHAIN);
             SqlReadFactory.registerSqlReadPollTask(ReadType.SCRIPT);
+
+
+            // enable-node-instance-id=true 创建节点实例ID表 如果不存在
+            if (liteflowConfig.getEnableNodeInstanceId()) {
+                JDBCHelper.getInstance().createNodeInstanceIdTable();
+            }
         } catch (ELSQLException elsqlException) {
             throw elsqlException;
         } catch (Exception ex) {

+ 21 - 1
liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/read/AbstractSqlRead.java

@@ -3,7 +3,6 @@ package com.yomahub.liteflow.parser.sql.read;
 import cn.hutool.core.util.StrUtil;
 import com.yomahub.liteflow.log.LFLog;
 import com.yomahub.liteflow.log.LFLoggerManager;
-import com.yomahub.liteflow.parser.constant.ReadType;
 import com.yomahub.liteflow.parser.constant.SqlReadConstant;
 import com.yomahub.liteflow.parser.sql.exception.ELSQLException;
 import com.yomahub.liteflow.parser.sql.util.LiteFlowJdbcUtil;
@@ -19,6 +18,7 @@ import java.util.List;
  * @author tangkc
  * @author houxinyu
  * @author Bryan.Zhang
+ * @author Jay li
  * @since 2.11.1
  */
 public abstract class AbstractSqlRead<T> implements SqlRead<T> {
@@ -29,6 +29,18 @@ public abstract class AbstractSqlRead<T> implements SqlRead<T> {
         this.config = config;
     }
 
+    @Override
+    public List<T> read(String chainId) {
+        if (!needRead()) {
+            return new ArrayList<>();
+        }
+
+        checkConfig();
+        String sqlCmd = buildQuerySql(chainId);
+        return readList(sqlCmd);
+    }
+
+
     @Override
     public List<T> read() {
         // 如果不需要读取直接返回
@@ -38,6 +50,12 @@ public abstract class AbstractSqlRead<T> implements SqlRead<T> {
 
         checkConfig();
         String sqlCmd = buildQuerySql();
+
+        return readList(sqlCmd);
+    }
+
+
+    private List<T> readList(String sqlCmd) {
         // 如果允许,就打印 sql 语句
         logSqlIfEnable(sqlCmd);
 
@@ -87,6 +105,8 @@ public abstract class AbstractSqlRead<T> implements SqlRead<T> {
 
     public abstract String buildQuerySql();
 
+    public abstract String buildQuerySql(String chainId);
+
     public abstract void checkConfig();
 
     /**

+ 10 - 0
liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/read/SqlRead.java

@@ -9,6 +9,7 @@ import java.util.List;
  *
  * @author tangkc
  * @author houxinyu
+ * @author Jay li
  * @since 2.11.1
  */
 public interface SqlRead<T> {
@@ -20,6 +21,15 @@ public interface SqlRead<T> {
      */
     List<T> read();
 
+
+    /**
+     * 根据chainId 读取
+     *
+     * @return 返回读取到的数据
+     */
+    List<T> read(String chainId);
+
+
     /**
      * 类型
      *

+ 3 - 0
liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/read/SqlReadFactory.java

@@ -5,6 +5,7 @@ import com.yomahub.liteflow.parser.sql.polling.SqlReadPollTask;
 import com.yomahub.liteflow.parser.sql.polling.impl.ChainReadPollTask;
 import com.yomahub.liteflow.parser.sql.polling.impl.ScriptReadPollTask;
 import com.yomahub.liteflow.parser.sql.read.impl.ChainRead;
+import com.yomahub.liteflow.parser.sql.read.impl.InstanceIdRead;
 import com.yomahub.liteflow.parser.sql.read.impl.ScriptRead;
 import com.yomahub.liteflow.parser.sql.vo.SQLParserVO;
 
@@ -16,6 +17,7 @@ import java.util.Map;
  *
  * @author tangkc
  * @author houxinyu
+ * @author Jay li
  * @since 2.11.1
  */
 public class SqlReadFactory {
@@ -25,6 +27,7 @@ public class SqlReadFactory {
     public static void registerRead(SQLParserVO config) {
         READ_MAP.put(ReadType.CHAIN, new ChainRead(config));
         READ_MAP.put(ReadType.SCRIPT, new ScriptRead(config));
+        READ_MAP.put(ReadType.INSTANCE_ID, new InstanceIdRead(config));
     }
 
     public static void registerSqlReadPollTask(ReadType readType) {

+ 14 - 0
liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/read/impl/ChainRead.java

@@ -66,6 +66,20 @@ public class ChainRead extends AbstractSqlRead<ChainVO> {
         return StrUtil.format(SqlReadConstant.SQL_PATTERN, chainTableName, chainApplicationNameField, applicationName);
     }
 
+    @Override
+    public String buildQuerySql(String chainId) {
+        if (StrUtil.isNotBlank(super.config.getChainCustomSql())) {
+            return super.config.getChainCustomSql();
+        }
+
+        String chainTableName = super.config.getChainTableName();
+        String chainApplicationNameField = super.config.getChainApplicationNameField();
+        String applicationName = super.config.getApplicationName();
+
+        return StrUtil.format(SqlReadConstant.SQL_PATTERN_WITH_CHAIN_ID, chainTableName, chainApplicationNameField, applicationName,
+                super.config.getChainNameField(), chainId);
+    }
+
     @Override
     public void checkConfig() {
         String chainTableName = super.config.getChainTableName();

+ 85 - 0
liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/read/impl/InstanceIdRead.java

@@ -0,0 +1,85 @@
+package com.yomahub.liteflow.parser.sql.read.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.yomahub.liteflow.parser.constant.ReadType;
+import com.yomahub.liteflow.parser.constant.SqlReadConstant;
+import com.yomahub.liteflow.parser.sql.exception.ELSQLException;
+import com.yomahub.liteflow.parser.sql.read.AbstractSqlRead;
+import com.yomahub.liteflow.parser.sql.read.vo.InstanceIdVO;
+import com.yomahub.liteflow.parser.sql.vo.SQLParserVO;
+import org.apache.commons.lang.StringUtils;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Jay li
+ * @since 2.13.0
+ */
+
+public class InstanceIdRead extends AbstractSqlRead<InstanceIdVO> {
+
+    public InstanceIdRead(SQLParserVO config) {
+        super(config);
+    }
+
+    @Override
+    protected InstanceIdVO parse(ResultSet rs) throws SQLException {
+        InstanceIdVO idVO = new InstanceIdVO();
+        idVO.setChainId(getStringFromRsWithCheck(rs, super.config.getInstanceChainIdField()));
+        idVO.setElDataMd5(getStringFromRsWithCheck(rs, super.config.getElDataMd5Field()));
+        idVO.setNodeInstanceIdMapJson(getStringFromRsWithCheck(rs, super.config.getNodeInstanceIdMapJsonField()));
+        return idVO;
+    }
+
+    @Override
+    public boolean hasEnableFiled() {
+        return true;
+    }
+
+    @Override
+    public boolean getEnableFiledValue(ResultSet rs) {
+        return true;
+    }
+
+    @Override
+    public String buildQuerySql() {
+        String tableName = super.config.getInstanceIdTableName();
+        String applicationNameField = super.config.getInstanceIdApplicationNameField();
+        String applicationName = super.config.getApplicationName();
+
+        return StrUtil.format(SqlReadConstant.SQL_PATTERN, tableName,
+                applicationNameField, applicationName);
+    }
+
+    @Override
+    public String buildQuerySql(String chainId) {
+        String tableName = super.config.getInstanceIdTableName();
+        String chainNameField = super.config.getInstanceChainIdField();
+        String instanceIdApplicationNameField = super.config.getInstanceIdApplicationNameField();
+        String applicationName = super.config.getApplicationName();
+
+        if (StringUtils.isEmpty(chainId)) {
+            throw new IllegalArgumentException("You did not define the chainId");
+        }
+        return StrUtil.format(SqlReadConstant.SQL_PATTERN_WITH_CHAIN_ID, tableName, instanceIdApplicationNameField
+                , applicationName, chainNameField, chainId);
+    }
+
+    @Override
+    public void checkConfig() {
+        String tableName = super.config.getInstanceIdTableName();
+        String chainNameField = super.config.getInstanceChainIdField();
+        if (StrUtil.isBlank(tableName)) {
+            throw new ELSQLException("You did not define the tableName property");
+        }
+        if (StrUtil.isBlank(chainNameField)) {
+            throw new ELSQLException("You did not define the chainNameField property");
+        }
+    }
+
+    @Override
+    public ReadType type() {
+        return null;
+    }
+}

+ 20 - 5
liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/read/impl/ScriptRead.java

@@ -70,6 +70,21 @@ public class ScriptRead extends AbstractSqlRead<ScriptVO> {
                 applicationName);
     }
 
+    @Override
+    public String buildQuerySql(String scriptNodeId) {
+        if (StringUtils.isNotBlank(super.config.getScriptCustomSql())) {
+            return super.config.getScriptCustomSql();
+        }
+
+        String scriptTableName = super.config.getScriptTableName();
+        String scriptApplicationNameField = super.config.getScriptApplicationNameField();
+        String applicationName = super.config.getApplicationName();
+        String scriptIdField = super.config.getScriptIdField();
+        return StrUtil.format(SqlReadConstant.SQL_PATTERN_WITH_CHAIN_ID,
+                scriptTableName, scriptApplicationNameField, applicationName,
+                scriptIdField, scriptNodeId);
+    }
+
     @Override
     public void checkConfig() {
         String scriptTableName = super.config.getScriptTableName();
@@ -78,19 +93,19 @@ public class ScriptRead extends AbstractSqlRead<ScriptVO> {
         String scriptTypeField = super.config.getScriptTypeField();
         String scriptApplicationNameField = super.config.getScriptApplicationNameField();
 
-        if(StrUtil.isBlank(scriptTableName)){
+        if (StrUtil.isBlank(scriptTableName)) {
             throw new ELSQLException("You did not define the scriptTableName property");
         }
-        if(StrUtil.isBlank(scriptIdField)){
+        if (StrUtil.isBlank(scriptIdField)) {
             throw new ELSQLException("You did not define the scriptIdField property");
         }
-        if(StrUtil.isBlank(scriptDataField)){
+        if (StrUtil.isBlank(scriptDataField)) {
             throw new ELSQLException("You did not define the scriptDataField property");
         }
-        if(StrUtil.isBlank(scriptTypeField)){
+        if (StrUtil.isBlank(scriptTypeField)) {
             throw new ELSQLException("You did not define the scriptTypeField property");
         }
-        if(StrUtil.isBlank(scriptApplicationNameField)){
+        if (StrUtil.isBlank(scriptApplicationNameField)) {
             throw new ELSQLException("You did not define the scriptApplicationNameField property");
         }
     }

+ 39 - 0
liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/read/vo/InstanceIdVO.java

@@ -0,0 +1,39 @@
+package com.yomahub.liteflow.parser.sql.read.vo;
+
+/**
+ * @author Jay li
+ * @since 2.12.4
+ */
+
+public class InstanceIdVO {
+
+    private String chainId;
+
+    private String elDataMd5;
+
+    private String nodeInstanceIdMapJson;
+
+    public String getChainId() {
+        return chainId;
+    }
+
+    public void setChainId(String chainId) {
+        this.chainId = chainId;
+    }
+
+    public String getElDataMd5() {
+        return elDataMd5;
+    }
+
+    public void setElDataMd5(String elDataMd5) {
+        this.elDataMd5 = elDataMd5;
+    }
+
+    public String getNodeInstanceIdMapJson() {
+        return nodeInstanceIdMapJson;
+    }
+
+    public void setNodeInstanceIdMapJson(String nodeInstanceIdMapJson) {
+        this.nodeInstanceIdMapJson = nodeInstanceIdMapJson;
+    }
+}

+ 68 - 0
liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/util/JDBCHelper.java

@@ -17,7 +17,10 @@ import com.yomahub.liteflow.parser.sql.read.vo.ChainVO;
 import com.yomahub.liteflow.parser.sql.read.vo.ScriptVO;
 import com.yomahub.liteflow.parser.sql.vo.SQLParserVO;
 import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import java.sql.*;
 import java.util.*;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.ThreadFactory;
@@ -30,10 +33,12 @@ import static com.yomahub.liteflow.parser.constant.SqlReadConstant.*;
  * jdbc 工具类
  *
  * @author tangkc
+ * @author Jay li
  * @since 2.9.0
  */
 public class JDBCHelper {
 
+    private static final Logger log = LoggerFactory.getLogger(JDBCHelper.class);
     private SQLParserVO sqlParserVO;
 
     private static JDBCHelper INSTANCE;
@@ -152,6 +157,10 @@ public class JDBCHelper {
         this.sqlParserVO = sqlParserVO;
     }
 
+    public SQLParserVO getSqlParserVO() {
+        return sqlParserVO;
+    }
+
     public static ScheduledThreadPoolExecutor getPollExecutor() {
         return pollExecutor;
     }
@@ -159,4 +168,63 @@ public class JDBCHelper {
     public static void setPollExecutor(ScheduledThreadPoolExecutor pollExecutor) {
         JDBCHelper.pollExecutor = pollExecutor;
     }
+
+    /**
+     * 执行upsert,先查询,判断插入还是修改
+     *
+     * @param selectSql
+     * @param insertSql
+     * @param updateSql
+     * @throws ELSQLException
+     */
+    public void executeUpsert(String selectSql, String insertSql, String updateSql) throws ELSQLException {
+        Connection conn = null;
+        PreparedStatement stmt = null;
+        ResultSet resultSet = null;
+
+        try {
+            conn = LiteFlowJdbcUtil.getConn(sqlParserVO);
+            stmt = conn.prepareStatement(selectSql);
+
+            resultSet = stmt.executeQuery();
+            resultSet.next();
+            String executeSql;
+
+            if (resultSet.getInt(1) > 0) {
+                executeSql = updateSql;
+            } else {
+                executeSql = insertSql;
+            }
+
+            try (PreparedStatement insertStatement = conn.prepareStatement(executeSql)) {
+                insertStatement.executeUpdate();
+            } catch (SQLException e) {
+                throw new ELSQLException(e);
+            }
+        } catch (SQLException e) {
+            throw new ELSQLException(e);
+        } finally {
+            LiteFlowJdbcUtil.close(conn, stmt, resultSet);
+        }
+    }
+
+    /**
+     * 创建node_instance_id表,如果不存在
+     */
+    public void createNodeInstanceIdTable()  {
+        Connection conn = null;
+        Statement stmt = null;
+
+        try {
+            conn = LiteFlowJdbcUtil.getConn(sqlParserVO);
+
+            stmt = conn.createStatement();
+            stmt.executeUpdate(INSTANT_CREATE_TABLE_SQL);
+        } catch (SQLException e) {
+            throw new ELSQLException(e);
+        } finally {
+            LiteFlowJdbcUtil.close(conn, stmt);
+        }
+    }
+
 }

+ 24 - 0
liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/util/LiteFlowJdbcUtil.java

@@ -117,6 +117,30 @@ public class LiteFlowJdbcUtil {
         return StrUtil.format(CHECK_SQL_PATTERN, chainNameField, elDataField, chainTableName);
     }
 
+    /**
+     * 关闭
+     * @param conn
+     * @param stmt
+     */
+    public static void close(Connection conn, Statement stmt) {
+        // 关闭 statement
+        if (stmt != null) {
+            try {
+                stmt.close();
+            } catch (SQLException e) {
+                throw new ELSQLException(e);
+            }
+        }
+        // 关闭连接
+        if (conn != null) {
+            try {
+                conn.close();
+            } catch (SQLException e) {
+                throw new ELSQLException(e);
+            }
+        }
+    }
+
     public static class DataSourceBeanNameHolder {
         private static String DATA_SOURCE_NAME = null;
 

+ 64 - 0
liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/vo/SQLParserVO.java

@@ -57,6 +57,30 @@ public class SQLParserVO {
      */
     private String elDataField = "el_data";
 
+    /**
+     * instanceId 表名
+     */
+    private String instanceIdTableName = "node_instance_id_table";
+
+    /**
+     * instanceId 里的应用名字段
+     */
+    private String instanceIdApplicationNameField = "application_name";
+    /**
+     * instanceId 里的 chainId 字段
+     */
+    private String instanceChainIdField = "chain_id";
+
+    /**
+     * el md5
+     */
+    private String elDataMd5Field = "el_data_md5";
+
+   /**
+    * group_key_instance_id
+    */
+    private String nodeInstanceIdMapJsonField = "node_instance_id_map_json";
+
     /**
      * 决策路由字段
      */
@@ -387,6 +411,46 @@ public class SQLParserVO {
         this.scriptCustomSql = scriptCustomSql;
     }
 
+    public String getInstanceIdApplicationNameField() {
+        return instanceIdApplicationNameField;
+    }
+
+    public void setInstanceIdApplicationNameField(String instanceIdApplicationNameField) {
+        this.instanceIdApplicationNameField = instanceIdApplicationNameField;
+    }
+
+    public String getInstanceIdTableName() {
+        return instanceIdTableName;
+    }
+
+    public String getInstanceChainIdField() {
+        return instanceChainIdField;
+    }
+
+    public void setInstanceChainIdField(String instanceChainIdField) {
+        this.instanceChainIdField = instanceChainIdField;
+    }
+
+    public void setInstanceIdTableName(String instanceIdTableName) {
+        this.instanceIdTableName = instanceIdTableName;
+    }
+
+    public String getElDataMd5Field() {
+        return elDataMd5Field;
+    }
+
+    public void setElDataMd5Field(String elDataMd5Field) {
+        this.elDataMd5Field = elDataMd5Field;
+    }
+
+    public String getNodeInstanceIdMapJsonField() {
+        return nodeInstanceIdMapJsonField;
+    }
+
+    public void setNodeInstanceIdMapJsonField(String nodeInstanceIdMapJsonField) {
+        this.nodeInstanceIdMapJsonField = nodeInstanceIdMapJsonField;
+    }
+
     public DataSourceConfig getBaomidou() {
         return baomidou;
     }

+ 1 - 0
liteflow-rule-plugin/liteflow-rule-sql/src/main/resources/META-INF/services/com.yomahub.liteflow.flow.instanceId.NodeInstanceIdManageSpi

@@ -0,0 +1 @@
+com.yomahub.liteflow.parser.spi.instanceId.SqlNodeInstanceIdManageSpiImpl

+ 1 - 1
liteflow-solon-plugin/src/main/resources/META-INF/liteflow-default.properties

@@ -18,4 +18,4 @@ liteflow.fallback-cmp-enable=false
 liteflow.global-thread-pool-size=16
 liteflow.global-thread-pool-queue-size=512
 liteflow.global-thread-pool-executor-class=com.yomahub.liteflow.thread.LiteFlowDefaultGlobalExecutorBuilder
-liteflow.enable-node-instance-id=true
+liteflow.enable-node-instance-id=false

+ 334 - 53
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/instanceIds/InstanceIdELSpringTest.java

@@ -2,7 +2,13 @@ package com.yomahub.liteflow.test.instanceIds;
 
 import com.yomahub.liteflow.core.FlowExecutor;
 import com.yomahub.liteflow.flow.LiteflowResponse;
+import com.yomahub.liteflow.flow.element.Node;
+import com.yomahub.liteflow.flow.entity.InstanceInfoDto;
+import com.yomahub.liteflow.flow.instanceId.NodeInstanceIdManageSpi;
+import com.yomahub.liteflow.flow.instanceId.NodeInstanceIdManageSpiHolder;
 import com.yomahub.liteflow.test.BaseTest;
+import com.yomahub.liteflow.util.JsonUtil;
+import org.assertj.core.util.Sets;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -11,70 +17,345 @@ import org.springframework.context.annotation.ComponentScan;
 import org.springframework.test.context.TestPropertySource;
 
 import javax.annotation.Resource;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.*;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 /**
  * 测试生成 instanceId
+ *
  * @author Jay li
+ * @since 2.13.0
  */
 @TestPropertySource(value = "classpath:/instanceIds/application.properties")
 @SpringBootTest(classes = InstanceIdELSpringTest.class)
 @EnableAutoConfiguration
-@ComponentScan({ "com.yomahub.liteflow.test.instanceIds.cmp" })
+@ComponentScan({"com.yomahub.liteflow.test.instanceIds.cmp"})
 public class InstanceIdELSpringTest extends BaseTest {
 
-	@Resource
-	private FlowExecutor flowExecutor;
+    @Resource
+    private FlowExecutor flowExecutor;
 
     // 文件保存实例id
-	@Test
-	public void testInstanceIds1() {
-		LiteflowResponse response = flowExecutor.execute2Resp("chain2", "arg");
-		Assertions.assertTrue(response.isSuccess());
-		Assertions.assertEquals("a==>a==>a==>a", response.getExecuteStepStr());
-
-		String executeStepStrWithInstanceId = response.getExecuteStepStrWithInstanceId();
-		Set<String> strings = extractValues(executeStepStrWithInstanceId);
-		System.out.println(executeStepStrWithInstanceId);
-		Assertions.assertEquals(strings.size(), 4);
-	}
-
-	// 重复调用实例id不变
-	@Test
-	public void testInstanceIds2() {
-		LiteflowResponse response = flowExecutor.execute2Resp("chain2", "arg");
-		Assertions.assertTrue(response.isSuccess());
-		Assertions.assertEquals("a==>a==>a==>a", response.getExecuteStepStr());
-
-		String executeStepStrWithInstanceId = response.getExecuteStepStrWithInstanceId();
-		Set<String> set1 = extractValues(executeStepStrWithInstanceId);
-		System.out.println(executeStepStrWithInstanceId);
-
-		Assertions.assertEquals(set1.size(), 4);
-
-		response = flowExecutor.execute2Resp("chain2", "arg");
-		Assertions.assertTrue(response.isSuccess());
-		Assertions.assertEquals("a==>a==>a==>a", response.getExecuteStepStr());
-
-		executeStepStrWithInstanceId = response.getExecuteStepStrWithInstanceId();
-		Set<String> set2 = extractValues(executeStepStrWithInstanceId);
-		System.out.println(executeStepStrWithInstanceId);
-
-		Assertions.assertEquals(set2.size(), 4);
-		Assertions.assertEquals(set1, set2);
-	}
-
-	public static Set<String> extractValues(String input) {
-		Set<String> values = new HashSet<>();
-		Pattern pattern = Pattern.compile("\\[(.*?)]");
-		Matcher matcher = pattern.matcher(input);
-		while (matcher.find()) {
-			values.add(matcher.group(1));
-		}
-		return values;
-	}
-
-}
+    @Test
+    public void testInstanceIds1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("chain2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("a==>a==>a==>a", response.getExecuteStepStr());
+
+        String executeStepStrWithInstanceId = response.getExecuteStepStrWithInstanceId();
+        Set<String> strings = extractValues(executeStepStrWithInstanceId);
+        System.out.println(executeStepStrWithInstanceId);
+        Assertions.assertEquals(strings.size(), 4);
+    }
+
+    // 重复调用实例id不变
+    @Test
+    public void testInstanceIds2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("chain2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("a==>a==>a==>a", response.getExecuteStepStr());
+
+        String executeStepStrWithInstanceId = response.getExecuteStepStrWithInstanceId();
+        Set<String> set1 = extractValues(executeStepStrWithInstanceId);
+        System.out.println(executeStepStrWithInstanceId);
+
+        Assertions.assertEquals(set1.size(), 4);
+
+        response = flowExecutor.execute2Resp("chain2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("a==>a==>a==>a", response.getExecuteStepStr());
+
+        executeStepStrWithInstanceId = response.getExecuteStepStrWithInstanceId();
+        Set<String> set2 = extractValues(executeStepStrWithInstanceId);
+        System.out.println(executeStepStrWithInstanceId);
+
+        Assertions.assertEquals(set2.size(), 4);
+        Assertions.assertEquals(set1, set2);
+    }
+
+
+    @Test
+    public void testInstanceIds3() {
+        LiteflowResponse response = flowExecutor.execute2Resp("chain2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("a==>a==>a==>a", response.getExecuteStepStr());
+
+        String executeStepStrWithInstanceId = response.getExecuteStepStrWithInstanceId();
+        List<String> strings = extractValuesList(executeStepStrWithInstanceId);
+        NodeInstanceIdManageSpi nodeInstanceIdManageSpi = NodeInstanceIdManageSpiHolder.getInstance().getNodeInstanceIdManageSpi();
+
+        for (int i = 0; i < strings.size(); i++) {
+            Assertions.assertEquals(nodeInstanceIdManageSpi.getNodeLocationById("chain2", strings.get(i)), "a(" + i + ")");
+        }
+
+        System.out.println(executeStepStrWithInstanceId);
+        Assertions.assertEquals(strings.size(), 4);
+    }
+
+    public static Set<String> extractValues(String input) {
+        Set<String> values = new HashSet<>();
+        Pattern pattern = Pattern.compile("\\[(.*?)]");
+        Matcher matcher = pattern.matcher(input);
+        while (matcher.find()) {
+            values.add(matcher.group(1));
+        }
+        return values;
+    }
+
+    public static List<String> extractValuesList(String input) {
+        List<String> values = new ArrayList<>();
+        Pattern pattern = Pattern.compile("\\[(.*?)]");
+        Matcher matcher = pattern.matcher(input);
+        while (matcher.find()) {
+            values.add(matcher.group(1));
+        }
+        return values;
+    }
+
+
+    // chain3 if 脚本
+    @Test
+    public void testXmlChain3() {
+        String chain4InstanceStr = queryInstanceStrByChainId("chain3");
+        LiteflowResponse response = flowExecutor.execute2Resp("chain3", "arg");
+
+        Assertions.assertEquals("x1==>a==>b", response.getExecuteStepStr());
+        System.out.println(chain4InstanceStr);
+        Assertions.assertEquals(chain4InstanceStr, response.getExecuteStepStrWithInstanceId());
+        List<String> extractStrings = extractValuesList(chain4InstanceStr);
+        Assertions.assertEquals(Sets.newHashSet(extractStrings).size(), 3);
+    }
+
+
+    // chain5 switch 切换 for 表达式
+    @Test
+    public void testXmlChain5() {
+        String chainId = "chain5";
+
+        LiteflowResponse response = flowExecutor.execute2Resp(chainId, "arg");
+        String executeStepStr = response.getExecuteStepStr();
+        Assertions.assertEquals("e==>c", response.getExecuteStepStr());
+
+        String instancePath = constructInstancePath(executeStepStr, chainId);
+        Assertions.assertEquals(instancePath, response.getExecuteStepStrWithInstanceId());
+        List<String> extractStrings = extractValuesList(instancePath);
+        Assertions.assertEquals(Sets.newHashSet(extractStrings).size(), 2);
+    }
+
+    //  FOR(x).DO(CATCH(THEN(a,b,a)));
+    @Test
+    public void testXmlChain4() {
+        String chainId = "chain4";
+        LiteflowResponse response = flowExecutor.execute2Resp(chainId, "arg");
+        String chain4InstanceStr2 = queryInstanceStrByChainId(chainId);
+        String executeStepStr = response.getExecuteStepStr();
+        Assertions.assertEquals("x==>a==>b==>a", executeStepStr);
+
+        String instancePath = constructInstancePath(executeStepStr, chainId);
+        Assertions.assertEquals(instancePath, response.getExecuteStepStrWithInstanceId());
+
+        List<String> extractStrings = extractValuesList(chain4InstanceStr2);
+        Assertions.assertEquals(Sets.newHashSet(extractStrings).size(), 4);
+    }
+
+
+    // THEN(a,WHEN(b, c), a)
+    @Test
+    public void testXmlChain6() {
+        String chainId = "chain6";
+
+        LiteflowResponse response = flowExecutor.execute2Resp(chainId, "arg");
+        String executeStepStr = response.getExecuteStepStr();
+        Assertions.assertTrue( response.isSuccess());
+        String instancePath = constructInstancePath(executeStepStr, chainId);
+        Assertions.assertEquals(instancePath, response.getExecuteStepStrWithInstanceId());
+        List<String> extractStrings = extractValuesList(instancePath);
+        Assertions.assertEquals(Sets.newHashSet(extractStrings).size(), 4);
+    }
+
+
+    // CATCH(THEN(a,b)).DO(c)
+    @Test
+    public void testXmlChain7() {
+        String chainId = "chain7";
+
+        LiteflowResponse response = flowExecutor.execute2Resp(chainId, "arg");
+        String executeStepStr = response.getExecuteStepStr();
+        Assertions.assertEquals("a==>b", response.getExecuteStepStr());
+
+        String instancePath = constructInstancePath(executeStepStr, chainId);
+        Assertions.assertEquals(instancePath, response.getExecuteStepStrWithInstanceId());
+        List<String> extractStrings = extractValuesList(instancePath);
+        Assertions.assertEquals(Sets.newHashSet(extractStrings).size(), 2);
+    }
+
+    @Test
+    public void getNodeByIdAndInstanceIdTest() {
+        String[] chainIds = new String[]{"chain1", "chain2", "chain3", "chain4", "chain5", "chain6", "chain7"};
+        for (String chainId : chainIds) {
+            LiteflowResponse response = flowExecutor.execute2Resp(chainId, "arg");
+            String executeStepStrWithInstanceId = response.getExecuteStepStrWithInstanceId();
+
+            Map<String, String> instanceMap = extractKeyValuePairs(executeStepStrWithInstanceId);
+
+            NodeInstanceIdManageSpi nodeInstanceIdManageSpi = NodeInstanceIdManageSpiHolder.getInstance().getNodeInstanceIdManageSpi();
+            for (Map.Entry<String, String> entry : instanceMap.entrySet()) {
+                Node node = nodeInstanceIdManageSpi.getNodeByIdAndInstanceId(chainId, entry.getKey());
+                Assertions.assertEquals(node.getId(), entry.getValue());
+            }
+        }
+
+    }
+
+
+    @Test
+    public void getNodeByIdAndIndexTest() {
+        String[] chainIds = new String[]{"chain1", "chain2", "chain3", "chain4", "chain5", "chain6", "chain7"};
+        for (String chainId : chainIds) {
+            LiteflowResponse response = flowExecutor.execute2Resp(chainId, "arg");
+            String executeStepStrWithInstanceId = response.getExecuteStepStrWithInstanceId();
+
+            Map<String, String> instanceMap = extractKeyValuePairs(executeStepStrWithInstanceId);
+
+            Map<String, Integer> idCntMap = new HashMap<>();
+
+            NodeInstanceIdManageSpi nodeInstanceIdManageSpi = NodeInstanceIdManageSpiHolder.getInstance().getNodeInstanceIdManageSpi();
+            for (Map.Entry<String, String> entry : instanceMap.entrySet()) {
+                idCntMap.put(entry.getValue(), idCntMap.getOrDefault(entry.getValue(), -1) + 1);
+                Node node = nodeInstanceIdManageSpi.getNodeByIdAndIndex(chainId, entry.getValue(), idCntMap.get(entry.getValue()));
+                Assertions.assertEquals(node.getId(), entry.getValue());
+            }
+        }
+    }
+
+    @Test
+    public void getNodeInstanceIdsTest() {
+        String[] chainIds = new String[]{"chain1", "chain2", "chain3", "chain4", "chain5", "chain6", "chain7"};
+        for (String chainId : chainIds) {
+            LiteflowResponse response = flowExecutor.execute2Resp(chainId, "arg");
+            String executeStepStrWithInstanceId = response.getExecuteStepStrWithInstanceId();
+
+            Map<String, List<String>> instanceMap = extractKeyValues(executeStepStrWithInstanceId);
+
+            NodeInstanceIdManageSpi nodeInstanceIdManageSpi = NodeInstanceIdManageSpiHolder.getInstance().getNodeInstanceIdManageSpi();
+            for (Map.Entry<String, List<String>> entry : instanceMap.entrySet()) {
+                Assertions.assertEquals(entry.getValue(), nodeInstanceIdManageSpi.getNodeInstanceIds(chainId, entry.getKey()));
+            }
+        }
+
+    }
+
+
+    private String constructInstancePath(String executeStepStr, String chainId) {
+        Map<String, InstanceInfoDto> instanceMap = queryInstanceMapByChainId(chainId);
+        String[] nodes = executeStepStr.split("==>");
+
+        StringBuilder nodePathStr = new StringBuilder();
+        Map<String, Integer> tmpMap = new HashMap<>();
+        for (String node : nodes) {
+            tmpMap.put(node, tmpMap.getOrDefault(node, -1) + 1);
+            nodePathStr.append("==>").append(node).append("[")
+                    .append(instanceMap.get(node + "_" + tmpMap.get(node)).getInstanceId())
+                    .append("]");
+        }
+
+        return nodePathStr.toString().replaceFirst("==>", "");
+
+    }
+
+
+    private String queryInstanceStrByChainId(String chainId) {
+        String instanceId = queryInstanceIdInfo(chainId);
+        // 解析 JSON
+        List<InstanceInfoDto> instanceInfoDtos = JsonUtil.parseList(instanceId, InstanceInfoDto.class);
+        // 构造实例id字符串
+        StringBuilder result = new StringBuilder();
+        int i = 0;
+
+        for (InstanceInfoDto dto : instanceInfoDtos) {
+            result.append(dto.getNodeId()).append("[").append(dto.getInstanceId()).append("]");
+            if (i + 1 < instanceInfoDtos.size()) {
+                result.append("==>");
+            }
+            i++;
+        }
+
+        return result.toString();
+    }
+
+    // key 为 nodeId_index
+    private Map<String, InstanceInfoDto> queryInstanceMapByChainId(String chainId) {
+        // 查询数据库实例id
+        String instanceId = queryInstanceIdInfo(chainId);
+        // 解析 JSON
+        List<InstanceInfoDto> instanceInfos = JsonUtil.parseList(instanceId, InstanceInfoDto.class);
+        // 构造实例id字符串
+        Map<String, InstanceInfoDto> result = new HashMap<>();
+        instanceInfos.forEach(instanceInfo -> result.put(instanceInfo.getNodeId() + "_" + instanceInfo.getIndex(), instanceInfo));
+
+        return result;
+
+    }
+
+
+    /**
+     * key 为 InstanceId  value 为 nodeId
+     */
+    private Map<String, String> extractKeyValuePairs(String input) {
+        String[] parts = input.split("==>");
+
+        // 创建一个 Map 来存储结果
+        Map<String, String> resultMap = new HashMap<>();
+
+        for (String part : parts) {
+            // 去掉前后括号
+            int startIndex = part.indexOf('[');
+            int endIndex = part.lastIndexOf(']');
+
+            if (startIndex != -1 && endIndex != -1) {
+                String value = part.substring(0, startIndex).trim();
+                String key = part.substring(startIndex + 1, endIndex).trim();
+
+                // 将键值对放入 Map
+                resultMap.put(key, value);
+            }
+        }
+        return resultMap;
+    }
+
+
+    /**
+     * key 为 nodeId value 为 list InstanceId
+     */
+    private Map<String, List<String>> extractKeyValues(String input) {
+        String[] parts = input.split("==>");
+
+        // 创建一个 Map 来存储结果
+        Map<String, List<String>> resultMap = new HashMap<>();
+
+        for (String part : parts) {
+            // 去掉前后括号
+            int startIndex = part.indexOf('[');
+            int endIndex = part.lastIndexOf(']');
+
+            if (startIndex != -1 && endIndex != -1) {
+                String key = part.substring(0, startIndex).trim();
+                String value = part.substring(startIndex + 1, endIndex).trim();
+
+                List<String> mapOrDefault = resultMap.getOrDefault(key, new ArrayList<>());
+                mapOrDefault.add(value);
+                resultMap.put(key, mapOrDefault);
+            }
+        }
+        return resultMap;
+    }
+
+
+    public String queryInstanceIdInfo(String chainId) {
+        NodeInstanceIdManageSpi nodeInstanceIdManageSpi = NodeInstanceIdManageSpiHolder.getInstance().getNodeInstanceIdManageSpi();
+        List<String> readInstanceIdFiles = nodeInstanceIdManageSpi.readInstanceIdFile(chainId);
+
+        return readInstanceIdFiles.get(1);
+    }
+}

+ 21 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/instanceIds/cmp/ESwitchCmp.java

@@ -0,0 +1,21 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.instanceIds.cmp;
+
+import com.yomahub.liteflow.core.NodeSwitchComponent;
+import org.springframework.stereotype.Component;
+
+@Component("e")
+public class ESwitchCmp extends NodeSwitchComponent {
+
+	@Override
+	public String processSwitch() throws Exception {
+		return "c";
+	}
+
+}

+ 21 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/instanceIds/cmp/X1Cmp.java

@@ -0,0 +1,21 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.instanceIds.cmp;
+
+import com.yomahub.liteflow.core.NodeBooleanComponent;
+import org.springframework.stereotype.Component;
+
+@Component("x1")
+public class X1Cmp extends NodeBooleanComponent {
+
+	@Override
+	public boolean processBoolean() throws Exception {
+		return Boolean.parseBoolean(this.getTag());
+	}
+
+}

+ 26 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/instanceIds/cmp/X2Cmp.java

@@ -0,0 +1,26 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.instanceIds.cmp;
+
+import com.yomahub.liteflow.core.NodeBooleanComponent;
+import org.springframework.stereotype.Component;
+
+@Component("x2")
+public class X2Cmp extends NodeBooleanComponent {
+
+	@Override
+	public boolean processBoolean() throws Exception {
+		return true;
+	}
+
+	@Override
+	public boolean isAccess() {
+		return false;
+	}
+
+}

+ 17 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/instanceIds/cmp/XCmp.java

@@ -0,0 +1,17 @@
+package com.yomahub.liteflow.test.instanceIds.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+
+@LiteflowComponent("x")
+public class XCmp {
+
+	@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_FOR, nodeType = NodeTypeEnum.FOR)
+	public int processFor(NodeComponent bindCmp) throws Exception {
+		return 1;
+	}
+
+}

+ 20 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/instanceIds/flow.el.xml

@@ -7,4 +7,24 @@
     <chain name="chain2">
         THEN(a,a,a,a);
     </chain>
+
+    <chain name="chain3">
+        IF(x1.tag("true"), THEN(a, b));
+    </chain>
+
+    <chain name="chain4">
+        FOR(x).DO(CATCH(THEN(a,b,a)));
+    </chain>
+
+    <chain name="chain5">
+        THEN(SWITCH(e).to(b, c));
+    </chain>
+
+    <chain name="chain6">
+        THEN(a,WHEN(b, c),a);
+    </chain>
+
+    <chain name="chain7">
+        CATCH(THEN(a,b)).DO(c)
+    </chain>
 </flow>

+ 21 - 0
liteflow-testcase-el/liteflow-testcase-el-sql-springboot/src/test/java/com/yomahub/liteflow/test/sql/cmp/ESwitchCmp.java

@@ -0,0 +1,21 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.sql.cmp;
+
+import com.yomahub.liteflow.core.NodeSwitchComponent;
+import org.springframework.stereotype.Component;
+
+@Component("e")
+public class ESwitchCmp extends NodeSwitchComponent {
+
+	@Override
+	public String processSwitch() throws Exception {
+		return "c";
+	}
+
+}

+ 17 - 0
liteflow-testcase-el/liteflow-testcase-el-sql-springboot/src/test/java/com/yomahub/liteflow/test/sql/cmp/XCmp.java

@@ -0,0 +1,17 @@
+package com.yomahub.liteflow.test.sql.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+
+@LiteflowComponent("x")
+public class XCmp {
+
+	@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_FOR, nodeType = NodeTypeEnum.FOR)
+	public int processFor(NodeComponent bindCmp) throws Exception {
+		return 1;
+	}
+
+}

+ 401 - 0
liteflow-testcase-el/liteflow-testcase-el-sql-springboot/src/test/java/com/yomahub/liteflow/test/sqlInstanceId/SQLWithXmlELInstanceIdSpringbootTest.java

@@ -0,0 +1,401 @@
+package com.yomahub.liteflow.test.sqlInstanceId;
+
+import com.yomahub.liteflow.core.FlowExecutor;
+import com.yomahub.liteflow.flow.LiteflowResponse;
+import com.yomahub.liteflow.flow.element.Node;
+import com.yomahub.liteflow.flow.entity.InstanceInfoDto;
+import com.yomahub.liteflow.flow.instanceId.NodeInstanceIdManageSpi;
+import com.yomahub.liteflow.flow.instanceId.NodeInstanceIdManageSpiHolder;
+import com.yomahub.liteflow.parser.sql.exception.ELSQLException;
+import com.yomahub.liteflow.parser.sql.vo.SQLParserVO;
+import com.yomahub.liteflow.property.LiteflowConfig;
+import com.yomahub.liteflow.property.LiteflowConfigGetter;
+import com.yomahub.liteflow.test.BaseTest;
+import com.yomahub.liteflow.util.JsonUtil;
+import org.assertj.core.util.Sets;
+import org.json.JSONException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import javax.annotation.Resource;
+import java.sql.*;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * @author jay li
+ * @since 2.13.0
+ */
+@ExtendWith(SpringExtension.class)
+@TestPropertySource(value = "classpath:/application-instanceId-xml.properties")
+@SpringBootTest(classes = SQLWithXmlELInstanceIdSpringbootTest.class)
+@EnableAutoConfiguration
+@ComponentScan({"com.yomahub.liteflow.test.sql.cmp"})
+public class SQLWithXmlELInstanceIdSpringbootTest extends BaseTest {
+
+    @Resource
+    private FlowExecutor flowExecutor;
+
+    @Test
+    public void testSQLWithXmlChain() throws SQLException, JSONException {
+        // 查询数据库实例id
+        String result = queryInstanceStrByChainId("r_chain4");
+        LiteflowResponse response = flowExecutor.execute2Resp("r_chain4", "arg");
+        Assertions.assertEquals("c==>b==>a", response.getExecuteStepStr());
+        Assertions.assertEquals(result, response.getExecuteStepStrWithInstanceId());
+        // 重复执行 检查实例id是否变化
+        response = flowExecutor.execute2Resp("r_chain4", "arg");
+        Assertions.assertEquals(result, response.getExecuteStepStrWithInstanceId());
+    }
+
+
+    // 测试sql实例id 构建 坐标返回
+    @Test
+    public void testSQLWithXmlChain2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("r_chain4", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("c==>b==>a", response.getExecuteStepStr());
+
+        String executeStepStrWithInstanceId = response.getExecuteStepStrWithInstanceId();
+        List<String> strings = extractValuesList(executeStepStrWithInstanceId);
+        NodeInstanceIdManageSpi nodeInstanceIdManageSpi = NodeInstanceIdManageSpiHolder.getInstance().getNodeInstanceIdManageSpi();
+
+        String[] nodes = new String[]{"c", "b", "a"};
+        for (int i = 0; i < strings.size(); i++) {
+            Assertions.assertEquals(nodeInstanceIdManageSpi.getNodeLocationById("r_chain4", strings.get(i)), nodes[i] + "(0)");
+        }
+
+        HashSet<String> hashSet = Sets.newHashSet(strings);
+        Assertions.assertEquals(hashSet.size(), 3);
+    }
+
+    // 测试chain 表达式更改后,实例id是否变化
+    @Test
+    public void testSQLWithXmlChain3() throws SQLException, JSONException {
+        String chain4InstanceStr = queryInstanceStrByChainId("r_chain4");
+        LiteflowResponse response = flowExecutor.execute2Resp("r_chain4", "arg");
+        Assertions.assertEquals("c==>b==>a", response.getExecuteStepStr());
+        Assertions.assertEquals(chain4InstanceStr, response.getExecuteStepStrWithInstanceId());
+
+        // 更该数据 查实例id是否变化
+        changeData("THEN(a, c, b);", "r_chain4");
+        flowExecutor.reloadRule();
+
+        // 重复查询
+        response = flowExecutor.execute2Resp("r_chain4", "arg");
+        String chain4InstanceStr2 = queryInstanceStrByChainId("r_chain4");
+        Assertions.assertNotEquals(chain4InstanceStr2, chain4InstanceStr);
+        Assertions.assertEquals("a==>c==>b", flowExecutor.execute2Resp("r_chain4", "arg").getExecuteStepStr());
+        Assertions.assertEquals(chain4InstanceStr2, response.getExecuteStepStrWithInstanceId());
+    }
+
+    // chain3 if 脚本 切换 if表达试
+    @Test
+    public void testSQLWithXmlChain4() throws SQLException {
+        String chain4InstanceStr = queryInstanceStrByChainId("chain3");
+        LiteflowResponse response = flowExecutor.execute2Resp("chain3", "arg");
+
+        Assertions.assertEquals("x0[if 脚本]==>a==>b", response.getExecuteStepStr());
+        System.out.println(chain4InstanceStr);
+        Assertions.assertEquals(chain4InstanceStr, response.getExecuteStepStrWithInstanceId());
+        List<String> extractStrings = extractValuesList(chain4InstanceStr);
+        Assertions.assertEquals(Sets.newHashSet(extractStrings).size(), 3);
+
+        // 更该数据 查实例id是否变化
+        changeData("IF(x2, IF(x0, THEN(a, b)));", "chain3");
+        flowExecutor.reloadRule();
+
+        // 重复查询
+        response = flowExecutor.execute2Resp("chain3", "arg");
+        String chain4InstanceStr2 = queryInstanceStrByChainId("chain3");
+
+        Assertions.assertNotEquals(chain4InstanceStr2, chain4InstanceStr);
+        Assertions.assertEquals("x2[python脚本]==>x0[if 脚本]==>a==>b", response.getExecuteStepStr());
+        Assertions.assertEquals(chain4InstanceStr2, response.getExecuteStepStrWithInstanceId());
+
+        extractStrings = extractValuesList(chain4InstanceStr2);
+        Assertions.assertEquals(Sets.newHashSet(extractStrings).size(), 4);
+    }
+
+
+    // chain5 switch 切换 for 表达式
+    @Test
+    public void testSQLWithXmlChain5() throws SQLException {
+        String chainId = "chain5";
+
+        LiteflowResponse response = flowExecutor.execute2Resp(chainId, "arg");
+        String executeStepStr = response.getExecuteStepStr();
+        Assertions.assertEquals("e==>c", response.getExecuteStepStr());
+
+        String instancePath = constructInstancePath(executeStepStr, chainId);
+        Assertions.assertEquals(instancePath, response.getExecuteStepStrWithInstanceId());
+        List<String> extractStrings = extractValuesList(instancePath);
+        Assertions.assertEquals(Sets.newHashSet(extractStrings).size(), 2);
+
+        // 更该数据 查实例id是否变化
+        changeData("FOR(x).DO(CATCH(THEN(a,b,a)))", chainId);
+        flowExecutor.reloadRule();
+
+        // 重复查询
+        response = flowExecutor.execute2Resp(chainId, "arg");
+        String chain4InstanceStr2 = queryInstanceStrByChainId(chainId);
+        executeStepStr = response.getExecuteStepStr();
+        Assertions.assertEquals("x==>a==>b==>a", executeStepStr);
+
+        instancePath = constructInstancePath(executeStepStr, chainId);
+        Assertions.assertEquals(instancePath, response.getExecuteStepStrWithInstanceId());
+
+        extractStrings = extractValuesList(chain4InstanceStr2);
+        Assertions.assertEquals(Sets.newHashSet(extractStrings).size(), 4);
+    }
+
+
+    // THEN(a,WHEN(b, c), a)
+    @Test
+    public void testSQLWithXmlChain6() throws SQLException {
+        String chainId = "chain6";
+
+        LiteflowResponse response = flowExecutor.execute2Resp(chainId, "arg");
+        String executeStepStr = response.getExecuteStepStr();
+        Assertions.assertTrue(response.isSuccess());
+
+        String instancePath = constructInstancePath(executeStepStr, chainId);
+        Assertions.assertEquals(instancePath, response.getExecuteStepStrWithInstanceId());
+        List<String> extractStrings = extractValuesList(instancePath);
+        Assertions.assertEquals(Sets.newHashSet(extractStrings).size(), 4);
+    }
+
+
+    // CATCH(THEN(a,b)).DO(c)
+    @Test
+    public void testSQLWithXmlChain7() throws SQLException {
+        String chainId = "chain7";
+
+        LiteflowResponse response = flowExecutor.execute2Resp(chainId, "arg");
+        String executeStepStr = response.getExecuteStepStr();
+        Assertions.assertEquals("a==>b", response.getExecuteStepStr());
+
+        String instancePath = constructInstancePath(executeStepStr, chainId);
+        Assertions.assertEquals(instancePath, response.getExecuteStepStrWithInstanceId());
+        List<String> extractStrings = extractValuesList(instancePath);
+        Assertions.assertEquals(Sets.newHashSet(extractStrings).size(), 2);
+    }
+
+    @Test
+    public void getNodeByIdAndInstanceIdTest() throws SQLException {
+        String[] chainIds = new String[]{"chain1", "chain2", "chain4","r_chain1","r_chain2","chain5"};
+        for (String chainId : chainIds) {
+            LiteflowResponse response = flowExecutor.execute2Resp(chainId, "arg");
+            String executeStepStrWithInstanceId = response.getExecuteStepStrWithInstanceId();
+
+            Map<String, String> instanceMap = extractKeyValuePairs(executeStepStrWithInstanceId);
+
+            NodeInstanceIdManageSpi nodeInstanceIdManageSpi = NodeInstanceIdManageSpiHolder.getInstance().getNodeInstanceIdManageSpi();
+            for (Map.Entry<String, String> entry : instanceMap.entrySet()) {
+                Node node = nodeInstanceIdManageSpi.getNodeByIdAndInstanceId(chainId, entry.getKey());
+                Assertions.assertEquals(node.getId(), entry.getValue());
+            }
+        }
+
+    }
+
+
+    @Test
+    public void getNodeByIdAndIndexTest() throws SQLException {
+        String[] chainIds = new String[]{"chain1", "chain2", "chain4","r_chain1","r_chain2","chain5"};
+        for (String chainId : chainIds) {
+            LiteflowResponse response = flowExecutor.execute2Resp(chainId, "arg");
+            String executeStepStrWithInstanceId = response.getExecuteStepStrWithInstanceId();
+
+            Map<String, String> instanceMap = extractKeyValuePairs(executeStepStrWithInstanceId);
+
+            Map<String, Integer> idCntMap = new HashMap<>();
+
+            NodeInstanceIdManageSpi nodeInstanceIdManageSpi = NodeInstanceIdManageSpiHolder.getInstance().getNodeInstanceIdManageSpi();
+            for (Map.Entry<String, String> entry : instanceMap.entrySet()) {
+                idCntMap.put(entry.getValue(), idCntMap.getOrDefault(entry.getValue(), -1) + 1);
+                Node node = nodeInstanceIdManageSpi.getNodeByIdAndIndex(chainId, entry.getValue(), idCntMap.get(entry.getValue()));
+                Assertions.assertEquals(node.getId(), entry.getValue());
+            }
+        }
+    }
+
+    @Test
+    public void getNodeInstanceIdsTest() throws SQLException {
+        String[] chainIds = new String[]{"chain1", "chain2", "chain4","r_chain1","r_chain2","chain5"};
+        for (String chainId : chainIds) {
+            LiteflowResponse response = flowExecutor.execute2Resp(chainId, "arg");
+            String executeStepStrWithInstanceId = response.getExecuteStepStrWithInstanceId();
+
+            Map<String, List<String>> instanceMap = extractKeyValues(executeStepStrWithInstanceId);
+
+            NodeInstanceIdManageSpi nodeInstanceIdManageSpi = NodeInstanceIdManageSpiHolder.getInstance().getNodeInstanceIdManageSpi();
+            for (Map.Entry<String, List<String>> entry : instanceMap.entrySet()) {
+                Assertions.assertEquals(entry.getValue(), nodeInstanceIdManageSpi.getNodeInstanceIds(chainId, entry.getKey()));
+            }
+        }
+
+    }
+
+
+
+    private String constructInstancePath(String executeStepStr, String chainId) throws SQLException {
+        Map<String, InstanceInfoDto> instanceMap = queryInstanceMapByChainId(chainId);
+        String[] nodes = executeStepStr.split("==>");
+
+        StringBuilder nodePathStr = new StringBuilder();
+        Map<String, Integer> tmpMap = new HashMap<>();
+        for (String node : nodes) {
+            tmpMap.put(node, tmpMap.getOrDefault(node, -1) + 1);
+            nodePathStr.append("==>").append(node).append("[")
+                    .append(instanceMap.get(node + "_" + tmpMap.get(node)).getInstanceId())
+                    .append("]");
+        }
+
+        return nodePathStr.toString().replaceFirst("==>", "");
+
+    }
+
+
+    // 修改数据库数据
+    private void changeData(String chainElData, String chainId) {
+        LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
+        SQLParserVO sqlParserVO = JsonUtil.parseObject(liteflowConfig.getRuleSourceExtData(), SQLParserVO.class);
+        Connection connection;
+        try {
+            connection = DriverManager.getConnection(sqlParserVO.getUrl(), sqlParserVO.getUsername(),
+                    sqlParserVO.getPassword());
+            Statement statement = connection.createStatement();
+            statement.executeUpdate("UPDATE EL_TABLE SET EL_DATA='" + chainElData + "' WHERE chain_name='" + chainId + "'");
+        } catch (SQLException e) {
+            throw new ELSQLException(e.getMessage());
+        }
+    }
+
+    private String queryInstanceStrByChainId(String chainId) throws SQLException {
+        // 查询数据库实例id
+        String instanceId = queryInstanceIdInfo(chainId);
+        // 解析 JSON
+        List<InstanceInfoDto> instanceInfoDtos = JsonUtil.parseList(instanceId, InstanceInfoDto.class);
+        // 构造实例id字符串
+        StringBuilder result = new StringBuilder();
+        int i = 0;
+
+        for (InstanceInfoDto dto : instanceInfoDtos) {
+            result.append(dto.getNodeId()).append("[").append(dto.getInstanceId()).append("]");
+            if (i + 1 < instanceInfoDtos.size()) {
+                result.append("==>");
+            }
+            i++;
+        }
+
+        return result.toString();
+    }
+
+    // key 为 nodeId_index
+    private Map<String, InstanceInfoDto> queryInstanceMapByChainId(String chainId) throws SQLException {
+        // 查询数据库实例id
+        String instanceId = queryInstanceIdInfo(chainId);
+        // 解析 JSON
+        List<InstanceInfoDto> instanceInfos = JsonUtil.parseList(instanceId, InstanceInfoDto.class);
+        // 构造实例id字符串
+        Map<String, InstanceInfoDto> result = new HashMap<>();
+        instanceInfos.forEach(instanceInfo -> result.put(instanceInfo.getNodeId() + "_" + instanceInfo.getIndex(), instanceInfo));
+
+        return result;
+
+    }
+
+    public static List<String> extractValuesList(String input) {
+        List<String> values = new ArrayList<>();
+        Pattern pattern = Pattern.compile("\\[(.*?)]");
+        Matcher matcher = pattern.matcher(input);
+        while (matcher.find()) {
+            values.add(matcher.group(1));
+        }
+        return values;
+    }
+
+    /**
+     * key 为 InstanceId  value 为 nodeId
+     */
+    private Map<String, String> extractKeyValuePairs(String input) {
+        String[] parts = input.split("==>");
+
+        // 创建一个 Map 来存储结果
+        Map<String, String> resultMap = new HashMap<>();
+
+        for (String part : parts) {
+            // 去掉前后括号
+            int startIndex = part.indexOf('[');
+            int endIndex = part.lastIndexOf(']');
+
+            if (startIndex != -1 && endIndex != -1) {
+                String value = part.substring(0, startIndex).trim();
+                String key = part.substring(startIndex + 1, endIndex).trim();
+
+                // 将键值对放入 Map
+                resultMap.put(key, value);
+            }
+        }
+        return resultMap;
+    }
+
+
+
+    /**
+     * key 为 nodeId value 为 list InstanceId
+     */
+    private Map<String, List<String>> extractKeyValues(String input) {
+        String[] parts = input.split("==>");
+
+        // 创建一个 Map 来存储结果
+        Map<String, List<String>> resultMap = new HashMap<>();
+
+        for (String part : parts) {
+            // 去掉前后括号
+            int startIndex = part.indexOf('[');
+            int endIndex = part.lastIndexOf(']');
+
+            if (startIndex != -1 && endIndex != -1) {
+                String key = part.substring(0, startIndex).trim();
+                String value = part.substring(startIndex + 1, endIndex).trim();
+
+                List<String> mapOrDefault = resultMap.getOrDefault(key, new ArrayList<>());
+                mapOrDefault.add(value);
+                resultMap.put(key, mapOrDefault);
+            }
+        }
+        return resultMap;
+    }
+
+
+    public String queryInstanceIdInfo(String chainId) throws SQLException {
+        LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
+        SQLParserVO sqlParserVO = JsonUtil.parseObject(liteflowConfig.getRuleSourceExtData(), SQLParserVO.class);
+        Connection connection;
+        try {
+            connection = DriverManager.getConnection(sqlParserVO.getUrl(), sqlParserVO.getUsername(),
+                    sqlParserVO.getPassword());
+            Statement statement = connection.createStatement();
+            ResultSet rs = statement.executeQuery("select * from NODE_INSTANCE_ID_TABLE where APPLICATION_NAME = 'demo' " +
+                    "and chain_id = '" + chainId + "' ");
+
+            String res = "";
+            while (rs.next()) {
+                res = rs.getString("node_instance_id_map_json");
+            }
+            return res;
+        } catch (SQLException e) {
+            throw new ELSQLException(e.getMessage());
+        }
+    }
+}

+ 21 - 0
liteflow-testcase-el/liteflow-testcase-el-sql-springboot/src/test/java/com/yomahub/liteflow/test/sqlroute/cmp/ESwitchCmp.java

@@ -0,0 +1,21 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.sqlroute.cmp;
+
+import com.yomahub.liteflow.core.NodeSwitchComponent;
+import org.springframework.stereotype.Component;
+
+@Component("e")
+public class ESwitchCmp extends NodeSwitchComponent {
+
+	@Override
+	public String processSwitch() throws Exception {
+		return "c";
+	}
+
+}

+ 26 - 0
liteflow-testcase-el/liteflow-testcase-el-sql-springboot/src/test/resources/application-instanceId-xml.properties

@@ -0,0 +1,26 @@
+liteflow.rule-source-ext-data={\
+  "url":"jdbc:h2:mem:test_db;MODE=MySQL",\
+  "driverClassName":"org.h2.Driver",\
+  "username":"root",\
+  "password":"123456",\
+  "applicationName":"demo",\
+  "chainTableName":"EL_TABLE",\
+  "chainApplicationNameField":"application_name",\
+  "chainNameField":"chain_name",\
+  "elDataField":"EL_DATA",\
+  "scriptTableName":"script_node_table",\
+  "scriptApplicationNameField":"application_name",\
+  "scriptIdField":"script_node_id",\
+  "scriptNameField":"script_node_name",\
+  "scriptDataField":"script_node_data",\
+  "scriptLanguageField":"script_language",\
+  "scriptTypeField":"script_node_type"\
+  }
+liteflow.enable-node-instance-id=true
+spring.datasource.driver-class-name=org.h2.Driver
+spring.datasource.url=jdbc:h2:mem:test_db;MODE=MySQL
+spring.datasource.username=root
+spring.datasource.password=123456
+spring.datasource.schema=classpath:/sql/schema.sql
+spring.datasource.data=classpath:/sql/data.sql
+spring.datasource.platform=h2

+ 3 - 0
liteflow-testcase-el/liteflow-testcase-el-sql-springboot/src/test/resources/sql/data.sql

@@ -9,6 +9,9 @@ INSERT INTO EL_TABLE (APPLICATION_NAME,CHAIN_NAME,EL_DATA,ROUTE,NAMESPACE) value
 INSERT INTO EL_TABLE (APPLICATION_NAME,CHAIN_NAME,EL_DATA,ROUTE,NAMESPACE) values ('demo','r_chain2','THEN(c,b,a);','OR(r1,r2)','ns');
 INSERT INTO EL_TABLE (APPLICATION_NAME,CHAIN_NAME,EL_DATA,CUSTOM_FILTER_TYPE) values ('demo','r_chain3','THEN(a,b,c);','biz1');
 INSERT INTO EL_TABLE (APPLICATION_NAME,CHAIN_NAME,EL_DATA,CUSTOM_FILTER_TYPE) values ('demo','r_chain4','THEN(c,b,a);','biz2');
+INSERT INTO EL_TABLE (APPLICATION_NAME,CHAIN_NAME,EL_DATA) values ('demo','chain5','THEN(SWITCH(e).to(b, c));');
+INSERT INTO EL_TABLE (APPLICATION_NAME,CHAIN_NAME,EL_DATA) values ('demo','chain6','THEN(a,WHEN(b, c),a)');
+INSERT INTO EL_TABLE (APPLICATION_NAME,CHAIN_NAME,EL_DATA) values ('demo','chain7','CATCH(THEN(a,b)).DO(c)');
 
 DELETE FROM SCRIPT_NODE_TABLE;
 

+ 1 - 1
liteflow-testcase-el/liteflow-testcase-el-sql-springboot/src/test/resources/sql/schema.sql

@@ -20,4 +20,4 @@ create table IF NOT EXISTS `script_node_table`
     `script_node_data`     varchar(1024) NOT NULL,
     `script_language`     varchar(1024) NOT NULL,
     PRIMARY KEY (`id`)
-);
+);