Fork me on GitHub

业务流程可视化-让你的流程图"Run"起来

前言

最近在研究业务可视化的问题,在日常的工作中,流程图和代码往往是分开管理的。

一个被维护多次的系统,到最后流程图和代码是否匹配这个都很难说。

于是一直有一个想法,让程序直接读流程图,根据流程图的配置来决定程序运行的顺序。

一转眼三年过去了,目前这个想法已经逐步落地实现变成代码。

问题

对于简单的流程

a -> b -> c

可以很容易用代码来实现

// 执行a
a();
// 执行b
b();
// 执行c
c();

 

对于并行的流程

a -> b
a -> c

这个就要多线程框架来实现

// 执行a
a();

// a结束后执行b
new Thread(b).start();
// a结束后执行c
new Thread(c).start();

 

对于分支合并的流程

a -> b
a -> c
b -> d
c -> d
程序会变得更加复杂
// 执行a
a();

// a结束后执行b
new Thread(b).start();
// a结束后执行c
new Thread(c).start();

// 等待b,c结束
waitComplete(b,c);

// 执行d
d();
这个是最常用的业务流程,在实际写程序的时候,一般会避开多线程框架,往往被简单写成:
a();
b();
c();
d();
去除了Fork-Join的麻烦,也没有改变业务执行顺序,但和流程图稍有出入。
 
于是想到能不能有个框架来控制a,b,c,d的运行顺序呢?
也就我们只需要编写a,b,c,d的单体,执行顺序变成可配置。
 

调查 

于是想到各种工作流框架和job执行框架可以满足这个需求,但是太重了。
为了简单的需求,引入庞大的工作流或者job执行引擎,无疑是每个项目都不能接受的。
 
于是,决定手写一个轻量的,即可以控制程序执行流程,又可以通过图形界面编辑程序流程的框架。
 

实现

首先要有一个绘制流程图的界面。并且能够将流程图转化为json格式。
这里我选择了Vis.js的network。
可以编辑简单流程,如下


还可以实现流程图和json之间的互转。

我们把这些节点的基本信息拿到,就可以得到一张图,然后通过程序遍历这张图的每个节点,即可达到运行流程图的效果。

接下来就是流程图的节点与Java的方法绑定了。
我做了一个Annotation来绑定流程图节点,

public @interface Node {

    String id()      default "" ;
    String label()      default "" ;
}


节点得到运行开始事件后,拿到要运行的节点ID和名称,查找对应的类的Annotation对应的方法,如找到则运行该方法。

public int execute(String flowId, String nodeId, String historyId, HistoryNodeEntity nodeEntity)       throws Exception{

  String nodeName = nodeEntity.getNodeName();
  System.out.println(      "execute:" + nodeId);
  System.out.println(      "node name:" + nodeEntity.getNodeName());

  Method methods[] =       this .getClass().getMethods();
  if (methods !=       null ) {
    for (Method method:methods) {
      Node node = method.getAnnotation(Node.      class );
      if (node !=       null ) {
        if (node.id().equals(nodeId) || node.label().equals(nodeName)) {
          try {
            method.invoke(      this );
            return 0 ;
          }       catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            e.printStackTrace();
            throw e;
          }
        }
      }
    }
  }
  return 0 ;
}

 

使用方法

我们需要做一个继承自FlowRunner的类,里面的方法和flow的节点绑定,和一个flow的配置文件,放在相同的目录下。

MyFlow1.java

public class MyFlow1      extends FlowRunner {

    @Node (label=     "a" )
    public void process_a() {
        System.out.println(     "processing a" );
    }

    @Node (label=     "b" )
    public void process_b() {
        System.out.println(     "processing b" );
    }

    @Node (label=     "c" )
    public void process_c() {
        System.out.println(     "processing c" );
    }

    @Node (label=     "d" )
    public void process_c() {
        System.out.println(     "processing d" );
    }
}

MyFlow1.json

{
    "flowId" :       "123" ,
    "nodes" : [
        {
            "id" :       "1" ,
            "label" :       "start"
        },
        {
            "id" :       "2" ,
            "label" :       "a"
        },
        {
            "id" :       "0b5ba9df-b6c7-4752-94e2-debb6104015c" ,
            "label" :       "b"
        },
        {
            "id" :       "29bc32c7-acd8-4893-9410-e9895da38b2e" ,
            "label" :       "c"
        }
    ],
    "edges" : [
        {
            "id" :       "1" ,
            "from" :       "1" ,
            "to" :       "2" ,
            "arrows" :       "to"
        },
        {
            "id" :       "078ffa82-5eff-4d33-974b-53890f2c9a18" ,
            "from" :       "1" ,
            "to" :       "0b5ba9df-b6c7-4752-94e2-debb6104015c" ,
            "arrows" :       "to"
        },
        {
            "id" :       "90663193-7077-4aca-9011-55bc8745403f" ,
            "from" :       "2" ,
            "to" :       "29bc32c7-acd8-4893-9410-e9895da38b2e" ,
            "arrows" :       "to"
        },
        {
            "id" :       "a6882e25-c07a-4abd-907e-e269d4eda0ec" ,
            "from" :       "0b5ba9df-b6c7-4752-94e2-debb6104015c" ,
            "to" :       "29bc32c7-acd8-4893-9410-e9895da38b2e" ,
            "arrows" :       "to"
        }
    ]
}

然后通过下面的代码来启动流程。

MyFlow1 myFlow1 =     new MyFlow1();
myFlow1.startFlow();

系统关闭时,通过下面的代码关闭流程管理器

FlowStarter.shutdown();

运行

正常结束日志如下

Ready queue thread started.
Complete queue thread started.
json:
{"flowId":"123","nodes":[{"id":"1","label":"a"},{"id":"2","label":"b"},{"id":"0b5ba9df-b6c7-4752-94e2-debb6104015c","label":"c"},{"id":"29bc32c7-acd8-4893-9410-e9895da38b2e","label":"d"}],"edges":[{"id":"1","from":"1","to":"2","arrows":"to"},{"id":"078ffa82-5eff-4d33-974b-53890f2c9a18","from":"1","to":"0b5ba9df-b6c7-4752-94e2-debb6104015c","arrows":"to"},{"id":"90663193-7077-4aca-9011-55bc8745403f","from":"2","to":"29bc32c7-acd8-4893-9410-e9895da38b2e","arrows":"to"},{"id":"a6882e25-c07a-4abd-907e-e269d4eda0ec","from":"0b5ba9df-b6c7-4752-94e2-debb6104015c","to":"29bc32c7-acd8-4893-9410-e9895da38b2e","arrows":"to"}]}
execute:1
node name:a
processing a
execute:2
node name:b
processing b
execute:0b5ba9df-b6c7-4752-94e2-debb6104015c
node name:c
processing c
execute:29bc32c7-acd8-4893-9410-e9895da38b2e
node name:d
processing d
Complete success.
json:
{"nodes":[{"id": "1","label": "a" ,"color": "#36AE7C"},{"id": "2","label": "b" ,"color": "#36AE7C"},{"id": "0b5ba9df-b6c7-4752-94e2-debb6104015c","label": "c" ,"color": "#36AE7C"},{"id": "29bc32c7-acd8-4893-9410-e9895da38b2e","label": "d" ,"color": "#36AE7C"}],"edges":[{"id": "1","from": "1","to": "2","arrows": "to"},{"id": "078ffa82-5eff-4d33-974b-53890f2c9a18","from": "1","to": "0b5ba9df-b6c7-4752-94e2-debb6104015c","arrows": "to"},{"id": "90663193-7077-4aca-9011-55bc8745403f","from": "2","to": "29bc32c7-acd8-4893-9410-e9895da38b2e","arrows": "to"},{"id": "a6882e25-c07a-4abd-907e-e269d4eda0ec","from": "0b5ba9df-b6c7-4752-94e2-debb6104015c","to": "29bc32c7-acd8-4893-9410-e9895da38b2e","arrows": "to"}]}

流程执行结束后,会输出执行结果和运行后的流程图状态。
可以直接将json贴到下面的位置,查看看结果(绿色表示正常结束,红色表示异常结束,白色表示等待执行)。

异常结束日志如下

Ready queue thread started.
Complete queue thread started.
json:
{"flowId":"123","nodes":[{"id":"1","label":"a"},{"id":"2","label":"b"},{"id":"0b5ba9df-b6c7-4752-94e2-debb6104015c","label":"c"},{"id":"29bc32c7-acd8-4893-9410-e9895da38b2e","label":"d"}],"edges":[{"id":"1","from":"1","to":"2","arrows":"to"},{"id":"078ffa82-5eff-4d33-974b-53890f2c9a18","from":"1","to":"0b5ba9df-b6c7-4752-94e2-debb6104015c","arrows":"to"},{"id":"90663193-7077-4aca-9011-55bc8745403f","from":"2","to":"29bc32c7-acd8-4893-9410-e9895da38b2e","arrows":"to"},{"id":"a6882e25-c07a-4abd-907e-e269d4eda0ec","from":"0b5ba9df-b6c7-4752-94e2-debb6104015c","to":"29bc32c7-acd8-4893-9410-e9895da38b2e","arrows":"to"}]}
execute:1
node name:a
processing a
execute:2
node name:b
processing b
execute:0b5ba9df-b6c7-4752-94e2-debb6104015c
node name:c
processing c
java.lang.reflect.InvocationTargetException
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at io.github.nobuglady.network.fw.FlowRunner.execute(FlowRunner.java:49)
	at io.github.nobuglady.network.fw.executor.NodeRunner.run(NodeRunner.java:93)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.RuntimeException: test
	at io.github.nobuglady.network.MyFlow1.process_b(MyFlow1.java:16)
	... 11 more
java.lang.reflect.InvocationTargetException
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at io.github.nobuglady.network.fw.FlowRunner.execute(FlowRunner.java:49)
	at io.github.nobuglady.network.fw.executor.NodeRunner.run(NodeRunner.java:93)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.RuntimeException: test
	at io.github.nobuglady.network.MyFlow1.process_b(MyFlow1.java:16)
	... 11 more
Complete error.
json:
{"nodes":[{"id": "1","label": "a" ,"color": "#36AE7C"},{"id": "2","label": "b" ,"color": "#EB5353"},{"id": "0b5ba9df-b6c7-4752-94e2-debb6104015c","label": "c" ,"color": "#36AE7C"},{"id": "29bc32c7-acd8-4893-9410-e9895da38b2e","label": "d" ,"color": "#E8F9FD"}],"edges":[{"id": "1","from": "1","to": "2","arrows": "to"},{"id": "078ffa82-5eff-4d33-974b-53890f2c9a18","from": "1","to": "0b5ba9df-b6c7-4752-94e2-debb6104015c","arrows": "to"},{"id": "90663193-7077-4aca-9011-55bc8745403f","from": "2","to": "29bc32c7-acd8-4893-9410-e9895da38b2e","arrows": "to"},{"id": "a6882e25-c07a-4abd-907e-e269d4eda0ec","from": "0b5ba9df-b6c7-4752-94e2-debb6104015c","to": "29bc32c7-acd8-4893-9410-e9895da38b2e","arrows": "to"}]}

 

流程执行结束后,会输出执行结果和运行后的流程图状态。
可以直接将json贴到下面的位置,查看看结果(绿色表示正常结束,红色表示异常结束,白色表示等待执行)。

源码:https://github.com/nobuglady/ladybugflow

感谢阅读。欢迎Star。

 

posted @ 2022-07-01 21:13  nobuglady  阅读(1459)  评论(9编辑  收藏  举报