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);
       }
    });
}

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

相关推荐
bobz9654 小时前
tcp/ip 中的多路复用
后端
bobz9654 小时前
tls ingress 简单记录
后端
皮皮林5515 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友5 小时前
什么是OpenSSL
后端·安全·程序员
bobz9656 小时前
mcp 直接操作浏览器
后端
前端小张同学8 小时前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook8 小时前
Manim实现闪光轨迹特效
后端·python·动效
武子康9 小时前
大数据-98 Spark 从 DStream 到 Structured Streaming:Spark 实时计算的演进
大数据·后端·spark
该用户已不存在9 小时前
6个值得收藏的.NET ORM 框架
前端·后端·.net
文心快码BaiduComate9 小时前
文心快码入选2025服贸会“数智影响力”先锋案例
前端·后端·程序员