Mxgraph结合java后台实现图形化工作流处理

287 阅读3分钟

前言

前面几篇文章分别介绍了前端使用mxGraph拖拽实现自定义流程,然后前端导出为xml文件,后端读取并解析xml,那么这篇文章介绍一下,如何使用java来实现一个轻量级的工作流引擎,使用工作流引擎来执行前端拖拽生成的流程。目前网上有很多开源可靠的工作流引擎,但是普遍偏重量级,有些还需要和idea结合才能使用拖拽实现工作流。

实现思路

1、后端解析xml文件,将xml文件转化为java对象。 2、定义后端节点处理器,每一个xml节点对应一个后端处理节点。 3、使用多线程技术来执行,更具拖拽形成的流程图,相应的实现串行或者并行。 4、定义回调接口实现节点间消息的传递和通知。 5、定义枚举类,将自定义的节点处理类和xml中的节点关联

源码:gitee.com/chengzhi2/j…

代码编写

1、抽象node接口

public interface Node {

    public void run(NodeCallBack callBack);

    public String getName();
}

2、编写一个抽象节点继承Node接口

public abstract class AbstractNode implements Node {

    public MxElement mxElement;

    private Runnable runnable;

    /**
     * 自定义业务参数传递
     */
    public Map<String, Object> map = new ConcurrentHashMap<String, Object>();

    public AbstractNode(MxElement mxElement) {
        this.mxElement = mxElement;
    }

    // 支持动态扩展
    public void run(NodeCallBack callBack) {
        map.put(NodeRunState.RUN_TYPE, NodeRunState.RUN_SUCCESS); // 默认运行状态为成功。
        try {
            verify();
            runNode(callBack);
            map.put(NodeRunState.RUN_TYPE, NodeRunState.RUN_SUCCESS);
            //MessagePushSocketUtils.sendMsg(MessagePushSocketUtils.AUTOUI_MESSAGE, mxCell.getId() + "|" + NodeRunState.NODE_TYPE_SUCCESS, 0);
        } catch (Exception e) {
            e.printStackTrace();
            //MessagePushSocketUtils.sendMsg(MessagePushSocketUtils.AUTOUI_MESSAGE, mxCell.getId() + "|" + NodeRunState.NODE_TYPE_FAIL, 0);
            map.put(NodeRunState.RUN_TYPE, NodeRunState.RUN_FAIL);
        }
        callBack.callBack(map);
        // 每个节点运行完成后默认休眠一段事件
        sleep();
    }

    public abstract void runNode(NodeCallBack callBack) throws Exception;
    /**
     * 检验参数
     */
    public abstract void verify() throws Exception;

    public void sleep() {
        sleep(10000L);
    }

    public void sleep(Long millis) {
        ThreadUtils.doSleep(millis);
    }


}

3、定义回调接口

public interface NodeCallBack {

    public void callBack(Map<String, Object> paramsMap);
}

4、定义多线程处理

public class AutoUiExcutor {

    private static final String NODE_NAME = "nodeName";

    /**
     * 全局锁, 用于控制多个上游节点公用一个下游节点的情况,保证下游节点只运行一次
     */
    private CountDownLatch latch;

    public void start(MxCell mxCell,String folwId) {

        this.run(mxCell, folwId);

    }

    public void run(MxCell mxCell, String folwId) {

        Node node = getNodeInstance(mxCell);
        LogUtils.debug(node.getName() + "即将运行" + Thread.currentThread().getName());
        node.run(new NodeCallBack() {
            @Override
            public void callBack(Map<String,Object> paramsMap) {
                // 执行下一个时先等待一下,防止初始化工作没有完成
                // 1标识成功,2标识执行失败
                if (paramsMap.get(NodeRunState.RUN_TYPE).equals(NodeRunState.RUN_SUCCESS)) {
                    runNext(mxCell.hasNext() ? mxCell.getNextCell() : new ArrayList<MxCell>(), paramsMap, folwId);
                }

            }
        });
    }

    public void runNext(List<MxCell> mxCells, Map<String,Object> paramsMap, String folwId) {

        // 串行
        int size = mxCells.size();
        if (size > 0 && size == 1) {
            MxCell mxCell = mxCells.get(0);
            mxCell.setParams(paramsMap); // 设置自定义参数
            if (!hasManyUpstream(mxCell, folwId)) {
                run(mxCell,folwId);
            }
            return;
        }

        // 多个下游节点需要并行运行
        for (MxCell mxCell : mxCells) {
            mxCell.setParams(paramsMap); // 设置自定义参数
            // 如果节点具有多个上游,要等待所有上游执行完成,再执行,且只执行一次
            if (!hasManyUpstream(mxCell, folwId)) {
                AutoUiThreadPool.getThreadPool(folwId).execute(() -> {
                    run(mxCell, folwId);
                });
            }
        }

    }

    /**
     * 处理节点具有多个上游节点的情况。
     * @param mxCell
     * @param folwId
     * @return
     */
    private boolean hasManyUpstream(MxCell mxCell, String folwId) {
        if (mxCell.hasManyUpstreamMode()) {
            List<Integer> upstreamNode = mxCell.getUpstreamNode();
            LogUtils.debug(mxCell.getName() + "具有多个上游节点,节点个数为:" + upstreamNode.size());
            if (latch == null || latch.getCount() == 0) {
                latch = new CountDownLatch(upstreamNode.size());
            }

            latch.countDown();
            long count = latch.getCount();
            if (count == 0) {
                run(mxCell, folwId);
                // 运行过之后,就要把锁消耗完
                while(latch.getCount() > 0) {
                    latch.countDown();
                }
            }

            return true;
        }

        return false;
    }
    private Node getNodeInstance(MxCell mxCell) {

        String name = mxCell.getMxObjectData().get(NODE_NAME);
        Class<? extends Node> nodeClassByNodeName = NodeEnum.getNodeClassByNodeName(name);
        Node node = null;
        try {
            node = nodeClassByNodeName.getConstructor(MxCell.class).newInstance(mxCell);
        } catch (InstantiationException | InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException | NoSuchMethodException e) {
            e.printStackTrace();
        }

        return node;
    }
}

5、定义枚举

定义枚举的目的是为了将节点处理类和xml中的节点关联

public enum NodeTypeEnum {

    BUTTON("button", ButtonNode.class),
    START("start", StartNode.class),
    CLICK("click", ClickNode.class),
    DBCLICK("dbClick", DbClickNode.class),
    IF("if", JudgeNode.class),
    TEXT("text", TextNode.class),
    ALERT("alert", AlertHandleNode.class),
    SWITCH("switch", SwitchNode.class),
    PRINT("print", PrintNode.class),
    OVER("over", OverNode.class),
    DELAYED("delayed", DelayedNode.class);

    private final String nodeName;
    private final Class<? extends Node> nodeClass;

    NodeTypeEnum(String nodeName, Class<? extends Node> nodeClass) {

        this.nodeName = nodeName;
        this.nodeClass = nodeClass;
    }

    public String getNodeName() {
        return nodeName;
    }

    public Class<? extends Node> getNodeClass() {
        return nodeClass;
    }

    public static Class<? extends Node> getNodeClassByNodeName(String nodeName) {

        NodeTypeEnum nodeTypeEnum = getEnumByNodeName(nodeName);

        return nodeTypeEnum.getNodeClass();
    }

    public static Class<? extends Node> getNodeClassByNode(Node node) {

        String nodeName = node.getName();

        return getNodeClassByNodeName(nodeName);
    }

    public static NodeTypeEnum getEnumByNodeName(String nodeName) {
        for (NodeTypeEnum anEnum : values()) {
            if (anEnum.getNodeName().equals(nodeName)) {
                return anEnum;
            }
        }
        return null;
    }


}

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第7天,点击查看活动详情