mirror of
https://gitee.com/dromara/liteFlow.git
synced 2026-05-14 12:12:08 +08:00
feature #I3CTY2 规则支持json和yml
This commit is contained in:
@@ -62,6 +62,10 @@
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dom4j</groupId>
|
||||
<artifactId>dom4j</artifactId>
|
||||
|
||||
@@ -16,6 +16,7 @@ import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.yomahub.liteflow.exception.ConfigErrorException;
|
||||
import com.yomahub.liteflow.parser.*;
|
||||
import com.yomahub.liteflow.property.LiteflowConfig;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
@@ -29,9 +30,6 @@ import com.yomahub.liteflow.exception.ChainNotFoundException;
|
||||
import com.yomahub.liteflow.exception.FlowExecutorNotInitException;
|
||||
import com.yomahub.liteflow.exception.NoAvailableSlotException;
|
||||
import com.yomahub.liteflow.flow.FlowBus;
|
||||
import com.yomahub.liteflow.parser.LocalXmlFlowParser;
|
||||
import com.yomahub.liteflow.parser.XmlFlowParser;
|
||||
import com.yomahub.liteflow.parser.ZookeeperXmlFlowParser;
|
||||
|
||||
/**
|
||||
* 流程规则主要执行器类
|
||||
@@ -53,13 +51,18 @@ public class FlowExecutor {
|
||||
|
||||
List<String> rulePath = Lists.newArrayList(liteflowConfig.getRuleSource().split(",|;"));
|
||||
|
||||
XmlFlowParser parser = null;
|
||||
// XmlFlowParser parser = null;
|
||||
FlowParser parser = null;
|
||||
for(String path : rulePath){
|
||||
try {
|
||||
|
||||
if(isLocalConfig(path)) { //判断是否是本地的xml文件
|
||||
parser = new LocalXmlFlowParser();
|
||||
}else if(isZKConfig(path)){ //判断是否是zk配置
|
||||
} else if(isLocalJsonConfig(path)) {
|
||||
parser = new LocalJsonFlowParser();
|
||||
} else if(isLocalYmlConfig(path)) {
|
||||
parser = new LocalYmlFlowParser();
|
||||
} else if(isZKConfig(path)){ //判断是否是zk配置
|
||||
if(StringUtils.isNotBlank(zkNode)) {
|
||||
parser = new ZookeeperXmlFlowParser(zkNode);
|
||||
}else {
|
||||
@@ -90,6 +93,18 @@ public class FlowExecutor {
|
||||
return m.find();
|
||||
}
|
||||
|
||||
private boolean isLocalJsonConfig(String path) {
|
||||
Pattern p = Pattern.compile("^[\\w_\\-\\@\\/]+\\.json$");
|
||||
Matcher m = p.matcher(path);
|
||||
return m.find();
|
||||
}
|
||||
|
||||
private boolean isLocalYmlConfig(String path) {
|
||||
Pattern p = Pattern.compile("^[\\w_\\-\\@\\/]+\\.yml$");
|
||||
Matcher m = p.matcher(path);
|
||||
return m.find();
|
||||
}
|
||||
|
||||
private boolean isClassConfig(String path) {
|
||||
Pattern p = Pattern.compile("^\\w+(\\.\\w+)*$");
|
||||
Matcher m = p.matcher(path);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.yomahub.liteflow.parser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @Author: guodongqing
|
||||
* @Date: 2021/3/25 4:47 下午
|
||||
*/
|
||||
public abstract class FlowParser {
|
||||
|
||||
public abstract void parseMain(String path) throws Exception;
|
||||
|
||||
public abstract void parse(String content) throws Exception ;
|
||||
|
||||
//条件节点的正则解析
|
||||
public static RegexEntity parseNodeStr(String str) {
|
||||
List<String> list = new ArrayList<String>();
|
||||
Pattern p = Pattern.compile("[^\\)\\(]+");
|
||||
Matcher m = p.matcher(str);
|
||||
while(m.find()){
|
||||
list.add(m.group());
|
||||
}
|
||||
RegexEntity regexEntity = new RegexEntity();
|
||||
regexEntity.setItem(list.get(0).trim());
|
||||
if(list.size() > 1){
|
||||
String[] realNodeArray = list.get(1).split("\\|");
|
||||
for (int i = 0; i < realNodeArray.length; i++) {
|
||||
realNodeArray[i] = realNodeArray[i].trim();
|
||||
}
|
||||
regexEntity.setRealItemArray(realNodeArray);
|
||||
}
|
||||
return regexEntity;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package com.yomahub.liteflow.parser;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson.parser.Feature;
|
||||
import com.yomahub.liteflow.core.NodeComponent;
|
||||
import com.yomahub.liteflow.entity.flow.*;
|
||||
import com.yomahub.liteflow.exception.ExecutableItemNotFoundException;
|
||||
import com.yomahub.liteflow.exception.ParseException;
|
||||
import com.yomahub.liteflow.flow.FlowBus;
|
||||
import com.yomahub.liteflow.spring.ComponentScaner;
|
||||
import com.yomahub.liteflow.util.SpringAware;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Json格式解析器
|
||||
* @Author: guodongqing
|
||||
* @Date: 2021-03-25 16:40:00
|
||||
*/
|
||||
public abstract class JsonFlowParser extends FlowParser{
|
||||
|
||||
private final Logger LOG = LoggerFactory.getLogger(JsonFlowParser.class);
|
||||
|
||||
@Override
|
||||
public void parse(String content) throws Exception {
|
||||
//把字符串原生转换为json对象,如果不加第二个参数OrderedField,会无序
|
||||
JSONObject flowJsonObject = JSONObject.parseObject(content, Feature.OrderedField);
|
||||
parse(flowJsonObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* json格式,解析过程
|
||||
* @param flowJsonObject
|
||||
* @throws Exception
|
||||
*/
|
||||
public void parse(JSONObject flowJsonObject) throws Exception {
|
||||
try {
|
||||
//判断是以spring方式注册节点,还是以json方式注册
|
||||
if(ComponentScaner.nodeComponentMap.isEmpty()){
|
||||
JSONArray nodeArrayList = flowJsonObject.getJSONObject("nodes").getJSONArray("node");
|
||||
String id;
|
||||
String clazz;
|
||||
Node node;
|
||||
NodeComponent component;
|
||||
Class<NodeComponent> nodeComponentClass;
|
||||
for(int i = 0; i< nodeArrayList.size(); i++) {
|
||||
JSONObject nodeObject = nodeArrayList.getJSONObject(i);
|
||||
node = new Node();
|
||||
id = nodeObject.getString("id");
|
||||
clazz = nodeObject.getString("class");
|
||||
node.setId(id);
|
||||
node.setClazz(clazz);
|
||||
nodeComponentClass = (Class<NodeComponent>)Class.forName(clazz);
|
||||
component = SpringAware.registerOrGet(nodeComponentClass);
|
||||
if (component == null) {
|
||||
LOG.error("couldn't find component class [{}] ", clazz);
|
||||
throw new ParseException("cannot parse flow json");
|
||||
}
|
||||
component.setNodeId(id);
|
||||
node.setInstance(component);
|
||||
FlowBus.addNode(id, node);
|
||||
}
|
||||
} else {
|
||||
for(Map.Entry<String, NodeComponent> componentEntry : ComponentScaner.nodeComponentMap.entrySet()){
|
||||
if(!FlowBus.containNode(componentEntry.getKey())){
|
||||
FlowBus.addNode(componentEntry.getKey(), new Node(componentEntry.getKey(), componentEntry.getValue().getClass().getName(), componentEntry.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 解析chain节点
|
||||
JSONArray chainList = flowJsonObject.getJSONObject("flow").getJSONArray("chain");
|
||||
Map<String, JSONObject> chainMap = new HashMap<>();
|
||||
for(int i=0; i<chainList.size(); i++) {
|
||||
JSONObject chainObject = chainList.getJSONObject(i);
|
||||
if(chainObject.containsKey("name") && StringUtils.isNotBlank(chainObject.getString("name"))) {
|
||||
chainMap.put(chainObject.getString("name"), chainObject);
|
||||
}
|
||||
}
|
||||
|
||||
for(Map.Entry<String, JSONObject> chainEntry : chainMap.entrySet()) {
|
||||
parseOneChain(chainEntry.getValue(), chainMap);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.error("JsonFlowParser parser exception", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析一个chain的过程
|
||||
* @param chainObject
|
||||
* @param chainMap
|
||||
* @throws Exception
|
||||
*/
|
||||
private void parseOneChain(JSONObject chainObject, Map<String, JSONObject> chainMap) throws Exception{
|
||||
String condArrayStr;
|
||||
String[] condArray;
|
||||
List<Executable> chainNodeList;
|
||||
List<Condition> conditionList;
|
||||
String chainName = chainObject.getString("name");
|
||||
JSONArray chainTopoArray = chainObject.getJSONArray("topo");
|
||||
conditionList = new ArrayList<>();
|
||||
for(Iterator<Object> iterator = chainTopoArray.iterator(); iterator.hasNext();) {
|
||||
JSONObject condObject = (JSONObject) iterator.next();
|
||||
String condType = condObject.getString("type");
|
||||
condArrayStr = condObject.getString("value");
|
||||
if (StringUtils.isBlank(condType) || StringUtils.isBlank(condArrayStr)) {
|
||||
continue;
|
||||
}
|
||||
chainNodeList = new ArrayList<>();
|
||||
condArray = condArrayStr.split(",");
|
||||
RegexEntity regexEntity;
|
||||
String itemExpression;
|
||||
String item;
|
||||
//这里解析的规则,优先按照node去解析,再按照chain去解析
|
||||
for (int i = 0; i < condArray.length; i++) {
|
||||
itemExpression = condArray[i].trim();
|
||||
regexEntity = parseNodeStr(itemExpression);
|
||||
item = regexEntity.getItem();
|
||||
if (FlowBus.containNode(item)) {
|
||||
Node node = FlowBus.getNode(item);
|
||||
chainNodeList.add(node);
|
||||
//这里判断是不是条件节点,条件节点会含有realItem,也就是括号里的node
|
||||
if (regexEntity.getRealItemArray() != null) {
|
||||
for (String key : regexEntity.getRealItemArray()) {
|
||||
if (FlowBus.containNode(key)) {
|
||||
Node condNode = FlowBus.getNode(key);
|
||||
node.setCondNode(condNode.getId(), condNode);
|
||||
} else if (hasChain(chainMap, key)) {
|
||||
Chain chain = FlowBus.getChain(key);
|
||||
node.setCondNode(chain.getChainName(), chain);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(hasChain(chainMap,item)){
|
||||
Chain chain = FlowBus.getChain(item);
|
||||
chainNodeList.add(chain);
|
||||
}
|
||||
else {
|
||||
String errorMsg = StrUtil.format("executable node[{}] is not found!", regexEntity.getItem());
|
||||
throw new ExecutableItemNotFoundException(errorMsg);
|
||||
}
|
||||
}
|
||||
if (condType.equals("then")) {
|
||||
conditionList.add(new ThenCondition(chainNodeList));
|
||||
} else if (condType.equals("when")) {
|
||||
conditionList.add(new WhenCondition(chainNodeList));
|
||||
}
|
||||
FlowBus.addChain(chainName, new Chain(chainName,conditionList));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断在这个FlowBus元数据里是否含有这个chain
|
||||
* 因为chain和node都是可执行器,在一个规则文件上,有可能是node,有可能是chain
|
||||
* @param chainMap
|
||||
* @param chainName
|
||||
* @return
|
||||
*/
|
||||
private boolean hasChain(Map<String, JSONObject> chainMap, String chainName) throws Exception {
|
||||
if(chainMap.containsKey(chainName) && !FlowBus.containChain(chainName)) {
|
||||
parseOneChain(chainMap.get(chainName), chainMap);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.yomahub.liteflow.parser;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
|
||||
/**
|
||||
* @Author: guodongqing
|
||||
* @Date: 2021/3/26 12:26 下午
|
||||
*/
|
||||
public class LocalJsonFlowParser extends JsonFlowParser{
|
||||
|
||||
@Override
|
||||
public void parseMain(String rulePath) throws Exception {
|
||||
String ruleContent = FileUtil.readUtf8String(rulePath);
|
||||
parse(ruleContent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.yomahub.liteflow.parser;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Yaml格式转换
|
||||
* @Author: guodongqing
|
||||
* @Date: 2021/3/26 12:56 下午
|
||||
*/
|
||||
public class LocalYmlFlowParser extends JsonFlowParser{
|
||||
|
||||
@Override
|
||||
public void parseMain(String rulePath) throws Exception {
|
||||
String ruleContent = FileUtil.readUtf8String(rulePath);
|
||||
JSONObject ruleObject = convertToJson(ruleContent);
|
||||
parse(ruleObject.toJSONString());
|
||||
}
|
||||
|
||||
private JSONObject convertToJson(String yamlString) {
|
||||
Yaml yaml= new Yaml();
|
||||
Map<String, Object> map = yaml.load(yamlString);
|
||||
JSONObject jsonObject = new JSONObject(map);
|
||||
return jsonObject;
|
||||
}
|
||||
}
|
||||
@@ -27,12 +27,10 @@ import com.yomahub.liteflow.spring.ComponentScaner;
|
||||
* xml形式的解析器
|
||||
* @author Bryan.Zhang
|
||||
*/
|
||||
public abstract class XmlFlowParser {
|
||||
public abstract class XmlFlowParser extends FlowParser{
|
||||
|
||||
private final Logger LOG = LoggerFactory.getLogger(XmlFlowParser.class);
|
||||
|
||||
public abstract void parseMain(String path) throws Exception;
|
||||
|
||||
public void parse(String content) throws Exception {
|
||||
Document document = DocumentHelper.parseText(content);
|
||||
parse(document);
|
||||
@@ -161,24 +159,4 @@ public abstract class XmlFlowParser {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//条件节点的正则解析
|
||||
public static RegexEntity parseNodeStr(String str) {
|
||||
List<String> list = new ArrayList<String>();
|
||||
Pattern p = Pattern.compile("[^\\)\\(]+");
|
||||
Matcher m = p.matcher(str);
|
||||
while(m.find()){
|
||||
list.add(m.group());
|
||||
}
|
||||
RegexEntity regexEntity = new RegexEntity();
|
||||
regexEntity.setItem(list.get(0).trim());
|
||||
if(list.size() > 1){
|
||||
String[] realNodeArray = list.get(1).split("\\|");
|
||||
for (int i = 0; i < realNodeArray.length; i++) {
|
||||
realNodeArray[i] = realNodeArray[i].trim();
|
||||
}
|
||||
regexEntity.setRealItemArray(realNodeArray);
|
||||
}
|
||||
return regexEntity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
liteflow.rule-source=config/flow.xml
|
||||
#liteflow.rule-source=config/flow.yml
|
||||
#liteflow.slot-size=2048
|
||||
liteflow.when-max-wait-seconds=20
|
||||
liteflow.monitor.enable-log=true
|
||||
|
||||
75
liteflow-test-springboot/src/main/resources/config/flow.json
Normal file
75
liteflow-test-springboot/src/main/resources/config/flow.json
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"flow": {
|
||||
"nodes": {
|
||||
"node": [
|
||||
{
|
||||
"id": "a",
|
||||
"class": "com.yomahub.liteflow.test.component.AComponent"
|
||||
},
|
||||
{
|
||||
"id": "b",
|
||||
"class": "com.yomahub.liteflow.test.component.BComponent"
|
||||
},
|
||||
{
|
||||
"id": "c",
|
||||
"class": "com.yomahub.liteflow.test.component.CComponent"
|
||||
},
|
||||
{
|
||||
"id": "d",
|
||||
"class": "com.yomahub.liteflow.test.component.DComponent"
|
||||
},
|
||||
{
|
||||
"id": "e",
|
||||
"class": "com.yomahub.liteflow.test.component.EComponent"
|
||||
},
|
||||
{
|
||||
"id": "f",
|
||||
"class": "com.yomahub.liteflow.test.component.FComponent"
|
||||
},
|
||||
{
|
||||
"id": "g",
|
||||
"class": "com.yomahub.liteflow.test.component.GComponent"
|
||||
},
|
||||
{
|
||||
"id": "cond",
|
||||
"class": "com.yomahub.liteflow.test.component.CondComponent"
|
||||
}
|
||||
]
|
||||
},
|
||||
"chain": [
|
||||
{
|
||||
"name": "chain1",
|
||||
"topo": [
|
||||
{"type": "then", "value": "a,cond(b|d)"},
|
||||
{"type": "then", "value": "e,f,g"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "chain2",
|
||||
"topo": [
|
||||
{"type": "then", "value": "a,c"},
|
||||
{"type": "when", "value": "b,d,e,f,g"},
|
||||
{"type": "then", "value": "c"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "chain3",
|
||||
"topo": [
|
||||
{"type": "then", "value": "a,c,strategy1,g"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "strategy1",
|
||||
"topo": [
|
||||
{"type": "then", "value": "m(m1|m2|strategy2)"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "strategy2",
|
||||
"topo": [
|
||||
{"type": "then", "value": "q,p(p1|p2)"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
46
liteflow-test-springboot/src/main/resources/config/flow.yml
Normal file
46
liteflow-test-springboot/src/main/resources/config/flow.yml
Normal file
@@ -0,0 +1,46 @@
|
||||
flow:
|
||||
nodes:
|
||||
node:
|
||||
- id: a
|
||||
class: com.yomahub.liteflow.test.component.AComponent
|
||||
- id: b
|
||||
class: com.yomahub.liteflow.test.component.BComponent
|
||||
- id: c
|
||||
class: com.yomahub.liteflow.test.component.CComponent
|
||||
- id: d
|
||||
class: com.yomahub.liteflow.test.component.DComponent
|
||||
- id: e
|
||||
class: com.yomahub.liteflow.test.component.EComponent
|
||||
- id: f
|
||||
class: com.yomahub.liteflow.test.component.FComponent
|
||||
- id: g
|
||||
class: com.yomahub.liteflow.test.component.GComponent
|
||||
- id: cond
|
||||
class: com.yomahub.liteflow.test.component.CondComponent
|
||||
chain:
|
||||
- name: chain1
|
||||
topo:
|
||||
- type: then
|
||||
value: 'a,cond(b|d)'
|
||||
- type: then
|
||||
value: 'e,f,g'
|
||||
- name: chain2
|
||||
topo:
|
||||
- type: then
|
||||
value: 'a,c'
|
||||
- type: when
|
||||
value: 'b,d,e,f,g'
|
||||
- type: then
|
||||
value: 'c'
|
||||
- name: chain3
|
||||
topo:
|
||||
- type: then
|
||||
value: 'a,c,strategy1,g'
|
||||
- name: strategy1
|
||||
topo:
|
||||
- type: then
|
||||
value: m(m1|m2|strategy2)
|
||||
- name: strategy2
|
||||
topo:
|
||||
- type: then
|
||||
value: 'q,p(p1|p2)'
|
||||
8
pom.xml
8
pom.xml
@@ -50,7 +50,8 @@
|
||||
<log4j.version>1.2.17</log4j.version>
|
||||
<log4j-slf4j.version>1.7.5</log4j-slf4j.version>
|
||||
<slf4j.version>1.7.13</slf4j.version>
|
||||
<fastjson.version>1.2.25</fastjson.version>
|
||||
<fastjson.version>1.2.70</fastjson.version>
|
||||
<snakeyaml.version>1.19</snakeyaml.version>
|
||||
<dom4j.version>1.6.1</dom4j.version>
|
||||
<curator.version>2.12.0</curator.version>
|
||||
<junit.version>4.12</junit.version>
|
||||
@@ -109,6 +110,11 @@
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>${fastjson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>${snakeyaml.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dom4j</groupId>
|
||||
<artifactId>dom4j</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user