1. chain、script分离方式支持Apollo动态配置

This commit is contained in:
zhanghua 2022-12-05 01:18:05 +08:00
parent 82028d33c4
commit 7e2e0c337c
5 changed files with 215 additions and 54 deletions

View File

@ -26,10 +26,6 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>

View File

@ -4,6 +4,7 @@ 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;
@ -13,7 +14,6 @@ import com.yomahub.liteflow.property.LiteflowConfigGetter;
import com.yomahub.liteflow.util.JsonUtil;
import java.util.Objects;
import java.util.function.Consumer;
/**
* @Description:
@ -33,29 +33,32 @@ public class ApolloXmlELParser extends ClassXmlFlowELParser {
} 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() {
Consumer<String> parseConsumer = t -> {
try {
parse(t);
} catch (Exception e) {
throw new ApolloException(e.getMessage());
}
};
try {
String content = apolloParseHelper.getContent();
apolloParseHelper.checkContent(content);
apolloParseHelper.listen(parseConsumer);
FlowInitHook.addHook(() -> {
apolloParseHelper.listenApollo();
return true;
});
return content;
} catch (Exception e) {

View File

@ -1,17 +1,26 @@
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.ConfigFile;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.yomahub.liteflow.exception.ParseException;
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.function.Consumer;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @Description:
@ -23,54 +32,197 @@ 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 ConfigFile liteflowConfigFile;
private Config chainConfig;
private Config scriptConfig;
public ApolloParseHelper(ApolloParserConfigVO apolloParserConfigVO) {
this.apolloParserConfigVO = apolloParserConfigVO;
try {
liteflowConfigFile = ConfigService.getConfigFile(apolloParserConfigVO.getNamespace(), ConfigFileFormat.XML);
chainConfig = ConfigService.getConfig(apolloParserConfigVO.getChainNamespace());
String scriptNamespace;
// scriptConfig is optional
if (StrUtil.isNotBlank(scriptNamespace = apolloParserConfigVO.getScriptNamespace())) {
scriptConfig = ConfigService.getConfig(scriptNamespace);
}
} catch (Exception e) {
LOG.error("[ApolloParseHelper] liteflowConfigFile get init error, apolloParserConfigVO:{}", apolloParserConfigVO);
throw new ApolloException(e.getMessage());
}
}
public String getContent() {
try {
ConfigFile configFile = ConfigService.getConfigFile(apolloParserConfigVO.getNamespace(), ConfigFileFormat.XML);
String content;
if (Objects.isNull(configFile) || StrUtil.isBlank(content = configFile.getContent())) {
throw new ApolloException(String.format("not find config file, namespace:%s", apolloParserConfigVO.getNamespace()));
// 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()));
}
return content;
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());
}
}
/**
* 检查 content 是否合法
*/
public void checkContent(String content) {
if (StrUtil.isBlank(content)) {
String error = StrUtil.format("the node[{}] value is empty", apolloParserConfigVO.toString());
throw new ParseException(error);
}
}
/**
* 监听 apollo 数据变化
* listen apollo config change
*/
public void listen(Consumer<String> parseConsumer) {
try {
liteflowConfigFile.addChangeListener(configFileChangeEvent -> {
String newContext = configFileChangeEvent.getNewValue();
parseConsumer.accept(newContext);
});
} catch (Exception e) {
throw new ApolloException(e.getMessage());
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

@ -7,28 +7,40 @@ package com.yomahub.liteflow.parser.apollo.vo;
*/
public class ApolloParserConfigVO {
private String namespace;
private String chainNamespace;
private String scriptNamespace;
public ApolloParserConfigVO() {
}
public ApolloParserConfigVO(String namespace) {
this.namespace = namespace;
public ApolloParserConfigVO(String chainNamespace, String scriptNamespace) {
this.chainNamespace = chainNamespace;
this.scriptNamespace = scriptNamespace;
}
public String getNamespace() {
return namespace;
public String getChainNamespace() {
return chainNamespace;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
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{" +
"namespace='" + namespace + '\'' +
"chainNamespace='" + chainNamespace + '\'' +
", scriptNamespace='" + scriptNamespace + '\'' +
'}';
}
}

View File

@ -377,8 +377,6 @@
<module>liteflow-spring-boot-starter</module>
<module>liteflow-spring</module>
<module>liteflow-testcase-el</module>
<module>liteflow-rule-apollo</module>
<module>liteflow-testcase-el-apollo-springboot</module>
</modules>
<distributionManagement>