Merge pull request !141 from Zero/feature/apollo_graceful
This commit is contained in:
铂赛东
2022-12-05 02:46:28 +00:00
committed by Gitee
15 changed files with 633 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>liteflow-rule-plugin</artifactId>
<groupId>com.yomahub</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>liteflow-rule-apollo</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-core</artifactId>
<version>${revision}</version>
<optional>true</optional>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -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());
}
}
}

View File

@@ -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;
}
}

View File

@@ -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 = "<chain name=\"{}\">{}</chain>";
private final String NODE_XML_PATTERN = "<nodes>{}</nodes>";
private final String NODE_ITEM_XML_PATTERN = "<node id=\"{}\" name=\"{}\" type=\"{}\"><![CDATA[{}]]></node>";
private final String XML_PATTERN = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><flow>{}{}</flow>";
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<String> propertyNames = chainConfig.getPropertyNames();
if (CollectionUtil.isEmpty(propertyNames)) {
throw new ApolloException(StrUtil.format("There are no chains in namespace : {}", apolloParserConfigVO.getChainNamespace()));
}
List<String> 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<String> scriptNamespaces;
if (Objects.nonNull(scriptConfig) && CollectionUtil.isNotEmpty(scriptNamespaces = scriptConfig.getPropertyNames())) {
List<String> 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<String> 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;
}
}
}

View File

@@ -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 + '\'' +
'}';
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1 @@
com.yomahub.liteflow.parser.spi.apollo.ApolloParserClassNameSpi

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>liteflow-testcase-el</artifactId>
<groupId>com.yomahub</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>liteflow-testcase-el-apollo-springboot</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-spring-boot-starter</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-rule-apollo</artifactId>
<version>${revision}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${hutool-core.version}</version>
</dependency>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-script-groovy</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</project>

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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.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!");
}
}

View File

@@ -0,0 +1,22 @@
/**
* <p>Title: liteflow</p>
* <p>Description: 轻量级的组件式流程框架</p>
*
* @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!");
}
}

View File

@@ -0,0 +1,22 @@
/**
* <p>Title: liteflow</p>
* <p>Description: 轻量级的组件式流程框架</p>
*
* @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!");
}
}

View File

@@ -0,0 +1,2 @@
liteflow.rule-source-ext-data={"chainNamespace":"chainConfig","scriptNamespace":"scriptConfig"}
liteflow.parse-on-start=false

View File

@@ -68,6 +68,7 @@
<guava.version>31.1-jre</guava.version>
<httpclient.version>4.5.13</httpclient.version>
<commons-beanutils.version>1.9.4</commons-beanutils.version>
<apollo.version>1.7.0</apollo.version>
</properties>
<dependencyManagement>
@@ -259,6 +260,12 @@
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>${apollo.version}</version>
</dependency>
</dependencies>
</dependencyManagement>