目录
需求,给定1个图(暂时不包含判断),求他的执行顺序
假设单线程主机(其中每个字母,代表不同的算子)

最终想得到的是执行顺序

如果3没有走,则不能运行4和5.
请求测试
{
"id":"1",
"newsName":"测试任务",
"jsonArray":[
{"name":"a","next":["b","c"],"pre":[]},
{"name":"b","next":["d"],"pre":["a"]},
{"name":"c","next":[],"pre":["a"]},
{"name":"d","next":["f","e"],"pre":["b"]},
{"name":"f","next":[],"pre":["d"]},
{"name":"e","next":[],"pre":["d"]}
],
"cron":"* * * * *",
"executeCount":1
}
只用管红色内容即可,得到
{
"code": 1,
"message": "执行成功",
"data": {
"1": {
"name": "a",
"next": [
"b",
"c"
],
"pre": []
},
"2": {
"name": "b",
"next": [
"d"
],
"pre": [
"a"
]
},
"3": {
"name": "d",
"next": [
"f",
"e"
],
"pre": [
"b"
]
},
"4": {
"name": "f",
"next": [],
"pre": [
"d"
]
},
"5": {
"name": "e",
"next": [],
"pre": [
"d"
]
},
"6": {
"name": "c",
"next": [],
"pre": [
"a"
]
}
}
}
JAVA,controller代码
- @PostMapping("/debugRun")
- @ResponseBody
- public RsJsonBean test(@RequestBody JsonRequestParam jsonRequestParam){
- System.out.println(jsonRequestParam);
- try {
-
-
- //走service
- JSONArray jsonArray = jsonRequestParam.getJsonArray();
-
- //排序算法,计算出顺序
-
- /**
- * 先寻找根节点
- * */
-
-
- //将节点遍历以name为key,value为jsonObject
- HashMap
nameJsonObject = new HashMap(); - for (int i = 0; i
- JSONObject jsonObject = jsonArray.getJSONObject(i);
- String name = jsonObject.getString("name");//name前端必须传过来,而且还是唯一的
- nameJsonObject.put(name,jsonObject);
- }
-
- HashMap
numAction = new HashMap();//定义最终顺序 - HashMap
preExecuteName = new HashMap();//定义已经编过号的节点,value暂时没有使用 - for (int i = 0; i < jsonArray.size(); i++) {
- JSONObject jsonObject = jsonArray.getJSONObject(i);
- JSONArray prev = jsonObject.getJSONArray("pre");
- if (prev.size()==0){//前端必须给数组,上面没有父节点,给空数组
- //找到了根节点,根节点编号,执行sortAction递归
- //编号
- numAction.put(numAction.size()+1,jsonObject);
- preExecuteName.put(jsonObject.getString("name"),"已经预执行");
- //递归遍历子节点,并且编号
- SortTool.sortAction(jsonObject,numAction,nameJsonObject,preExecuteName);
- }
- }
-
- System.out.println("成功,顺序为"+numAction);
-
- //---等会儿封装到service中
- return new RsJsonBean(1,"执行成功",numAction);
-
- }catch (NullPointerException e){
- e.printStackTrace();
- return new RsJsonBean
(0,"传入数据字段格式不匹配,导致解析为空"+e); - }
-
-
- }
JAVA的递归代码
- package com.dahua.tools;
-
- import com.alibaba.fastjson.JSONArray;
- import com.alibaba.fastjson.JSONObject;
-
- import java.util.HashMap;
-
- public class SortTool {
-
- //预排序,执行算子的各个顺序
- /**
- *方法1,计算执行顺序
- * @param jsonObject 父节点
- * @param numAction 排序的节点,key是执行顺序,value是算子jsonObj
- * @param nameJsonObject 所有的节点,key是唯一名称,value是jsonObj
- * @param preExecuteName 已经预执行过的节点,key是节点唯一名称
- *
- *
- */
- public static HashMap sortAction(JSONObject jsonObject,HashMap numAction,
- HashMap
nameJsonObject, - HashMap
preExecuteName) { - JSONArray nextNames = jsonObject.getJSONArray("next");//存节点唯一名称
-
- for (int i = 0; i < nextNames.size(); i++) {
- String name = nextNames.getString(i);
- JSONObject jsonObjectSon = nameJsonObject.get(name);//获取真正的子节点对象
- //子节点判断,是否为有多个父节点,并且都已经预执行过了
- JSONArray preNames = jsonObjectSon.getJSONArray("pre");
-
- if(preNames.size()>1){//则说明他是多节点合并来的
- //此时则遍历他的父节点,是否全部执行完成,若完成,则递归执行,若未完成,则返回到上一级
-
- boolean flag=true; //表示他的所有父节点已经执行完成
- for (int j = 0; j < preNames.size(); j++) {
- String preName = preNames.getString(j);
- if(!preExecuteName.containsKey(preName)){//父节点存在,没有执行完的
- flag=false;
- }
- }
-
- if(flag){
- // System.out.println(flag);
- //若没有子节点,则递归结束,返回上一级
- JSONArray nextNamesSon = jsonObjectSon.getJSONArray("next");
- if(nextNamesSon.size()==0){
- numAction.put(numAction.size()+1,jsonObjectSon);
- preExecuteName.put(name,"已经预执行此节点");
- }else{//有子节点,则处理完递归
- numAction.put(numAction.size()+1,jsonObjectSon);
- preExecuteName.put(name,"已经预执行此节点");
- jsonObject=jsonObjectSon;//父节点改变
- sortAction(jsonObject,numAction,nameJsonObject,preExecuteName);
- }
- }
- }else{
- //父节点处理结束--
- //若没有子节点,则递归结束,返回上一级
- JSONArray nextNamesSon = jsonObjectSon.getJSONArray("next");
- if(nextNamesSon.size()==0){
- numAction.put(numAction.size()+1,jsonObjectSon);
- preExecuteName.put(name,"已经预执行此节点");
- }else{//有子节点,则处理完递归
- numAction.put(numAction.size()+1,jsonObjectSon);
- preExecuteName.put(name,"已经预执行此节点");
- jsonObject=jsonObjectSon;//父节点改变
- sortAction(jsonObject,numAction,nameJsonObject,preExecuteName);
- }
- }
- }
-
- //获取下一个节点的数据
- return numAction;
-
-
-
-
- }
- }
根据后续业务升级版(支持判断和debug)
需求新增了,原来的预排序不能满足,于是想了一天,想到了很多方法,从中挑选了一个最简单,最清晰的方法

当e判断为【是】,则会执行i,不会执行f,那么f后面依赖的h也不会被执行
思路如下:
1.首先预排序
与前初版不同的是,预排序时候会【记录判断节点】所在的位置

2.按顺序执行
升级版新增:每次执行的时候,对整个任务流的算子进行记录(分为,已执行,未执行,不执行)
初始状态为【未执行】(下图1),当执行到【判断时】【根据判断结果】标识它的子节点为不执行的节点,进行递归(下图2),将判断下面的所有为否的节点进行递归标记,改为不执行状态
(图1)

(图2)

3. 按照顺序进行
》1首先会执行1,2,3(判断),此时执行到了判断。
判断根据结果进行递归,选择为不执行的子节点,然后子节点的子节点等全部设定为不执行状态。
》2 然后继续执行,4,5,6(执行到这里时)6是不执行状态则直接跳过,然后执行7,7也是不执行状态直接跳过
总结:每个算子除了头节点,都要判断是否为不执行状态,如果是,则不执行。由于是根据预排序改编的,所以不存在父节点没有执行,子节点已执行的情况。
代码(暂时不写了,可以自己根据代码研究)
暂时不写了
上司看了后,觉得不要预排序,觉得每次新增特殊算子(指的是更改任务流流程的算子),都要特殊处理,他们觉得递归不容易理解,然后使用数组代替递归,仔细想了下还是得递归,于是有了下面的方案
新方案(不依赖于预排序,不依赖递归,更加简单)
这个思路是领导想出来的,我后续实现时才发现它的妙处。完全替代了递归,用堆栈去解决(堆栈在java里面你可以看作是一个arrayList,每次删除开头的节点,插入从开头的节点插入,这就是一个堆栈)
流程图如下:

1.遍历整个任务流json,找到没有父节点的节点,作为根节点
2.遍历根节点的子节点,将其放入到堆栈{a,判断1}
3.终止条件为,堆栈中没有数据,即arrayList的大小为0
判断,对父节点的结果进行判断,若满足则为true走一条任务,若不满足则false走另一条任务
循环,分为循环开始和循环结束,循环开始你可以看作是一个标志节点,实质上没有什么用,为了避免循环结束各种甩到其他不适合的算子上,导致错误才搞出来的。
循环结束,里面有两种方式,1个是条件循环,当满足了什么条件才会停止,则为true时,则退出到下一个节点,为false则指向某个循环开始节点,另外一个是次数,当循环了多少次停止,这些都是在循环算子里定义的。如下图,为从整个任务流中抽出来的单个循环算子。

成果(我们采用的是socket连接),当执行完算子会向前端发tcp的消息,然后前端根据信号对界面进行渲染

下面为核心算法,我自己写的,分享给大家
传入参数json
{
"newsId":"asdadxaad1234",
"userId":"admin",
"clickTime":"2022-10-31 18:35:29",
"newsName":"测试合并",
"jsonArray":[
{"newsId":"asdadxaad1234","name":"a","next":["c","b"],"pre":[],"type":"开始","status":"未执行"},
{"newsId":"asdadxaad1234","name":"c","next":["e"],"pre":["a","h"],"type":"循环开始","status":"未执行"},
{"newsId":"asdadxaad1234","name":"e","next":["i","f"],"pre":["c"],"type":"判断","status":"未执行","content":{"true":["f"],"false":["i"]}},
{"newsId":"asdadxaad1234","name":"i","next":[],"pre":["e"],"type":"读取数据","status":"未执行"},
{"newsId":"asdadxaad1234","name":"f","next":["h"],"pre":["e"],"type":"读取数据","status":"未执行"},
{"newsId":"asdadxaad1234","name":"d","next":["h"],"pre":["b"],"type":"读取数据","status":"未执行"},
{"newsId":"asdadxaad1234","name":"b","next":["d"],"pre":["a"],"type":"读取数据","status":"未执行"},
{"newsId":"asdadxaad1234","name":"h","next":["c","z"],"pre":["f","d"],"type":"循环结束","status":"未执行","content":{"condition":"","times":1,"true":["z"],"false":["c"]}}
,{"newsId":"asdadxaad1234","name":"z","next":[],"pre":["h"],"type":"读取数据","status":"未执行"}
],
"cron":""
}
run方法(java,复制到controller即可使用)
- //TODO 新架构
- //支持判断,支持debug,非预排序,新版架构
- @PostMapping("/run2")
- @ResponseBody
- public static RsJsonBean run2(@RequestBody JsonRequestParam jsonRequestParam) {
-
-
- //检测特殊情况,写一个方法,调用,若不满足,//如果循环的condition不为""并且times也不为""则报错
- //判断算子的父节点不能有多个节点
- // 比如判断前面有多个节点,或者循环开始没有循环父节点指向,则报错 //TODO 还没有写
- //判断节点,只能有1个父节点,不能没有父节点,所以取1个,并且父节点必须是存在数据的节点,不能为输出节点
-
- //1.拿到根节点存入待执行数组,2//初始化得到name与obj的map
- JSONArray jsonArray = jsonRequestParam.getJsonArray();
-
-
- HashMap
name4json = new HashMap<>();//名字for杰森对象 - ArrayList
waitExeNode = new ArrayList<>(); - for (int i = 0; i < jsonArray.size(); i++) {
- JSONObject jsonObject = jsonArray.getJSONObject(i);
- String name = jsonObject.getString("name");
- if (jsonObject.getJSONArray("pre").size()<1){
- waitExeNode.add(name);
- }
- name4json.put(name,jsonObject);
- }
-
- //2.执行节点,取子节点,将要执行的子节点放在待执行数组的最前头
- while (waitExeNode.size()!=0){
- String name = waitExeNode.get(0);
- JSONObject jsonObject = name4json.get(name);
-
- //判断父节点是否满足
- JSONArray pres = jsonObject.getJSONArray("pre");
- if(pres.size()!=0){//根节点不需要满足
-
- //去掉他的循环结束的父节点(方法),在进行对比
- // TODO 等待写,传入pres,返回pres
- //方法开始
- for (int i = 0; i < pres.size(); i++) {
- String preName = pres.getString(i);
- JSONObject jsonObj = name4json.get(preName);
- String type = jsonObj.getString("type");
- if(type.equals("循环结束")){
- pres.remove(i);
- }
- }
- //方法结束
-
- if(SortTool.isRun(pres,name4json)==false){//则跳出此次循环
- //删除节点;
- waitExeNode.remove(0);
- continue;
- }
- }
- System.out.println("执行了"+name);
-
-
- //更新状态
- jsonObject.remove("status");
- jsonObject.put("status","已执行");
- name4json.remove(name);
- name4json.put(name,jsonObject);
-
- waitExeNode.remove(0);
-
- if(jsonObject.getString("type").equals("判断")){
- //这里存放判断算子的内容,后续替换 //TODO 等产品画完判断算子
- String flag="true";//假设结果是false
- JSONObject content = jsonObject.getJSONObject("content");
- JSONArray sonNames = content.getJSONArray(flag);
- for (int i = 0; i < sonNames.size(); i++) {
- waitExeNode.add(0,sonNames.getString(i));
- }
- }else if(jsonObject.getString("type").equals("循环结束")){//循环开始只是普通算子,只起到标识作用
-
- //次数和条件,具体算子判断,flag由判断结果生成//TODO 循环算子代码编写
-
- String flag="false";//假设结果是fasle
- JSONObject content = jsonObject.getJSONObject("content");
- JSONArray sonNames = content.getJSONArray(flag);
- for (int i = 0; i < sonNames.size(); i++) {
- waitExeNode.add(0,sonNames.getString(i));
- }
- }else{//非修改流程算子---普通算子
- //装入待执行,完美绕过递归(在纸上,画几个节点和一个存储的数组,你就能理解)
- JSONArray sonNames = jsonObject.getJSONArray("next");
- for (int i = 0; i < sonNames.size(); i++) {//不满足父节点条件,要在开头删除(已写了)
-
- /*
- 将他排在下一位(必须,否则导致重复,导致重复的原因是,他有多个父节点,每个父节点
- 指向了他,所以创建了多个,当一个父节点执行完,那么直接执行它,则会进入开头的删除判断,
- 存在其他父节点没有执行完,此节点会被删除,等到最后一个父节点执行后,那么所有父节点
- 现在已经执行完了,那他就可以正常执行了,这样就是这个算子只执行了一次
- 若不放到下一个,则会存在多个节点没有被删除,会造成重复)
- */
- waitExeNode.add(0,sonNames.getString(i));
-
- }
- }
-
-
- }
-
-
-
-
- return new RsJsonBean(0,"测试.");
- }
以及工具类SortTool.java
- //判断某个子节点的父节点是否全部执行完毕
- public static boolean isRun(JSONArray pres,HashMap
name4json) { - for (int i = 0; i < pres.size(); i++) {
- String fatherName = pres.getString(i);
- JSONObject fatherObj = name4json.get(fatherName);
- String status = fatherObj.getString("status");
- if(status.equals("未执行")){
- return false;//终止循环,将子节点(该执行的本节点,移除待执行节点)
- }
- }
- return true;
- }
依赖,主要就一个fastJson
剩下为业务代码,对大家学习任务流没有什么太大帮助,这里就不展示了。
完!