目录
初版
需求,给定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<String, JSONObject> nameJsonObject = new HashMap<String, JSONObject>();
for (int i = 0; i <jsonArray.size() ; i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
String name = jsonObject.getString("name");//name前端必须传过来,而且还是唯一的
nameJsonObject.put(name,jsonObject);
}
HashMap<Integer, JSONObject> numAction = new HashMap<Integer, JSONObject>();//定义最终顺序
HashMap<String, String> preExecuteName = new HashMap<String, String>();//定义已经编过号的节点,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<Void>(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<String, JSONObject> nameJsonObject,
HashMap<String, String> 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<String, JSONObject> name4json = new HashMap<>();//名字for杰森对象
ArrayList<String> 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<String, JSONObject> 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
剩下为业务代码,对大家学习任务流没有什么太大帮助,这里就不展示了。
完!