diff --git a/liteflow-rule-plugin/liteflow-rule-etcd/pom.xml b/liteflow-rule-plugin/liteflow-rule-etcd/pom.xml
new file mode 100644
index 000000000..ebee4d69e
--- /dev/null
+++ b/liteflow-rule-plugin/liteflow-rule-etcd/pom.xml
@@ -0,0 +1,27 @@
+
+
+
+ liteflow-rule-plugin
+ com.yomahub
+ ${revision}
+ ../pom.xml
+
+ 4.0.0
+
+ liteflow-rule-etcd
+
+
+
+ com.yomahub
+ liteflow-core
+ ${revision}
+ true
+
+
+ io.etcd
+ jetcd-core
+
+
+
\ No newline at end of file
diff --git a/liteflow-rule-plugin/liteflow-rule-etcd/src/main/java/com/yomahub/liteflow/parser/etcd/EtcdClient.java b/liteflow-rule-plugin/liteflow-rule-etcd/src/main/java/com/yomahub/liteflow/parser/etcd/EtcdClient.java
new file mode 100644
index 000000000..8983ce9f9
--- /dev/null
+++ b/liteflow-rule-plugin/liteflow-rule-etcd/src/main/java/com/yomahub/liteflow/parser/etcd/EtcdClient.java
@@ -0,0 +1,127 @@
+package com.yomahub.liteflow.parser.etcd;
+
+import cn.hutool.core.collection.CollUtil;
+import io.etcd.jetcd.ByteSequence;
+import io.etcd.jetcd.Client;
+import io.etcd.jetcd.KeyValue;
+import io.etcd.jetcd.Watch;
+import io.etcd.jetcd.watch.WatchEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+/**
+ * Etcd 客户端封装类.
+ * @author zendwang
+ * @since 2.9.0
+ */
+public class EtcdClient {
+
+ private static final Logger LOG = LoggerFactory.getLogger(EtcdClient.class);
+
+ private Client client;
+
+ private final ConcurrentHashMap watchCache = new ConcurrentHashMap<>();
+
+ public EtcdClient(final Client client) {
+ this.client = client;
+ }
+
+ /**
+ * close client.
+ */
+ public void close() {
+ this.client.close();
+ }
+
+ /**
+ * get node value.
+ *
+ * @param key node name
+ * @return string
+ */
+ public String get(final String key) {
+ List keyValues = null;
+ try {
+ keyValues = client.getKVClient().get(ByteSequence.from(key, StandardCharsets.UTF_8)).get().getKvs();
+ } catch (InterruptedException | ExecutionException e) {
+ LOG.error(e.getMessage(), e);
+ }
+
+ if (CollUtil.isEmpty(keyValues)) {
+ return null;
+ }
+
+ return keyValues.iterator().next().getValue().toString(StandardCharsets.UTF_8);
+ }
+
+ /**
+ * put a key-value pair into etcd.
+ * @param key node name
+ * @param value node value
+ * @return
+ */
+ public KeyValue put(final String key, final String value) {
+ KeyValue prevKv = null;
+ ByteSequence keyByteSequence = ByteSequence.from(key, StandardCharsets.UTF_8);
+ ByteSequence valueByteSequence = ByteSequence.from(value, StandardCharsets.UTF_8);
+ try {
+ prevKv = client.getKVClient().put(keyByteSequence, valueByteSequence).get().getPrevKv();
+ } catch (InterruptedException | ExecutionException e) {
+ LOG.error(e.getMessage(), e);
+ }
+ return prevKv;
+ }
+
+ /**
+ * subscribe data change.
+ *
+ * @param key node name
+ * @param updateHandler node value handler of update
+ * @param deleteHandler node value handler of delete
+ */
+ public void watchDataChange(final String key,
+ final BiConsumer updateHandler,
+ final Consumer deleteHandler) {
+ Watch.Listener listener = watch(updateHandler, deleteHandler);
+ Watch.Watcher watch = client.getWatchClient().watch(ByteSequence.from(key, StandardCharsets.UTF_8), listener);
+ watchCache.put(key, watch);
+ }
+
+ private Watch.Listener watch(final BiConsumer updateHandler,
+ final Consumer deleteHandler) {
+ return Watch.listener(response -> {
+ for (WatchEvent event : response.getEvents()) {
+ String path = event.getKeyValue().getKey().toString(StandardCharsets.UTF_8);
+ String value = event.getKeyValue().getValue().toString(StandardCharsets.UTF_8);
+ switch (event.getEventType()) {
+ case PUT:
+ updateHandler.accept(path, value);
+ continue;
+ case DELETE:
+ deleteHandler.accept(path);
+ continue;
+ default:
+ }
+ }
+ });
+ }
+
+ /**
+ * cancel subscribe.
+ *
+ * @param key node name
+ */
+ public void watchClose(final String key) {
+ if (watchCache.containsKey(key)) {
+ watchCache.get(key).close();
+ watchCache.remove(key);
+ }
+ }
+}
\ No newline at end of file
diff --git a/liteflow-rule-plugin/liteflow-rule-etcd/src/main/java/com/yomahub/liteflow/parser/etcd/EtcdXmlELParser.java b/liteflow-rule-plugin/liteflow-rule-etcd/src/main/java/com/yomahub/liteflow/parser/etcd/EtcdXmlELParser.java
new file mode 100644
index 000000000..99252655d
--- /dev/null
+++ b/liteflow-rule-plugin/liteflow-rule-etcd/src/main/java/com/yomahub/liteflow/parser/etcd/EtcdXmlELParser.java
@@ -0,0 +1,65 @@
+package com.yomahub.liteflow.parser.etcd;
+
+import cn.hutool.core.util.StrUtil;
+import com.yomahub.liteflow.parser.el.ClassXmlFlowELParser;
+import com.yomahub.liteflow.parser.etcd.exception.EtcdException;
+import com.yomahub.liteflow.parser.etcd.util.EtcdParserHelper;
+import com.yomahub.liteflow.parser.etcd.vo.EtcdParserVO;
+import com.yomahub.liteflow.property.LiteflowConfig;
+import com.yomahub.liteflow.property.LiteflowConfigGetter;
+import com.yomahub.liteflow.util.JsonUtil;
+
+import java.util.function.Consumer;
+
+/**
+ * Etcd解析器实现,只支持EL形式的XML,不支持其他的形式
+ * @author zendwang
+ * @since 2.9.0
+ */
+public class EtcdXmlELParser extends ClassXmlFlowELParser {
+
+ private final EtcdParserHelper etcdParserHelper;
+
+ public EtcdXmlELParser() {
+ LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
+
+ if (StrUtil.isBlank(liteflowConfig.getRuleSourceExtData())){
+ throw new EtcdException("rule-source-ext-data is empty");
+ }
+
+ try{
+ EtcdParserVO etcdParserVO = JsonUtil.parseObject(liteflowConfig.getRuleSourceExtData(), EtcdParserVO.class);
+ assert etcdParserVO != null;
+
+ if (StrUtil.isBlank(etcdParserVO.getNodePath())){
+ etcdParserVO.setNodePath("/lite-flow/flow");
+ }
+ if (StrUtil.isBlank(etcdParserVO.getConnectStr())){
+ throw new EtcdException("Etcd connect string is empty");
+ }
+
+ etcdParserHelper = new EtcdParserHelper(etcdParserVO);
+ }catch (Exception e){
+ throw new EtcdException(e.getMessage());
+ }
+ }
+
+ @Override
+ public String parseCustom() {
+ Consumer parseConsumer = t -> {
+ try {
+ parse(t);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ };
+ try {
+ String content = etcdParserHelper.getContent();
+ etcdParserHelper.checkContent(content);
+ etcdParserHelper.listen(parseConsumer);
+ return content;
+ } catch (Exception e){
+ throw new EtcdException(e.getMessage());
+ }
+ }
+}
diff --git a/liteflow-rule-plugin/liteflow-rule-etcd/src/main/java/com/yomahub/liteflow/parser/etcd/exception/EtcdException.java b/liteflow-rule-plugin/liteflow-rule-etcd/src/main/java/com/yomahub/liteflow/parser/etcd/exception/EtcdException.java
new file mode 100644
index 000000000..fb2f1d408
--- /dev/null
+++ b/liteflow-rule-plugin/liteflow-rule-etcd/src/main/java/com/yomahub/liteflow/parser/etcd/exception/EtcdException.java
@@ -0,0 +1,28 @@
+
+package com.yomahub.liteflow.parser.etcd.exception;
+
+/**
+ * Etcd解析异常
+ * @author zendwang
+ * @since 2.9.0
+ */
+public class EtcdException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ /** 异常信息 */
+ private String message;
+
+ public EtcdException(String message) {
+ this.message = message;
+ }
+
+ @Override
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+}
diff --git a/liteflow-rule-plugin/liteflow-rule-etcd/src/main/java/com/yomahub/liteflow/parser/etcd/util/EtcdParserHelper.java b/liteflow-rule-plugin/liteflow-rule-etcd/src/main/java/com/yomahub/liteflow/parser/etcd/util/EtcdParserHelper.java
new file mode 100644
index 000000000..6c45f36a2
--- /dev/null
+++ b/liteflow-rule-plugin/liteflow-rule-etcd/src/main/java/com/yomahub/liteflow/parser/etcd/util/EtcdParserHelper.java
@@ -0,0 +1,72 @@
+package com.yomahub.liteflow.parser.etcd.util;
+
+import cn.hutool.core.util.StrUtil;
+import com.yomahub.liteflow.exception.ParseException;
+import com.yomahub.liteflow.parser.el.XmlFlowELParser;
+import com.yomahub.liteflow.parser.etcd.EtcdClient;
+import com.yomahub.liteflow.parser.etcd.exception.EtcdException;
+import com.yomahub.liteflow.parser.etcd.vo.EtcdParserVO;
+import com.yomahub.liteflow.spi.holder.ContextAwareHolder;
+import io.etcd.jetcd.Client;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.MessageFormat;
+import java.util.function.Consumer;
+
+/**
+ * @author zendwang
+ * @since 2.9.0
+ */
+public class EtcdParserHelper {
+
+ private static final Logger LOG = LoggerFactory.getLogger(EtcdParserHelper.class);
+
+ private final EtcdParserVO etcdParserVO;
+
+ private EtcdClient etcdClient;
+
+ public EtcdParserHelper(EtcdParserVO etcdParserVO) {
+ this.etcdParserVO = etcdParserVO;
+
+ try{
+ this.etcdClient = ContextAwareHolder.loadContextAware().getBean(EtcdClient.class);
+ if (this.etcdClient == null) {
+ Client client = Client.builder()
+ .endpoints(etcdParserVO.getConnectStr().split(","))
+ .build();
+ this.etcdClient = new EtcdClient(client);
+ }
+ }catch (Exception e){
+ throw new EtcdException(e.getMessage());
+ }
+ }
+
+ public String getContent(){
+ try{
+ return this.etcdClient.get(etcdParserVO.getNodePath());
+ }catch (Exception e){
+ throw new EtcdException(e.getMessage());
+ }
+ }
+
+ /**
+ * 检查 content 是否合法
+ */
+ public void checkContent(String content) {
+ if (StrUtil.isBlank(content)) {
+ String error = MessageFormat.format("the node[{0}] value is empty", etcdParserVO.getNodePath());
+ throw new ParseException(error);
+ }
+ }
+
+ /**
+ * 监听 etcd 节点
+ */
+ public void listen(Consumer parseConsumer) {
+ this.etcdClient.watchDataChange(this.etcdParserVO.getNodePath(), (updatePath, updateValue) -> {
+ LOG.info("starting load flow config....");
+ parseConsumer.accept(updateValue);
+ }, null);
+ }
+}
diff --git a/liteflow-rule-plugin/liteflow-rule-etcd/src/main/java/com/yomahub/liteflow/parser/etcd/vo/EtcdParserVO.java b/liteflow-rule-plugin/liteflow-rule-etcd/src/main/java/com/yomahub/liteflow/parser/etcd/vo/EtcdParserVO.java
new file mode 100644
index 000000000..c7e5b291a
--- /dev/null
+++ b/liteflow-rule-plugin/liteflow-rule-etcd/src/main/java/com/yomahub/liteflow/parser/etcd/vo/EtcdParserVO.java
@@ -0,0 +1,29 @@
+package com.yomahub.liteflow.parser.etcd.vo;
+
+/**
+ * 用于解析RuleSourceExtData的vo类,用于etcd模式中
+ * @author zendwang
+ * @since 2.9.0
+ */
+public class EtcdParserVO {
+
+ private String connectStr;
+
+ private String nodePath;
+
+ public String getConnectStr() {
+ return connectStr;
+ }
+
+ public void setConnectStr(String connectStr) {
+ this.connectStr = connectStr;
+ }
+
+ public String getNodePath() {
+ return nodePath;
+ }
+
+ public void setNodePath(String nodePath) {
+ this.nodePath = nodePath;
+ }
+}
diff --git a/liteflow-rule-plugin/liteflow-rule-etcd/src/main/java/com/yomahub/liteflow/parser/spi/etcd/EtcdParserClassNameSpi.java b/liteflow-rule-plugin/liteflow-rule-etcd/src/main/java/com/yomahub/liteflow/parser/spi/etcd/EtcdParserClassNameSpi.java
new file mode 100644
index 000000000..72c6b7f63
--- /dev/null
+++ b/liteflow-rule-plugin/liteflow-rule-etcd/src/main/java/com/yomahub/liteflow/parser/spi/etcd/EtcdParserClassNameSpi.java
@@ -0,0 +1,18 @@
+package com.yomahub.liteflow.parser.spi.etcd;
+
+
+import com.yomahub.liteflow.parser.etcd.EtcdXmlELParser;
+import com.yomahub.liteflow.parser.spi.ParserClassNameSpi;
+
+
+/**
+ * Etcd解析器SPI实现
+ * @author zendwang
+ * @since 2.9.0
+ */
+public class EtcdParserClassNameSpi implements ParserClassNameSpi {
+ @Override
+ public String getSpiClassName() {
+ return EtcdXmlELParser.class.getName();
+ }
+}
diff --git a/liteflow-rule-plugin/liteflow-rule-etcd/src/main/resources/META-INF/services/com.yomahub.liteflow.parser.spi.ParserClassNameSpi b/liteflow-rule-plugin/liteflow-rule-etcd/src/main/resources/META-INF/services/com.yomahub.liteflow.parser.spi.ParserClassNameSpi
new file mode 100644
index 000000000..f418cfdf7
--- /dev/null
+++ b/liteflow-rule-plugin/liteflow-rule-etcd/src/main/resources/META-INF/services/com.yomahub.liteflow.parser.spi.ParserClassNameSpi
@@ -0,0 +1 @@
+com.yomahub.liteflow.parser.spi.etcd.EtcdParserClassNameSpi
\ No newline at end of file
diff --git a/liteflow-rule-plugin/pom.xml b/liteflow-rule-plugin/pom.xml
index 9599dbec3..bc05dc7b3 100644
--- a/liteflow-rule-plugin/pom.xml
+++ b/liteflow-rule-plugin/pom.xml
@@ -13,6 +13,7 @@
liteflow-rule-zk
liteflow-rule-sql
+ liteflow-rule-etcd
liteflow-rule-plugin
diff --git a/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/pom.xml b/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/pom.xml
new file mode 100644
index 000000000..5fdf91f6b
--- /dev/null
+++ b/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/pom.xml
@@ -0,0 +1,57 @@
+
+
+
+ liteflow-testcase-el
+ com.yomahub
+ ${revision}
+ ../pom.xml
+
+ 4.0.0
+
+ liteflow-testcase-el-etcd-springboot
+
+
+
+ com.yomahub
+ liteflow-spring-boot-starter
+ ${revision}
+
+
+
+ com.yomahub
+ liteflow-rule-etcd
+ ${revision}
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+
+
+ org.aspectj
+ aspectjweaver
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${springboot.version}
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+ 2.8.2
+
+ true
+
+
+
+
+
\ No newline at end of file
diff --git a/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/java/com/yomahub/liteflow/test/BaseTest.java b/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/java/com/yomahub/liteflow/test/BaseTest.java
new file mode 100644
index 000000000..64886670f
--- /dev/null
+++ b/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/java/com/yomahub/liteflow/test/BaseTest.java
@@ -0,0 +1,20 @@
+package com.yomahub.liteflow.test;
+
+import com.yomahub.liteflow.flow.FlowBus;
+import com.yomahub.liteflow.property.LiteflowConfigGetter;
+import com.yomahub.liteflow.spi.holder.SpiFactoryCleaner;
+import com.yomahub.liteflow.spring.ComponentScanner;
+import com.yomahub.liteflow.thread.ExecutorHelper;
+import org.junit.AfterClass;
+
+public class BaseTest {
+
+ @AfterClass
+ public static void cleanScanCache(){
+ ComponentScanner.cleanCache();
+ FlowBus.cleanCache();
+ ExecutorHelper.loadInstance().clearExecutorServiceMap();
+ SpiFactoryCleaner.clean();
+ LiteflowConfigGetter.clean();
+ }
+}
diff --git a/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/java/com/yomahub/liteflow/test/etcd/EtcdWithXmlELSpringbootTest.java b/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/java/com/yomahub/liteflow/test/etcd/EtcdWithXmlELSpringbootTest.java
new file mode 100644
index 000000000..f1756512a
--- /dev/null
+++ b/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/java/com/yomahub/liteflow/test/etcd/EtcdWithXmlELSpringbootTest.java
@@ -0,0 +1,66 @@
+package com.yomahub.liteflow.test.etcd;
+
+import cn.hutool.core.util.ReflectUtil;
+import com.yomahub.liteflow.core.FlowExecutor;
+import com.yomahub.liteflow.flow.LiteflowResponse;
+import com.yomahub.liteflow.parser.etcd.EtcdClient;
+import com.yomahub.liteflow.parser.etcd.EtcdXmlELParser;
+import com.yomahub.liteflow.parser.etcd.util.EtcdParserHelper;
+import com.yomahub.liteflow.spi.holder.ContextAwareHolder;
+import com.yomahub.liteflow.test.BaseTest;
+import org.junit.*;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.MockitoAnnotations;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import javax.annotation.Resource;
+import java.util.function.Consumer;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+/**
+ * springboot环境下的etcd 规则解析器 测试
+ */
+@RunWith(SpringRunner.class)
+@TestPropertySource(value = "classpath:/etcd/application-xml-cluster.properties")
+@SpringBootTest(classes = EtcdWithXmlELSpringbootTest.class)
+@EnableAutoConfiguration
+@ComponentScan({"com.yomahub.liteflow.test.etcd.cmp"})
+public class EtcdWithXmlELSpringbootTest extends BaseTest {
+
+ @MockBean(answer= Answers.RETURNS_MOCKS)
+ private EtcdClient etcdClient;
+
+ @Resource
+ private FlowExecutor flowExecutor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ String flowXml = "THEN(a, b, c);";
+ String changedFlowXml = "THEN(a, c);";
+ when(etcdClient.get(any())).thenReturn(flowXml).thenReturn(changedFlowXml);
+ }
+
+ @Test
+ public void testEtcdNodeWithXml() throws Exception {
+ LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg");
+ Assert.assertTrue(response.isSuccess());
+ Assert.assertTrue("a==>b==>c".equals(response.getExecuteStepStr()));
+
+ // 手动触发一次 模拟节点数据变更
+ EtcdXmlELParser parser = ContextAwareHolder.loadContextAware().getBean(EtcdXmlELParser.class);
+ parser.parse(etcdClient.get("/lite-flow/flow"));
+
+ LiteflowResponse response2 = flowExecutor.execute2Resp("chain1", "arg");
+ Assert.assertTrue(response2.isSuccess());
+ Assert.assertTrue("a==>c".equals(response2.getExecuteStepStr()));
+ }
+}
diff --git a/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/java/com/yomahub/liteflow/test/etcd/cmp/ACmp.java b/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/java/com/yomahub/liteflow/test/etcd/cmp/ACmp.java
new file mode 100644
index 000000000..6d1ae42c8
--- /dev/null
+++ b/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/java/com/yomahub/liteflow/test/etcd/cmp/ACmp.java
@@ -0,0 +1,20 @@
+/**
+ * Title: liteflow
+ * Description: 轻量级的组件式流程框架
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.etcd.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+@Component("a")
+public class ACmp extends NodeComponent {
+
+ @Override
+ public void process() {
+ System.out.println("ACmp executed!");
+ }
+}
diff --git a/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/java/com/yomahub/liteflow/test/etcd/cmp/BCmp.java b/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/java/com/yomahub/liteflow/test/etcd/cmp/BCmp.java
new file mode 100644
index 000000000..19ea3b7cd
--- /dev/null
+++ b/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/java/com/yomahub/liteflow/test/etcd/cmp/BCmp.java
@@ -0,0 +1,21 @@
+/**
+ * Title: liteflow
+ * Description: 轻量级的组件式流程框架
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.etcd.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+@Component("b")
+public class BCmp extends NodeComponent {
+
+ @Override
+ public void process() {
+ System.out.println("BCmp executed!");
+ }
+
+}
diff --git a/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/java/com/yomahub/liteflow/test/etcd/cmp/CCmp.java b/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/java/com/yomahub/liteflow/test/etcd/cmp/CCmp.java
new file mode 100644
index 000000000..6ddf383b2
--- /dev/null
+++ b/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/java/com/yomahub/liteflow/test/etcd/cmp/CCmp.java
@@ -0,0 +1,21 @@
+/**
+ * Title: liteflow
+ * Description: 轻量级的组件式流程框架
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.etcd.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+@Component("c")
+public class CCmp extends NodeComponent {
+
+ @Override
+ public void process() {
+ System.out.println("CCmp executed!");
+ }
+
+}
diff --git a/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/resources/etcd/application-xml-cluster.properties b/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/resources/etcd/application-xml-cluster.properties
new file mode 100644
index 000000000..211b3b4ec
--- /dev/null
+++ b/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/resources/etcd/application-xml-cluster.properties
@@ -0,0 +1,2 @@
+liteflow.rule-source-ext-data={"connectStr":"http://localhost:2379,http://localhost:3379,http://localhost:4379"}
+liteflow.parse-on-start=false
diff --git a/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/resources/etcd/application-xml.properties b/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/resources/etcd/application-xml.properties
new file mode 100644
index 000000000..3938e12e9
--- /dev/null
+++ b/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/resources/etcd/application-xml.properties
@@ -0,0 +1 @@
+liteflow.rule-source-ext-data={"connectStr":"http://localhost:2379"}
\ No newline at end of file
diff --git a/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/resources/etcd/flow.xml b/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/resources/etcd/flow.xml
new file mode 100644
index 000000000..049210cf4
--- /dev/null
+++ b/liteflow-testcase-el/liteflow-testcase-el-etcd-springboot/src/test/resources/etcd/flow.xml
@@ -0,0 +1,6 @@
+
+
+
+ THEN(a, b, c);
+
+
\ No newline at end of file
diff --git a/liteflow-testcase-el/pom.xml b/liteflow-testcase-el/pom.xml
index c31339763..550b63df3 100644
--- a/liteflow-testcase-el/pom.xml
+++ b/liteflow-testcase-el/pom.xml
@@ -24,5 +24,6 @@
liteflow-testcase-el-script-qlexpress-springboot
liteflow-testcase-el-zk-springboot
liteflow-testcase-el-sql-springboot
+ liteflow-testcase-el-etcd-springboot
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index f9186b488..8d44a7293 100644
--- a/pom.xml
+++ b/pom.xml
@@ -56,6 +56,7 @@
2.12.3
5.1.0
0.10
+ 0.5.0
3.3.0
3.0.8
1.11.13
@@ -167,7 +168,11 @@
zkclient
${zkclient.version}
-
+
+ io.etcd
+ jetcd-core
+ ${jetcd.version}
+
com.alibaba
QLExpress
@@ -324,7 +329,7 @@
liteflow-spring
liteflow-testcase-old
liteflow-testcase-el
-
+