liteflow可视化编排实现

前言

liteflow是一款轻量、快速、稳定可编排的组件式规则引擎,可编排是liteflow非常重要的特性,甚至是为编排而生。目前liteflow官方支持的流程编排还是主要依靠编写el表达式来实现,因此分享一下我实现的liteflow组件可视化编排的思路。

liteflow组件特点

先来看一个简单el表达式的例子:

css 复制代码
THEN(
        a,
        WHEN(b, c, d),
        e
    );

对应的流程图如下:

THENWHEN是liteflow的组件关键字,被THEN包裹的节点会串行执行,被WHEN包裹的节点会并行执行,这个el表达式解析后,先执行a,再并行执行b,c,d,默认并行都执行完毕后,执行e。

在实现通过ui拖拽进行编排时,如果生成像上面的这样的流程图,我们发现其实这个流程图隐藏了一些信息:

  1. 是a执行完后是有一个开启并行执行的动作
  2. b,c,d执行完毕后有个聚合join的动作

这些信息在简单的流程场景下不是必要的,因为可以通过流程图的节点连接信息进行推导,但是在有大量嵌套的并行组件之后,想要把这信息正确推导出来就会比较复杂,例如下面这个例子: 这张图来自gitee上一个项目的issues

完善编排流程图

接下来我们在流程图中把el表达式中的信息都展示出来:

引入了两个图形用于表示WHEN并行组件的入口和出口,这样无论有多少嵌套的并行组件都能很容易的解析出来并转换成el表达式。

可能有些同学会觉得对于使用者来说无需关心WHEN并行组件的入口和出口,觉得这在图形上展示是多余的,其实不然,因为显式的标注出WHEN并行组件的入口和出口在多层嵌套的场景下,会让各个组件之间的边界更加清晰,其次当我们使用并行组件的时候,我们需要对并行组件根据业务要求进行配置,这在liteflow里也是支持的:

  • 忽略错误
  • 任一节点先执行完则忽略其他
  • 指定任意节点先执行完则忽略其他

UI交互

前面我们说到liteflow的组件是必须闭合的,因此我们可以在ui操作上进行约束,当添加并行组件时,直接在流程图中把并行组件的出口、入口以及默认分支全部创建好:

对UI操作约束能够让使用的人不会因为不熟悉liteflow的组件特点而导致生成不适配liteflow的编排流程图,而且操作更加高效。

代码实现

这里前端以logicflow为例,前端通过UI编排好组件之后调用getGraphData()获取logicflow的流程绘图数据如下:

json 复制代码
{
	"nodes": [{
		"id": "init_6d5b8qfcwl00000",
		"type": "EventNode",
		"properties": {
			"name": "页面初始化"
		}
	}, {
		"id": "0",
		"type": "ParallelGateway",
		"properties": {
			"name": "并行组件0",
			"joinNode": "3"
		}
	}, {
		"id": "1",
		"type": "CommonNode",
		"properties": {
			"name": "1"
		}
	}, {
		"id": "3",
		"type": "JoinGateway",
		"properties": {
			"name": "join3"
		}
	}, {
		"id": "2",
		"type": "CommonNode",
		"properties": {
			"name": "2"
		}
	}],
	"edges": [{
		"sourceNodeId": "1",
		"targetNodeId": "3"
	}, {
		"sourceNodeId": "2",
		"targetNodeId": "3"
	}, {
		"sourceNodeId": "0",
		"targetNodeId": "1"
	}, {
		"sourceNodeId": "0",
		"targetNodeId": "2"
	}, {
		"sourceNodeId": "init_6d5b8qfcwl00000",
		"targetNodeId": "0"
	}]
}

这里我把数据精简了,只留下必要的数据,之后就是要将前端绘图数据转换成liteflow能够使用的数据。

通过debug liteflow源码,知道el表达式的解析之后生成的内存数据结构如下:

了解了liteflow内存中编排数据的结构,我们可以直接解析前端的绘图数据来生成liteflow需要的流程数据,而不需要先生成liteflow的el表达式,再让liteflow解析一遍。

liteflow使用LiteFlowChainELBuilder类来解析el表达式、组装规则链,我们可以模仿创建一个LiteFlowChainLogicFlowBuilder来解析前端生成的编排绘图数据,再组装生成liteflow能够使用的规则链。

对并行组件的处理:

java 复制代码
// 前端绘图数据当前节点为when组件时
private void whenCondition(Condition parentCondition, LogicFlowGraphData.Node node) {
    WhenCondition whenCondition = new WhenCondition();
    whenCondition.setParallelStrategy(ParallelStrategyEnum.ALL);
    parentCondition.addExecutable(whenCondition);
    // 1.获取并行组件的所有分支第一个节点,每个分支使用ThenCondition组件包裹
    List<LogicFlowGraphData.Node> outgoingNodes = this.getNodeOutgoingNodes(node.id);
    outgoingNodes.forEach(outgoingNode -> {
       ThenCondition thenCondition = this.createThenCondition(whenCondition);
       this.commonNode(thenCondition, outgoingNode);
        });
    // 2.获取并行组件的join节点,再处理join节点的后续节点数据
    LogicFlowGraphData.Node joinNode = this.nodeMap.get(node.properties.get("joinNode"));
    List<LogicFlowGraphData.Node> joinNodeOutgoingNodes = this.getNodeOutgoingNodes(joinNode.id);
    joinNodeOutgoingNodes.forEach(outgoingNode -> {
       if (outgoingNode.type.equals("ParallelGateway")) {
          this.whenCondition(parentCondition, outgoingNode);
       } else if (outgoingNode.type.equals("JoinGateway")) {
          // TODO 处理并行组件配置数据
       } else {
          ThenCondition thenCondition = this.createThenCondition(parentCondition);
          this.commonNode(thenCondition, outgoingNode);
       }
    });
}

对普通任务节点的处理:

java 复制代码
// 前端绘图数据当前节点为普通任务节点
private void commonNode(Condition parentCondition, LogicFlowGraphData.Node node) {
    // 创建普通任务节点,配置节点的名称、id等数据,也可能是脚本节点,处理方式基本一致
    NodeComponent nodeComponent = new NodeComponent() {
       @Override
       public void process() throws Exception {
          System.out.println(node.properties.get("name"));
       }
    };
    nodeComponent.setSelf(nodeComponent);
    Node liteFlowNode = new Node(nodeComponent);
    parentCondition.addExecutable(liteFlowNode);
    // 处理后续节点
    List<LogicFlowGraphData.Node> outgoingNodes = this.getNodeOutgoingNodes(node.id);
    outgoingNodes.forEach(outgoingNode -> {
       if (outgoingNode.type.equals("ParallelGateway")) {
          this.whenCondition(parentCondition, outgoingNode);
       } else if (outgoingNode.type.equals("JoinGateway")) {
          // TODO 处理并行组件配置数据
       } else {
          this.commonNode(parentCondition, outgoingNode);
       }
    });
}

主要介绍这两个节点的解析,其他选择组件、条件组件与并行组件处理方式基本一致。

相关推荐
XiaoLeisj1 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck1 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei1 小时前
java的类加载机制的学习
java·学习
码农小旋风3 小时前
详解K8S--声明式API
后端
Peter_chq3 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
Yaml43 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~3 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616883 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
aloha_7894 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
记录成长java4 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet