一个系列搞懂自研工作流(二)--流程定义解析

2,796 阅读3分钟

写在前面

要想自研工作流,先得要考虑好整体的模型再进行设计。在此不再介绍概念性问题,如什么是任务节点之类的。重点介绍如何抽象设计模型,并最终落地,希望能给大家一些启示,弥补市面上这类文章的缺失。阅读本文钱也可以先查看专栏中之前的文章进行简单了解 # 一个系列搞懂自研工作流(一)流程定义,了解流程定义如何抽象

模型介绍

简单的自研工作流,需要明白这么几个问题。

  • 如何进行流程定义描述?

通过json/xml的方式描述对象关系,后续会有实例,一看便知。

  • 如何将流程定义转成对象?

将json进行对象实例化,这个过程就有点像spring通过xml配置创建bean,只是过程更加简单。

  • 如何存放流程对象?

像spring管理bean一样,我们也将解析完的流程对象放在类似于容器(执行对象)中进行管理,在流程执行环节中需要的时候拿出来。

流程定义解析

描述

{"id":"process_askForLeave","templateName":"请假流程","nodes":[{"id":"start","name":"开始","type":"start"},{"id":"leader_audit","name":"主管审批","type":"task","candidate":{"userIds":["11","U5B785B5AA1005919840278298192656"]}},{"id":"manager_audit","name":"经理审批","type":"task","candidate":{"userIds":["11","U5B785B5AA1005919840278298192656"]}},{"id":"end","name":"结束","type":"end"}],"lines":[{"to":"leader_audit","id":"start-leader_audit","from":"start","name":"发起流程"},{"to":"manager_audit","id":"leader_audit_manager_audit","from":"leader_audit","name":"主管审批通过"},{"to":"end","id":"askForLeaveApplyResult_end","from":"manager_audit","name":"经理审批通过"}]}

image.png

掘金中不知道如何设置json格式方便观看,大家将就看下扁平化的json(如果有大神知道的麻烦留言告知一下)。以上的json描述的就是这样一个流程定义。

主要的就是基本元素:id、templateName、nodes(节点list)、lines(连线list)。

流程定义解析

下面是定义的流程模型对象,用于存放解析后的数据,

public class ProcessModel {

    /**
     * 模板id
     */
    private Long templateId;

    /**
     * 开始节点
     */
    private NodeModel startModel;

    /**
     * 节点列表
     */
    private List<NodeModel> nodes = new ArrayList<>();


    /**
     * 节点map列表
     */
    private Map<String, NodeModel> nodeMap;
}
  • startModel 开始节点,用于流程启动时快速找到启动的节点来执行。
  • nodes 解析后的所有节点list,用list存储记录偏平化的节点数据
  • nodeMap 解析后的所有节点map,方便后续用节点id直接获取对应的节点对象。

没有设计lines记录所有连线对象的list,因为到目前为止没有需求需要直接获取连线对象,在节点node中有记录进出线对象list,可供操作。后续如果需要可以在解析的时候进行设计。

解析层代码

整体思路

  1. 取nodes先new出节点的基本属性
  2. 取出lines,new对象并装配NodeModel的source和target
  3. 然后将node节点中的进出线line对象列表注入
  4. 最后实例化ProcessModel对象结束。

话不多说上解析代码

public static ProcessModel parse(String json) {
    ProcessTemplateDTO processTemplateDTO = JSON.parseObject(json, ProcessTemplateDTO.class);
    List<NodeJson> nodeList = processTemplateDTO.getNodes();
    List<LineJson> lineList = processTemplateDTO.getLines();

    // 初始化node. 部分属性后续再注入,因为需要先初始化line对象,类似于spring中的循环依赖,部分属性延迟加载
    List<NodeModel> nodeModelList = nodeList.stream().map(ModelParser::nodeBuilder).collect(Collectors.toList());

    // 以节点code为key,节点对象为值构建节点对象map,用于line对象的初始化
    Map<String, NodeModel> flowNodeMap = nodeModelList.stream().collect(Collectors.toMap(NodeModel::getId, value -> value));

    List<LineModel> flowLineList = lineList.stream().map(lineJson -> lineBuilder(lineJson, flowNodeMap)).collect(Collectors.toList());

    // 以fromNode和toNode为key分组归类,后续注入到node属性中
    Map<String, List<LineModel>> fromLineMap = flowLineList.stream().collect(Collectors.groupingBy(followLine -> followLine.getSource().getId()));
    Map<String, List<LineModel>> toLineMap = flowLineList.stream().collect(Collectors.groupingBy(followLine -> followLine.getTarget().getId()));


    // 注入到node属性中
    nodeModelList.forEach(flowNodeResult -> {

        flowNodeResult.setOutputs(fromLineMap.get(flowNodeResult.getId()));
        flowNodeResult.setInputs(toLineMap.get(flowNodeResult.getId()));
    });

    Map<String, NodeModel> nodeModelMap = nodeModelList.stream().collect(Collectors.toMap(NodeModel::getId, value -> value));

    // 后续需要在root属性中添加可自行添加
    ProcessModel processModel = new ProcessModel();
    processModel.setNodes(nodeModelList);
    processModel.setNodeModelMap(nodeModelMap);

    processModel.setStartModel(buildStartNode(nodeModelList));

    return processModel;
}

写在最后

至此解析工作已经做完,到了这一步我们已经将流程定义解析完,得到我们要的对象。下一步我们就要将流程流转起来,也是工作流引擎的最重要一步,下篇文章将会进行介绍。