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


    前言

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

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

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

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

    问题

    对于简单的流程

    1
    a -> b -> c

    可以很容易用代码来实现

    1
    2
    3
    4
    5
    6
    // 执行a
    a();
    // 执行b
    b();
    // 执行c
    c();

     

    对于并行的流程

    1
    2
    a -> b
    a -> c

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

    1
    2
    3
    4
    5
    6
    7
    // 执行a
    a();
     
    // a结束后执行b
    new Thread(b).start();
    // a结束后执行c
    new Thread(c).start();

     

    对于分支合并的流程

    1
    2
    3
    4
    a -> b
    a -> c
    b -> d
    c -> d
    程序会变得更加复杂
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 执行a
    a();
     
    // a结束后执行b
    new Thread(b).start();
    // a结束后执行c
    new Thread(c).start();
     
    // 等待b,c结束
    waitComplete(b,c);
     
    // 执行d
    d();
    这个是最常用的业务流程,在实际写程序的时候,一般会避开多线程框架,往往被简单写成:
    1
    2
    3
    4
    a();
    b();
    c();
    d();
    去除了Fork-Join的麻烦,也没有改变业务执行顺序,但和流程图稍有出入。
     
    于是想到能不能有个框架来控制a,b,c,d的运行顺序呢?
    也就我们只需要编写a,b,c,d的单体,执行顺序变成可配置。
     

    调查 

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

    实现

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


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

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

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

    1
    2
    3
    4
    5
    public @interface Node {
     
        String id()     default "" ;
        String label()     default "" ;
    }


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    {
        "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"
            }
        ]
    }

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

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

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

    1
    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。

     

  • 相关阅读:
    pandas iloc和loc
    硬件故障诊断:快速定位问题
    Linux系统:OpenSSH7.4p升级到9.0p(服务器漏洞)
    Linkerd Service Mesh 快速上手
    火山引擎 DataLeap 的 Data Catalog 系统公有云实践
    如何做好分支管理,保证高效CI/CD?
    Hadoop3 - MapReduce Join 关联注意点
    OCX 添加方法和事件 HTML调用ocx函数及回调 ocx又调用dll VS2017
    phpstudy+sql注入靶场环境搭建中的几个坑
    【C++】内联函数讲解和关键字inline纯干货简介
  • 原文地址:https://www.cnblogs.com/nobuglady/p/16423995.html