本篇文章是作者开源的第二个框架
一、背景
为啥会有这个项目呢?作者是做物联网相关的,会有一些智能调控的需求,大概场景就是会通过设备上传的数据,执行一些算法逻辑,然后去下发调控设备。而这些算法逻辑,一般就是根据工艺人员给的数据调控的流程图去写代码实现。
直接写代码会有一些问题:
1、各种if else 横行,其他人员根本不知道代码的业务逻辑;
2、工艺人员可能会修改相关的流程顺序,这样我们就必须得修改相关代码;
3、无法很直观的监控当前数据的执行情况
因此,就想着看看能不能找一款流程控制框架,来动态控制数据计算流程。找了一些市面的流程框架,都无法满足当前业务场景。比如,工艺人员可以自己直接更改流程相关参数, 不需要研发介入。比如流程中的业务数据是随时流程的不断执行而不断动态变化的,市面上的框架基本流程基本都是一次性的,每次执行流程都是新的流程,流程中运行的数据没有保存。因此,针对这样的业务场景情况,开发了一款流程控制引擎。
二、框架介绍
mermaid-flow,顾名思义,就是一款基于mermaid设计的流程框架。
mermaid介绍如下:
Mermaid 允许你使用文本和代码创建图表和可视化。
它是一个基于 JavaScript 的图表绘制工具,可渲染 Markdown 启发的文本定义以动态创建和修改图表。
项目中用到的是mermaid流程图语法,当然也没有那么复杂,只用到部分定义(文本框、菱形框、链接线),程序通过解析绘制的流程图,来根据流程图进行流程控制,所见即所得。
大概实现了以下功能:
1、流程控制(顺序流程、条件流程、循环流程)
2、流程节点动态更新
3、流程执行日志
4、流程运行数据持久化
。。。
三、框架使用
1、基本使用
定义的流程图flow1.md如下:
flowchart LR
start[开始]
10000{i<100}
11000["exp(x,x+10)"]
12000["i=i+1"]
start --> 10000 --> 11000 --> 12000
12000 --> 10000
流程图代码如下:
flowchart LR
start[开始]
10000{i<100}
11000["exp(x,x+10)"]
12000["i=i+1"]
start --> 10000 --> 11000 --> 12000
12000 --> 10000
该流程图主要功能就是循环增加x的值。
其中,流程图中必须仅有一个start节点,作为流程起始节点。
exp(x,x+10) 是系统内置的一个函数,就是把x+10的值赋值给x
当然我们也可以用简写形式,比如i=i+1
使用代码如下
@Test
void testFlow1(){
// 创建一个流程工厂实例
FlowFactory flowFactory=new FlowFactory();
// 设置流程图解析器,使用Mermaid文件格式的流程图
flowFactory.setFlowchartParser(new MermaidFileParser(getFilePath("flow1.md")));
// 定义一个整型变量键,用于流程中变量的访问
VariableKey<Integer> x=VariableKey.of("x",Integer.class);
// 初始化全局流程变量x为0
flowFactory.initVariable(x,0);
// 获取或创建一个名为"flow"的流程实例
Flow flow=flowFactory.getOrCreateFlow("flow");
// 获取流程的上下文,用于存储和访问流程执行过程中的变量
FlowContext flowContext=flow.getFlowContext();
// 循环10次,每次执行流程,并验证流程的执行结果
for(int j=1;j<=10;j++){
// 定义一个局部变量键i,用于循环中的临时变量
VariableKey<Integer> i=VariableKey.of("i",Integer.class);
// 在流程上下文中设置局部变量i为0
flowContext.putLocal(i,0);
// 启动流程执行
flow.start();
// 验证流程执行次数是否与预期相符
Assertions.assertEquals(j,flowContext.getCount());
// 验证流程中变量x的值是否与预期相符
Assertions.assertEquals(j*1000,flowContext.get(x));
}
}
流程图里面的变量分为局部变量和全局变量。
局部变量:执行完一次流程就会清空。
全局变量:执行销毁流程的方法就会清空。
2、顺序流程
定义的流程图flow2.md如下:
flowchart LR
start[开始]
10000["x=x+1"]
11000["y=x+1"]
start --> 10000 --> 11000
流程图代码如下:
flowchart LR
start[开始]
10000["x=x+1"]
11000["y=x+1"]
start --> 10000 --> 11000
使用代码:
@Test
void testFlow2(){
FlowFactory flowFactory=new FlowFactory();
flowFactory.setFlowchartParser(new MermaidFileParser(getFilePath("flow2.md")));
VariableKey<Integer> x=VariableKey.of("x",Integer.class);
VariableKey<Integer> y=VariableKey.of("y",Integer.class);
flowFactory.initVariable(x,0);
flowFactory.initVariable(y,0);
Flow flow=flowFactory.getOrCreateFlow("flow");
flow.start();
FlowContext flowContext=flow.getFlowContext();
Assertions.assertEquals(1,flowContext.get(x));
Assertions.assertEquals(2,flowContext.get(y));
}
3、条件控制
定义的流程图flow3.md如下:
flowchart LR
start[开始]
10000{x>y}
11000[y=y+1]
12000[y=y+2]
start --> 10000 -- true --> 11000
10000 -- false --> 12000
flowchart LR
start[开始]
10000{x>y}
11000[y=y+1]
12000[y=y+2]
start --> 10000 -- true --> 11000
10000 -- false --> 12000
@Test
void testFlow3(){
FlowFactory flowFactory=new FlowFactory();
flowFactory.setFlowchartParser(new MermaidFileParser(getFilePath("flow3.md")));
VariableKey<Integer> x=VariableKey.of("x",Integer.class);
VariableKey<Integer> y=VariableKey.of("y",Integer.class);
flowFactory.initVariable(x,5);
flowFactory.initVariable(y,0);
Flow flow=flowFactory.getOrCreateFlow("flow");
FlowContext flowContext=flow.getFlowContext();
for(int i=0;i< 10;i++){
System.out.println("x:"+flowContext.get(x)+" y:"+flowContext.get(y));
flow.start();
}
}
start-->10000--true-->11000 等同于 start-->10000-->11000
执行结果如下:
x:5 y:0
flowId:flow execute time:27
flowId:flow execute count:1 context:start==>10000==>11000
x:5 y:1
flowId:flow execute time:0
flowId:flow execute count:2 context:start==>10000==>11000
x:5 y:2
flowId:flow execute time:0
flowId:flow execute count:3 context:start==>10000==>11000
x:5 y:3
flowId:flow execute time:2
flowId:flow execute count:4 context:start==>10000==>11000
x:5 y:4
flowId:flow execute time:0
flowId:flow execute count:5 context:start==>10000==>11000
x:5 y:5
flowId:flow execute time:0
flowId:flow execute count:6 context:start==>10000==>12000
x:5 y:7
flowId:flow execute time:1
flowId:flow execute count:7 context:start==>10000==>12000
x:5 y:9
flowId:flow execute time:0
flowId:flow execute count:8 context:start==>10000==>12000
x:5 y:11
flowId:flow execute time:0
flowId:flow execute count:9 context:start==>10000==>12000
x:5 y:13
flowId:flow execute time:0
flowId:flow execute count:10 context:start==>10000==>12000
当然也可以这样使用,在流程连接上直接定义执行条件
flowchart LR
start[开始]
10000{ }
11000[y=y+1]
12000[y=y+2]
start-->10000--"x>y"-->11000
10000--"x<=y"-->12000
flowchart LR
start[开始]
10000{ }
11000[y=y+1]
12000[y=y+2]
start-->10000--"x>y"-->11000
10000--"x<=y"-->12000
@Test
void testFlow4(){
FlowFactory flowFactory=new FlowFactory();
flowFactory.setFlowchartParser(new MermaidFileParser(getFilePath("flow4.md")));
VariableKey<Integer> x=VariableKey.of("x",Integer.class);
VariableKey<Integer> y=VariableKey.of("y",Integer.class);
flowFactory.initVariable(x,5);
flowFactory.initVariable(y,0);
Flow flow=flowFactory.getOrCreateFlow("flow");
FlowContext flowContext=flow.getFlowContext();
for(int i=0;i< 10;i++){
System.out.println("x:"+flowContext.get(x)+" y:"+flowContext.get(y));
flow.start();
}
}
或者这样使用
flowchart LR
start[开始]
10000{x+y}
11000[y=y+1]
12000[y=y+2]
13000[x=x+1]
start --> 13000
start-->10000--"5"-->11000-->13000
10000--"10"-->12000-->13000
flowchart LR
start[开始]
10000{x+y}
11000[y=y+1]
12000[y=y+2]
13000[x=x+1]
start-->13000
start-->10000--5-->11000-->13000
10000--10-->12000-->13000
@Test
void testFlow5(){
FlowFactory flowFactory=new FlowFactory();
flowFactory.setFlowchartParser(new MermaidFileParser(getFilePath("flow5.md")));
VariableKey<Integer> x=VariableKey.of("x",Integer.class);
VariableKey<Integer> y=VariableKey.of("y",Integer.class);
flowFactory.initVariable(x,1);
flowFactory.initVariable(y,0);
Flow flow=flowFactory.getOrCreateFlow("flow");
FlowContext flowContext=flow.getFlowContext();
for(int i=0;i< 10;i++){
System.out.println("x:"+flowContext.get(x)+" y:"+flowContext.get(y));
flow.start();
}
}
条件里面比较等值
4、流程更新
我们可以动态更新流程节点,以上面的流程flow4.md为例
@Test
void testFlow4Update(){
FlowFactory flowFactory=new FlowFactory();
flowFactory.setFlowchartParser(new MermaidFileParser(getFilePath("flow4.md")));
VariableKey<Integer> x=VariableKey.of("x",Integer.class);
VariableKey<Integer> y=VariableKey.of("y",Integer.class);
flowFactory.initVariable(x,5);
flowFactory.initVariable(y,0);
Flow flow=flowFactory.getOrCreateFlow("flow");
FlowContext flowContext=flow.getFlowContext();
for(int i=0;i< 10;i++){
if(i==6){
flow.updateNodeContent("12000","x=x+5");
}
if(i==8){
flow.updateNodeLink("10000","12000","y>5");
}
System.out.println("x:"+flowContext.get(x)+" y:"+flowContext.get(y));
flow.start();
}
}
执行结果如下:
x:5 y:0
flowId:flow execute time:20
flowId:flow execute count:1 context:start==>10000==>11000
x:5 y:1
flowId:flow execute time:0
flowId:flow execute count:2 context:start==>10000==>11000
x:5 y:2
flowId:flow execute time:0
flowId:flow execute count:3 context:start==>10000==>11000
x:5 y:3
flowId:flow execute time:0
flowId:flow execute count:4 context:start==>10000==>11000
x:5 y:4
flowId:flow execute time:0
flowId:flow execute count:5 context:start==>10000==>11000
x:5 y:5
flowId:flow execute time:1
flowId:flow execute count:6 context:start==>10000==>12000
x:5 y:7
flowId:flow execute time:0
flowId:flow execute count:7 context:start==>10000==>12000
x:10 y:7
flowId:flow execute time:1
flowId:flow execute count:8 context:start==>10000==>11000
x:10 y:8
flowId:flow execute time:0
flowId:flow execute count:9 context:start==>10000==>11000==>12000
x:15 y:9
flowId:flow execute time:0
flowId:flow execute count:10 context:start==>10000==>11000==>12000
5、流程拓展
5.1、监控流程执行情况
实现FlowAspect 、FlowNodeAspect 这2个接口,这里面分别对流程和单个流程节点执行进行监控。
比如,实现一个对当前执行顺序监控
public class FlowExecAop implements FlowAspect {
public final static VariableKey<Long> TIME = VariableKey.of("time", Long.class);
public final static VariableKey<List<String>> EXE_CONTEXT = VariableKey.of("exeContext", new TypeReference<List<String>>() {
});
@Override
public void beforeStart(Flow flow, FlowContext flowContext) {
flowContext.putLocal(TIME, System.currentTimeMillis());
flowContext.putLocal(EXE_CONTEXT, new ArrayList<>());
}
@Override
public void afterStart(Flow flow, FlowContext flowContext) {
long time = System.currentTimeMillis() - flowContext.getFromLocal(TIME);
System.out.println("flowId:" + flow.getFlowId() + " execute time:" + time);
List<String> fromLocal = flowContext.getFromLocal(EXE_CONTEXT);
String collect = String.join("==>", fromLocal);
System.out.println("flowId:" + flow.getFlowId() + " execute count:" + flowContext.getCount() + " context:" + collect);
}
}
public class FlowNodeExecAop implements FlowNodeAspect {
@Override
public void beforeExecute(AbstractFlowNode flowNode, FlowContext flowContext) {
}
@Override
public void afterExecute(AbstractFlowNode flowNode, FlowContext flowContext) {
List<String> context = flowContext.getFromLocal(FlowExecAop.EXE_CONTEXT);
context.add(flowNode.getFlowNodeMeta().getNodeId());
}
}
把切面添加进入切面管理类即可
FlowAspectManager.addFlowNodeAspect(new FlowNodeExecAop());
FlowAspectManager.addFlowAspect(new FlowExecAop());
5.2、自定义函数
有一些节点可能需要实现复杂的业务逻辑,这个时候可以通过自定义函数实现
接口实现OptFunction即可
public interface OptFunction {
/**
* 获取操作函数的名称。
*
* @return 操作函数的名称。
*/
String getName();
/**
* 执行操作函数。
*
* @param params 执行函数所需的参数数组。
* @param flowContext 流程上下文,包含执行环境的相关信息。
* @return 执行结果,true表示执行成功,false表示执行失败。执行失败后,将不会进入下一个节点。
*/
boolean execute(String[] params, FlowContext flowContext);
}
进行函数注册:
OptFunctionFactory.register();