業務視覺化-讓你的流程圖"Run"起來

2022-07-02 06:01:05

前言

最近在研究業務視覺化的問題,在日常的工作中,流程圖和程式碼往往是分開管理的。

一個被維護多次的系統,到最後流程圖和程式碼是否匹配這個都很難說。

於是一直有一個想法,讓程式直接讀流程圖,根據流程圖的設定來決定程式執行的順序。

一轉眼三年過去了,目前這個想法已經逐步落地實現變成程式碼。

問題

對於簡單的流程

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/nobuglady-network

感謝閱讀。歡迎Star。