# mermaid-flow
**Repository Path**: orangewest/mermaid-flow
## Basic Information
- **Project Name**: mermaid-flow
- **Description**: 一款基于mermaid的流程控制框架
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 1
- **Created**: 2024-11-15
- **Last Updated**: 2025-03-03
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# mermaid-flow
一款基于mermaid的流程控制框架
[![Contributors][contributors-shield]][contributors-url]
[![Forks][forks-shield]][forks-url]
[![Stargazers][stars-shield]][stars-url]
[![Issues][issues-shield]][issues-url]
[![MIT License][license-shield]][license-url]
mermaid-flow
一款基于mermaid的流程控制框架
探索本项目的文档 »
查看Demo
·
报告Bug
·
提出新特性
## 一、框架说明
mermaid-flow,顾名思义,就是一款基于mermaid设计的流程框架。
[mermaid](https://mermaid.nodejs.cn/intro/)介绍如下:
> **Mermaid 允许你使用文本和代码创建图表和可视化。**
>
> 它是一个基于 JavaScript 的图表绘制工具,可渲染 Markdown 启发的文本定义以动态创建和修改图表。
>
项目中用到的是mermaid流程图语法,当然也没有那么复杂,只用到部分定义(文本框、菱形框、链接线),程序通过解析绘制的流程图,来根据流程图进行流程控制,所见即所得。
大概实现了以下功能:
1、流程控制(顺序流程、条件流程、循环流程)
2、流程节点动态更新
3、流程执行日志
4、流程运行数据持久化
。。。
## 二、框架使用
### 1、基本使用
定义的流程图flow1.md如下:
```mermaid
flowchart LR
start[开始]
10000{i<100}
11000["exp(x,x+10)"]
12000["i=i+1"]
start --> 10000 --> 11000 --> 12000
12000 --> 10000
```
流程图代码如下:
```plain
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
使用代码如下
```java
@Test
void testFlow1(){
// 创建一个流程工厂实例
FlowFactory flowFactory=new FlowFactory();
// 设置流程图解析器,使用Mermaid文件格式的流程图
flowFactory.setFlowchartParser(new MermaidFileParser(getFilePath("flow1.md")));
// 定义一个整型变量键,用于流程中变量的访问
VariableKey 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 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如下:
```mermaid
flowchart LR
start[开始]
10000["x=x+1"]
11000["y=x+1"]
start --> 10000 --> 11000
```
流程图代码如下:
```plain
flowchart LR
start[开始]
10000["x=x+1"]
11000["y=x+1"]
start --> 10000 --> 11000
```
使用代码:
```java
@Test
void testFlow2(){
FlowFactory flowFactory=new FlowFactory();
flowFactory.setFlowchartParser(new MermaidFileParser(getFilePath("flow2.md")));
VariableKey x=VariableKey.of("x",Integer.class);
VariableKey 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如下:
```mermaid
flowchart LR
start[开始]
10000{x>y}
11000[y=y+1]
12000[y=y+2]
start --> 10000 -- true --> 11000
10000 -- false --> 12000
```
```plain
flowchart LR
start[开始]
10000{x>y}
11000[y=y+1]
12000[y=y+2]
start --> 10000 -- true --> 11000
10000 -- false --> 12000
```
```java
@Test
void testFlow3(){
FlowFactory flowFactory=new FlowFactory();
flowFactory.setFlowchartParser(new MermaidFileParser(getFilePath("flow3.md")));
VariableKey x=VariableKey.of("x",Integer.class);
VariableKey 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
执行结果如下:
```plain
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
```
当然也可以这样使用,在流程连接上直接定义执行条件
```mermaid
flowchart LR
start[开始]
10000{ }
11000[y=y+1]
12000[y=y+2]
start-->10000--"x>y"-->11000
10000--"x<=y"-->12000
```
```plain
flowchart LR
start[开始]
10000{ }
11000[y=y+1]
12000[y=y+2]
start-->10000--"x>y"-->11000
10000--"x<=y"-->12000
```
```java
@Test
void testFlow4(){
FlowFactory flowFactory=new FlowFactory();
flowFactory.setFlowchartParser(new MermaidFileParser(getFilePath("flow4.md")));
VariableKey x=VariableKey.of("x",Integer.class);
VariableKey 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();
}
}
```
或者这样使用
```mermaid
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
```
```plain
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
```
```java
@Test
void testFlow5(){
FlowFactory flowFactory=new FlowFactory();
flowFactory.setFlowchartParser(new MermaidFileParser(getFilePath("flow5.md")));
VariableKey x=VariableKey.of("x",Integer.class);
VariableKey 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为例
```java
@Test
void testFlow4Update(){
FlowFactory flowFactory=new FlowFactory();
flowFactory.setFlowchartParser(new MermaidFileParser(getFilePath("flow4.md")));
VariableKey x=VariableKey.of("x",Integer.class);
VariableKey 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();
}
}
```
执行结果如下:
```plain
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个接口,这里面分别对流程和单个流程节点执行进行监控。
比如,实现一个对当前执行顺序监控
```java
public class FlowExecAop implements FlowAspect {
public final static VariableKey TIME = VariableKey.of("time", Long.class);
public final static VariableKey> EXE_CONTEXT = VariableKey.of("exeContext", new TypeReference>() {
});
@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 fromLocal = flowContext.getFromLocal(EXE_CONTEXT);
String collect = String.join("==>", fromLocal);
System.out.println("flowId:" + flow.getFlowId() + " execute count:" + flowContext.getCount() + " context:" + collect);
}
}
```
```java
public class FlowNodeExecAop implements FlowNodeAspect {
@Override
public void beforeExecute(AbstractFlowNode flowNode, FlowContext flowContext) {
}
@Override
public void afterExecute(AbstractFlowNode flowNode, FlowContext flowContext) {
List context = flowContext.getFromLocal(FlowExecAop.EXE_CONTEXT);
context.add(flowNode.getFlowNodeMeta().getNodeId());
}
}
```
把切面添加进入切面管理类即可
```java
FlowAspectManager.addFlowNodeAspect(new FlowNodeExecAop());
FlowAspectManager.addFlowAspect(new FlowExecAop());
```
#### 5.2、自定义函数
有一些节点可能需要实现复杂的业务逻辑,这个时候可以通过自定义函数实现
接口实现OptFunction即可
```java
public interface OptFunction {
/**
* 获取操作函数的名称。
*
* @return 操作函数的名称。
*/
String getName();
/**
* 执行操作函数。
*
* @param params 执行函数所需的参数数组。
* @param flowContext 流程上下文,包含执行环境的相关信息。
* @return 执行结果,true表示执行成功,false表示执行失败。执行失败后,将不会进入下一个节点。
*/
boolean execute(String[] params, FlowContext flowContext);
}
```
进行函数注册:
```java
OptFunctionFactory.register();
```
[your-project-path]:orangewest/mermaid-flow
[contributors-shield]: https://img.shields.io/github/contributors/orangewest/mermaid-flow.svg?style=flat-square
[contributors-url]: https://gitee.com/orangewest/mermaid-flow/graphs/contributors
[forks-shield]: https://img.shields.io/github/forks/orangewest/mermaid-flow.svg?style=flat-square
[forks-url]: https://gitee.com/orangewest/mermaid-flow/network/members
[stars-shield]: https://img.shields.io/github/stars/orangewest/mermaid-flow.svg?style=flat-square
[stars-url]: https://gitee.com/orangewest/mermaid-flow/stargazers
[issues-shield]: https://img.shields.io/github/issues/orangewest/mermaid-flow.svg?style=flat-square
[issues-url]: https://img.shields.io/github/issues/orangewest/mermaid-flow.svg
[license-shield]: https://img.shields.io/github/license/orangewest/mermaid-flow.svg?style=flat-square
[license-url]: https://gitee.com/orangewest/mermaid-flow/blob/master/LICENSE.txt