diff --git a/liteflow-rule-plugin/liteflow-rule-apollo/pom.xml b/liteflow-rule-plugin/liteflow-rule-apollo/pom.xml new file mode 100644 index 000000000..23c2643b8 --- /dev/null +++ b/liteflow-rule-plugin/liteflow-rule-apollo/pom.xml @@ -0,0 +1,41 @@ + + + + liteflow-rule-plugin + com.yomahub + ${revision} + ../pom.xml + + 4.0.0 + + liteflow-rule-apollo + + + 8 + 8 + + + + + com.yomahub + liteflow-core + ${revision} + true + provided + + + + org.apache.httpcomponents + httpclient + ${httpclient.version} + + + com.ctrip.framework.apollo + apollo-client + + + + + \ No newline at end of file diff --git a/liteflow-rule-plugin/liteflow-rule-apollo/src/main/java/com/yomahub/liteflow/parser/apollo/ApolloXmlELParser.java b/liteflow-rule-plugin/liteflow-rule-apollo/src/main/java/com/yomahub/liteflow/parser/apollo/ApolloXmlELParser.java new file mode 100644 index 000000000..f47ec4848 --- /dev/null +++ b/liteflow-rule-plugin/liteflow-rule-apollo/src/main/java/com/yomahub/liteflow/parser/apollo/ApolloXmlELParser.java @@ -0,0 +1,68 @@ +package com.yomahub.liteflow.parser.apollo; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import com.yomahub.liteflow.core.FlowInitHook; +import com.yomahub.liteflow.parser.apollo.exception.ApolloException; +import com.yomahub.liteflow.parser.apollo.util.ApolloParseHelper; +import com.yomahub.liteflow.parser.apollo.vo.ApolloParserConfigVO; +import com.yomahub.liteflow.parser.el.ClassXmlFlowELParser; +import com.yomahub.liteflow.property.LiteflowConfig; +import com.yomahub.liteflow.property.LiteflowConfigGetter; +import com.yomahub.liteflow.util.JsonUtil; + +import java.util.Objects; + +/** + * @Description: + * @Author: zhanghua + * @Date: 2022/12/3 13:38 + */ +public class ApolloXmlELParser extends ClassXmlFlowELParser { + + private final ApolloParseHelper apolloParseHelper; + + public ApolloXmlELParser() { + LiteflowConfig liteflowConfig = LiteflowConfigGetter.get(); + try { + ApolloParserConfigVO apolloParserConfigVO = null; + if (MapUtil.isNotEmpty((liteflowConfig.getRuleSourceExtDataMap()))) { + apolloParserConfigVO = BeanUtil.toBean(liteflowConfig.getRuleSourceExtDataMap(), ApolloParserConfigVO.class, CopyOptions.create()); + } else if (StrUtil.isNotBlank(liteflowConfig.getRuleSourceExtData())) { + apolloParserConfigVO = JsonUtil.parseObject(liteflowConfig.getRuleSourceExtData(), ApolloParserConfigVO.class); + } + + // check config + if (Objects.isNull(apolloParserConfigVO)) { + throw new ApolloException("ruleSourceExtData or map is empty"); + } + + if (StrUtil.isBlank(apolloParserConfigVO.getChainNamespace())) { + throw new ApolloException("chainNamespace is empty, you must configure the chainNamespace property"); + } + + apolloParseHelper = new ApolloParseHelper(apolloParserConfigVO); + } catch (Exception e) { + throw new ApolloException(e.getMessage()); + } + } + + + @Override + public String parseCustom() { + + try { + String content = apolloParseHelper.getContent(); + FlowInitHook.addHook(() -> { + apolloParseHelper.listenApollo(); + return true; + }); + return content; + + } catch (Exception e) { + throw new ApolloException(e.getMessage()); + } + } +} diff --git a/liteflow-rule-plugin/liteflow-rule-apollo/src/main/java/com/yomahub/liteflow/parser/apollo/exception/ApolloException.java b/liteflow-rule-plugin/liteflow-rule-apollo/src/main/java/com/yomahub/liteflow/parser/apollo/exception/ApolloException.java new file mode 100644 index 000000000..f8aa53ec9 --- /dev/null +++ b/liteflow-rule-plugin/liteflow-rule-apollo/src/main/java/com/yomahub/liteflow/parser/apollo/exception/ApolloException.java @@ -0,0 +1,21 @@ +package com.yomahub.liteflow.parser.apollo.exception; + +/** + * @Description: + * @Author: zhanghua + * @Date: 2022/12/3 13:45 + */ +public class ApolloException extends RuntimeException { + + private String message; + + public ApolloException(String message) { + super(); + this.message = message; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/liteflow-rule-plugin/liteflow-rule-apollo/src/main/java/com/yomahub/liteflow/parser/apollo/util/ApolloParseHelper.java b/liteflow-rule-plugin/liteflow-rule-apollo/src/main/java/com/yomahub/liteflow/parser/apollo/util/ApolloParseHelper.java new file mode 100644 index 000000000..11e2557c8 --- /dev/null +++ b/liteflow-rule-plugin/liteflow-rule-apollo/src/main/java/com/yomahub/liteflow/parser/apollo/util/ApolloParseHelper.java @@ -0,0 +1,230 @@ +package com.yomahub.liteflow.parser.apollo.util; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ReUtil; +import cn.hutool.core.util.StrUtil; +import com.ctrip.framework.apollo.Config; +import com.ctrip.framework.apollo.ConfigService; +import com.ctrip.framework.apollo.enums.PropertyChangeType; +import com.ctrip.framework.apollo.model.ConfigChange; +import com.yomahub.liteflow.builder.LiteFlowNodeBuilder; +import com.yomahub.liteflow.builder.el.LiteFlowChainELBuilder; +import com.yomahub.liteflow.enums.NodeTypeEnum; +import com.yomahub.liteflow.flow.FlowBus; +import com.yomahub.liteflow.parser.apollo.exception.ApolloException; +import com.yomahub.liteflow.parser.apollo.vo.ApolloParserConfigVO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @Description: + * @Author: zhanghua + * @Date: 2022/12/3 13:47 + */ +public class ApolloParseHelper { + + + private static final Logger LOG = LoggerFactory.getLogger(ApolloParseHelper.class); + + private final String CHAIN_XML_PATTERN = "{}"; + + private final String NODE_XML_PATTERN = "{}"; + + private final String NODE_ITEM_XML_PATTERN = ""; + + private final String XML_PATTERN = "{}{}"; + + private final ApolloParserConfigVO apolloParserConfigVO; + + private Config chainConfig; + + private Config scriptConfig; + + + public ApolloParseHelper(ApolloParserConfigVO apolloParserConfigVO) { + this.apolloParserConfigVO = apolloParserConfigVO; + try { + chainConfig = ConfigService.getConfig(apolloParserConfigVO.getChainNamespace()); + String scriptNamespace; + // scriptConfig is optional + if (StrUtil.isNotBlank(scriptNamespace = apolloParserConfigVO.getScriptNamespace())) { + scriptConfig = ConfigService.getConfig(scriptNamespace); + } + } catch (Exception e) { + throw new ApolloException(e.getMessage()); + } + } + + public String getContent() { + + try { + // 1. handle chain + Set propertyNames = chainConfig.getPropertyNames(); + if (CollectionUtil.isEmpty(propertyNames)) { + throw new ApolloException(StrUtil.format("There are no chains in namespace : {}", apolloParserConfigVO.getChainNamespace())); + } + List chainItemContentList = propertyNames.stream() + .map(item -> StrUtil.format(CHAIN_XML_PATTERN, item, chainConfig.getProperty(item, StrUtil.EMPTY))) + .collect(Collectors.toList()); + // merge all chain content + String chainAllContent = CollUtil.join(chainItemContentList, StrUtil.EMPTY); + + // 2. handle script if needed + String scriptAllContent = StrUtil.EMPTY; + Set scriptNamespaces; + if (Objects.nonNull(scriptConfig) && CollectionUtil.isNotEmpty(scriptNamespaces = scriptConfig.getPropertyNames())) { + + List scriptItemContentList = scriptNamespaces.stream() + .map(item -> convert(item, scriptConfig.getProperty(item, StrUtil.EMPTY))) + .filter(Objects::nonNull) + .map(item -> StrUtil.format(NODE_ITEM_XML_PATTERN, item.getNodeId(), item.getName(), item.getType(), item.getScript())) + .collect(Collectors.toList()); + + scriptAllContent = StrUtil.format(NODE_XML_PATTERN, CollUtil.join(scriptItemContentList, StrUtil.EMPTY)); + } + + return StrUtil.format(XML_PATTERN, scriptAllContent, chainAllContent); + } catch (Exception e) { + throw new ApolloException(e.getMessage()); + } + } + + + /** + * listen apollo config change + */ + public void listenApollo() { + + // chain + chainConfig.addChangeListener(changeEvent -> + changeEvent.changedKeys().forEach(changeKey -> { + ConfigChange configChange = changeEvent.getChange(changeKey); + String newValue = configChange.getNewValue(); + PropertyChangeType changeType = configChange.getChangeType(); + switch (changeType) { + case ADDED: + case MODIFIED: + LOG.info("starting reload flow config... {} key={} value={},", changeType.name(), changeKey, newValue); + LiteFlowChainELBuilder.createChain() + .setChainId(changeKey) + .setEL(newValue) + .build(); + break; + case DELETED: + LOG.info("starting reload flow config... delete key={}", changeKey); + FlowBus.removeChain(changeKey); + + } + })); + + // script + if (Objects.isNull(scriptConfig)) { + // no script config + return; + } + scriptConfig.addChangeListener(changeEvent -> + changeEvent.changedKeys().forEach(changeKey -> { + ConfigChange configChange = changeEvent.getChange(changeKey); + String newValue = configChange.getNewValue(); + + PropertyChangeType changeType = configChange.getChangeType(); + + NodeSimpleVO nodeSimpleVO; + switch (changeType) { + case ADDED: + case MODIFIED: + LOG.info("starting reload flow config... {} key={} value={},", changeType.name(), changeKey, newValue); + nodeSimpleVO = convert(changeKey, newValue); + LiteFlowNodeBuilder.createScriptNode() + .setId(nodeSimpleVO.getNodeId()) + .setType(NodeTypeEnum.getEnumByCode(nodeSimpleVO.getType())) + .setName(nodeSimpleVO.getName()) + .setScript(nodeSimpleVO.getScript()) + .build(); + break; + case DELETED: + LOG.info("starting reload flow config... delete key={}", changeKey); + nodeSimpleVO = convert(changeKey, null); + FlowBus.getNodeMap().remove(nodeSimpleVO.getNodeId()); + } + })); + + } + + + private NodeSimpleVO convert(String key, String value) { + //不需要去理解这串正则,就是一个匹配冒号的 + //一定得是a:b,或是a:b:c...这种完整类型的字符串的 + List matchItemList = ReUtil.findAllGroup0("(?<=[^:]:)[^:]+|[^:]+(?=:[^:])", key); + if (CollUtil.isEmpty(matchItemList)) { + return null; + } + + NodeSimpleVO nodeSimpleVO = new NodeSimpleVO(); + if (matchItemList.size() > 1) { + nodeSimpleVO.setNodeId(matchItemList.get(0)); + nodeSimpleVO.setType(matchItemList.get(1)); + } + + if (matchItemList.size() > 2) { + nodeSimpleVO.setName(matchItemList.get(2)); + } + + // set script + nodeSimpleVO.setScript(value); + + return nodeSimpleVO; + } + + + private static class NodeSimpleVO { + + private String nodeId; + + private String type; + + private String name = StrUtil.EMPTY; + + private String script; + + public String getNodeId() { + return nodeId; + } + + public void setNodeId(String nodeId) { + this.nodeId = nodeId; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getScript() { + return script; + } + + public void setScript(String script) { + this.script = script; + } + } + + +} diff --git a/liteflow-rule-plugin/liteflow-rule-apollo/src/main/java/com/yomahub/liteflow/parser/apollo/vo/ApolloParserConfigVO.java b/liteflow-rule-plugin/liteflow-rule-apollo/src/main/java/com/yomahub/liteflow/parser/apollo/vo/ApolloParserConfigVO.java new file mode 100644 index 000000000..311fd3bd7 --- /dev/null +++ b/liteflow-rule-plugin/liteflow-rule-apollo/src/main/java/com/yomahub/liteflow/parser/apollo/vo/ApolloParserConfigVO.java @@ -0,0 +1,46 @@ +package com.yomahub.liteflow.parser.apollo.vo; + +/** + * @Description: + * @Author: zhanghua + * @Date: 2022/12/3 13:45 + */ +public class ApolloParserConfigVO { + + private String chainNamespace; + + private String scriptNamespace; + + + public ApolloParserConfigVO() { + } + + public ApolloParserConfigVO(String chainNamespace, String scriptNamespace) { + this.chainNamespace = chainNamespace; + this.scriptNamespace = scriptNamespace; + } + + public String getChainNamespace() { + return chainNamespace; + } + + public void setChainNamespace(String chainNamespace) { + this.chainNamespace = chainNamespace; + } + + public String getScriptNamespace() { + return scriptNamespace; + } + + public void setScriptNamespace(String scriptNamespace) { + this.scriptNamespace = scriptNamespace; + } + + @Override + public String toString() { + return "ApolloParserConfigVO{" + + "chainNamespace='" + chainNamespace + '\'' + + ", scriptNamespace='" + scriptNamespace + '\'' + + '}'; + } +} diff --git a/liteflow-rule-plugin/liteflow-rule-apollo/src/main/java/com/yomahub/liteflow/parser/spi/apollo/ApolloParserClassNameSpi.java b/liteflow-rule-plugin/liteflow-rule-apollo/src/main/java/com/yomahub/liteflow/parser/spi/apollo/ApolloParserClassNameSpi.java new file mode 100644 index 000000000..d83a7eca1 --- /dev/null +++ b/liteflow-rule-plugin/liteflow-rule-apollo/src/main/java/com/yomahub/liteflow/parser/spi/apollo/ApolloParserClassNameSpi.java @@ -0,0 +1,17 @@ +package com.yomahub.liteflow.parser.spi.apollo; + +import com.yomahub.liteflow.parser.apollo.ApolloXmlELParser; +import com.yomahub.liteflow.parser.spi.ParserClassNameSpi; + +/** + * @Description: + * @Author: zhanghua + * @Date: 2022/12/3 13:40 + */ +public class ApolloParserClassNameSpi implements ParserClassNameSpi { + + @Override + public String getSpiClassName() { + return ApolloXmlELParser.class.getName(); + } +} diff --git a/liteflow-rule-plugin/liteflow-rule-apollo/src/main/resources/META-INF/services/com.yomahub.liteflow.parser.spi.ParserClassNameSpi b/liteflow-rule-plugin/liteflow-rule-apollo/src/main/resources/META-INF/services/com.yomahub.liteflow.parser.spi.ParserClassNameSpi new file mode 100644 index 000000000..b5c5af6bd --- /dev/null +++ b/liteflow-rule-plugin/liteflow-rule-apollo/src/main/resources/META-INF/services/com.yomahub.liteflow.parser.spi.ParserClassNameSpi @@ -0,0 +1 @@ +com.yomahub.liteflow.parser.spi.apollo.ApolloParserClassNameSpi \ No newline at end of file diff --git a/liteflow-testcase-el/liteflow-testcase-el-apollo-springboot/pom.xml b/liteflow-testcase-el/liteflow-testcase-el-apollo-springboot/pom.xml new file mode 100644 index 000000000..d47c6904e --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-apollo-springboot/pom.xml @@ -0,0 +1,51 @@ + + + + liteflow-testcase-el + com.yomahub + ${revision} + ../pom.xml + + 4.0.0 + + liteflow-testcase-el-apollo-springboot + + + 8 + 8 + + + + + com.yomahub + liteflow-spring-boot-starter + ${revision} + + + + org.springframework.boot + spring-boot-starter-test + + + + com.yomahub + liteflow-rule-apollo + ${revision} + test + + + + cn.hutool + hutool-core + ${hutool-core.version} + + + + com.yomahub + liteflow-script-groovy + ${revision} + + + \ No newline at end of file diff --git a/liteflow-testcase-el/liteflow-testcase-el-apollo-springboot/src/test/java/com/yomahub/liteflow/test/BaseTest.java b/liteflow-testcase-el/liteflow-testcase-el-apollo-springboot/src/test/java/com/yomahub/liteflow/test/BaseTest.java new file mode 100644 index 000000000..785bc391b --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-apollo-springboot/src/test/java/com/yomahub/liteflow/test/BaseTest.java @@ -0,0 +1,28 @@ +package com.yomahub.liteflow.test; + +import com.yomahub.liteflow.core.FlowInitHook; +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; + +/** + * @Description: + * @Author: zhanghua + * @Date: 2022/12/3 15:22 + */ +public class BaseTest { + + @AfterClass + public static void cleanScanCache() { + ComponentScanner.cleanCache(); + FlowBus.cleanCache(); + ExecutorHelper.loadInstance().clearExecutorServiceMap(); + SpiFactoryCleaner.clean(); + LiteflowConfigGetter.clean(); + FlowInitHook.cleanHook(); + } + +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-apollo-springboot/src/test/java/com/yomahub/liteflow/test/apollo/ApolloWithXmlELSpringbootTest.java b/liteflow-testcase-el/liteflow-testcase-el-apollo-springboot/src/test/java/com/yomahub/liteflow/test/apollo/ApolloWithXmlELSpringbootTest.java new file mode 100644 index 000000000..fa9e229c8 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-apollo-springboot/src/test/java/com/yomahub/liteflow/test/apollo/ApolloWithXmlELSpringbootTest.java @@ -0,0 +1,56 @@ +package com.yomahub.liteflow.test.apollo; + +import com.yomahub.liteflow.core.FlowExecutor; +import com.yomahub.liteflow.flow.FlowBus; +import com.yomahub.liteflow.flow.LiteflowResponse; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +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.junit4.SpringRunner; + +import javax.annotation.Resource; + +/** + * @Description: + * @Author: zhanghua + * @Date: 2022/12/3 15:22 + */ +@RunWith(SpringRunner.class) +@TestPropertySource(value = "classpath:/apollo/application-xml.properties") +@SpringBootTest(classes = ApolloWithXmlELSpringbootTest.class) +@EnableAutoConfiguration +@ComponentScan({"com.yomahub.liteflow.test.apollo.cmp"}) +public class ApolloWithXmlELSpringbootTest { + + + @Resource + private FlowExecutor flowExecutor; + + @After + public void after() { + FlowBus.cleanCache(); + } + + + @Test + public void testApolloWithXml1() throws InterruptedException { + LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg"); + Assert.assertEquals("a==>b==>c==>s1[脚本s1]", response.getExecuteStepStrWithoutTime()); + } + + + @Test + public void testApolloWithXml2() throws InterruptedException { + while (true) { + LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg"); + System.out.println("liteflow step : " + response.getExecuteStepStrWithoutTime()); + Thread.sleep(2000l); + } + } + +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-apollo-springboot/src/test/java/com/yomahub/liteflow/test/apollo/cmp/ACmp.java b/liteflow-testcase-el/liteflow-testcase-el-apollo-springboot/src/test/java/com/yomahub/liteflow/test/apollo/cmp/ACmp.java new file mode 100644 index 000000000..871f8144b --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-apollo-springboot/src/test/java/com/yomahub/liteflow/test/apollo/cmp/ACmp.java @@ -0,0 +1,21 @@ +/** + *

Title: liteflow

+ *

Description: 轻量级的组件式流程框架

+ * + * @author Bryan.Zhang + * @email weenyc31@163.com + * @Date 2020/4/1 + */ +package com.yomahub.liteflow.test.apollo.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-apollo-springboot/src/test/java/com/yomahub/liteflow/test/apollo/cmp/BCmp.java b/liteflow-testcase-el/liteflow-testcase-el-apollo-springboot/src/test/java/com/yomahub/liteflow/test/apollo/cmp/BCmp.java new file mode 100644 index 000000000..482e2c2a6 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-apollo-springboot/src/test/java/com/yomahub/liteflow/test/apollo/cmp/BCmp.java @@ -0,0 +1,22 @@ +/** + *

Title: liteflow

+ *

Description: 轻量级的组件式流程框架

+ * + * @author Bryan.Zhang + * @email weenyc31@163.com + * @Date 2020/4/1 + */ +package com.yomahub.liteflow.test.apollo.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-apollo-springboot/src/test/java/com/yomahub/liteflow/test/apollo/cmp/CCmp.java b/liteflow-testcase-el/liteflow-testcase-el-apollo-springboot/src/test/java/com/yomahub/liteflow/test/apollo/cmp/CCmp.java new file mode 100644 index 000000000..79b305837 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-apollo-springboot/src/test/java/com/yomahub/liteflow/test/apollo/cmp/CCmp.java @@ -0,0 +1,22 @@ +/** + *

Title: liteflow

+ *

Description: 轻量级的组件式流程框架

+ * + * @author Bryan.Zhang + * @email weenyc31@163.com + * @Date 2020/4/1 + */ +package com.yomahub.liteflow.test.apollo.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-apollo-springboot/src/test/resources/apollo/application-xml.properties b/liteflow-testcase-el/liteflow-testcase-el-apollo-springboot/src/test/resources/apollo/application-xml.properties new file mode 100644 index 000000000..366b1dc36 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-apollo-springboot/src/test/resources/apollo/application-xml.properties @@ -0,0 +1,2 @@ +liteflow.rule-source-ext-data={"chainNamespace":"chainConfig","scriptNamespace":"scriptConfig"} +liteflow.parse-on-start=false \ No newline at end of file diff --git a/pom.xml b/pom.xml index 9ece2bb5f..3d91f3510 100644 --- a/pom.xml +++ b/pom.xml @@ -68,6 +68,7 @@ 31.1-jre 4.5.13 1.9.4 + 1.7.0 @@ -259,6 +260,12 @@ provided true + + com.ctrip.framework.apollo + apollo-client + ${apollo.version} + +