diff --git a/liteflow-core/pom.xml b/liteflow-core/pom.xml index a98acb533..b7cbd1f22 100644 --- a/liteflow-core/pom.xml +++ b/liteflow-core/pom.xml @@ -62,6 +62,10 @@ com.alibaba fastjson + + org.yaml + snakeyaml + dom4j dom4j diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java b/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java index 6a9ff0bd2..990653fa7 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java @@ -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 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); diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/parser/FlowParser.java b/liteflow-core/src/main/java/com/yomahub/liteflow/parser/FlowParser.java new file mode 100644 index 000000000..e5eaeb605 --- /dev/null +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/parser/FlowParser.java @@ -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 list = new ArrayList(); + 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; + } + +} diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/parser/JsonFlowParser.java b/liteflow-core/src/main/java/com/yomahub/liteflow/parser/JsonFlowParser.java new file mode 100644 index 000000000..2943c0ea6 --- /dev/null +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/parser/JsonFlowParser.java @@ -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 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)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 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 chainMap = new HashMap<>(); + for(int i=0; i 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 chainMap) throws Exception{ + String condArrayStr; + String[] condArray; + List chainNodeList; + List conditionList; + String chainName = chainObject.getString("name"); + JSONArray chainTopoArray = chainObject.getJSONArray("topo"); + conditionList = new ArrayList<>(); + for(Iterator 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 chainMap, String chainName) throws Exception { + if(chainMap.containsKey(chainName) && !FlowBus.containChain(chainName)) { + parseOneChain(chainMap.get(chainName), chainMap); + return true; + } + return false; + } +} diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/parser/LocalJsonFlowParser.java b/liteflow-core/src/main/java/com/yomahub/liteflow/parser/LocalJsonFlowParser.java new file mode 100644 index 000000000..a8e0de423 --- /dev/null +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/parser/LocalJsonFlowParser.java @@ -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); + } +} diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/parser/LocalYmlFlowParser.java b/liteflow-core/src/main/java/com/yomahub/liteflow/parser/LocalYmlFlowParser.java new file mode 100644 index 000000000..ec46d9d03 --- /dev/null +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/parser/LocalYmlFlowParser.java @@ -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 map = yaml.load(yamlString); + JSONObject jsonObject = new JSONObject(map); + return jsonObject; + } +} diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/parser/XmlFlowParser.java b/liteflow-core/src/main/java/com/yomahub/liteflow/parser/XmlFlowParser.java index 372524838..f1b1c849a 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/parser/XmlFlowParser.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/parser/XmlFlowParser.java @@ -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 list = new ArrayList(); - 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; - } } diff --git a/liteflow-test-springboot/src/main/resources/application.properties b/liteflow-test-springboot/src/main/resources/application.properties index 0649d9746..91894fdb4 100644 --- a/liteflow-test-springboot/src/main/resources/application.properties +++ b/liteflow-test-springboot/src/main/resources/application.properties @@ -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 diff --git a/liteflow-test-springboot/src/main/resources/config/flow.json b/liteflow-test-springboot/src/main/resources/config/flow.json new file mode 100644 index 000000000..975f60e03 --- /dev/null +++ b/liteflow-test-springboot/src/main/resources/config/flow.json @@ -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)"} + ] + } + ] + } +} \ No newline at end of file diff --git a/liteflow-test-springboot/src/main/resources/config/flow.yml b/liteflow-test-springboot/src/main/resources/config/flow.yml new file mode 100644 index 000000000..91f702598 --- /dev/null +++ b/liteflow-test-springboot/src/main/resources/config/flow.yml @@ -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)' diff --git a/pom.xml b/pom.xml index 3370bfacb..4b0a0fec1 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,8 @@ 1.2.17 1.7.5 1.7.13 - 1.2.25 + 1.2.70 + 1.19 1.6.1 2.12.0 4.12 @@ -109,6 +110,11 @@ fastjson ${fastjson.version} + + org.yaml + snakeyaml + ${snakeyaml.version} + dom4j dom4j