3
版权声明:
本文为博主原创文章,未经博主允许不得转载。关注公众号技术汇(ID: jishuhui_2015) 可联系到作者。

在上两篇文章中,介绍了BPMN2.0和工作流定义语言(以下简称WDL),以及工单系统的业务流程设计。本文是工单系统系列的最后一篇,着重讲解工单系统的程序设计。

因为所有流程的配置和定义都在WDL文件中,所以必然会引入关于WDL的解析层,这是整个工单系统的基础。

除了解析层,还少不了执行层的存在,整个工单流程就是靠执行层去推动的。

如果对Activiti的源码有所钻研的读者,看完此篇文章后,会觉得非常类似,这是不可否认的,站在巨人的肩膀上才能看得更远更全。

Activiti:一直被模仿,从未被超越。

一、BpmnModel

这部分内容主要介绍WDL中各元素的设计,它们有个统一的名字:BpmnModel。这个名字来源于Activiti的源码,在此沿用之。

根据基于BPMN2.0的工单系统架构设计(上)文章中的介绍,WDL文件中可以提取五个关键的基本要素:

1、Definitions,这个是根元素,无可厚非;

2、Message,关于消息的定义,是Definitions的子元素,可以有多个消息的定义;

3、Process,整个流程的定义,也是Definitions的子元素,暂不支持SubProcess;

4、EventDefinition,事件定义,与其他元素区别较大,所以单独提取出来,包括了消息事件,定时事件;

6、FlowNode,可以理解为是Process的各个子元素,比如SequenceFlow,Task,Gateway等等。

基本元素继承关系

BaseElement是最顶层的抽象,id和elementName作为共有属性,同时有一个validate校验方法,对元素的合法性进行校验。

因为是需要将一整个WDL文件解析出来,这其中就会包含很多元素对象,最后还需要一个真正意义上的BpmnModel进行装载,如下:
BpmnModel

在上图的BpmnModel中,Process非常关键,因为其下有众多子元素(FlowNode),找到了Process就能找到其他的子元素。所以,Process的另外一个作用就是元素容器(Container),容器里面可以维护自己的子元素,如下是接口定义:
FlowNodeContainer

二、WDL解析器

Activiti有一个强大之处在于能进行可视化流程配置,只要了解BPMN2.0规范,就能很轻松的配置出想要的流程。

当然,本工单系统是没有必要去实现这个功能的,所以这一部分侧重讲解从WDL文件到BpmnModel的过程。

笔者从Activiti源码中看到了有一个interface,当中定义了所有Activiti支持的元素,当然也涵盖了上述提到的那些基本元素。该类名是BpmnXMLConstants,其内容较大,故不在此展示了。

笔者沿用了这个类,但是做了一点小改进:将元素标签和元素属性分开,做成了两个interface,如下所示:
BpmnXmlConstants

Activiti中使用的解析工具是JDK自带的XMLStream,大概是不想引入新的依赖,增加开发者的负担吧!笔者使用的是Dom4j,而不是XMLStream。基于这一点考虑,笔者在设计的时候抽象出了顶层接口,如下所示:
Parser

BaseBpmnXMLParser接口中的泛型即为元素解析对象,比如XMLStreamReader,Dom4j中的Element对象等等。doParse方法即为解析的入口,WDL中所支持的各类元素标签都将有一个Parser实现。

三、WDL转换器

这一个部分的主要功能是WDL与BpmnModel的互相转换。
Converter

与解析器的设计类似,也是要根据XML的解析工具进行实现。

在Converter接口中,定义了三个方法,其中两个是WDL和BpmnModel的互转,目前仅实现了convertToBpmnModel。

还有一个是对BpmnModel的校验方法,校验不通过直接抛出Exception。

对于转换成功的BpmnModel,会存放在一个容器内,称之为BpmnModelContainer。
BpmnModelContainer

这个Container接口定义了对BpmnModel基本的添加,移除和查找方法。另外还有三个方法需要解释一下:

1、next。指示工单流程的下一个步骤,返回下一个步骤的元素。因为可能存在ParallelGateway,这就意味着下一个步骤将会有多个节点同步执行,所以返回结果是一个Queue对象,存放着下一个步骤要执行的节点元素。

2、conditionParse。在基于BPMN2.0的工单系统架构设计(上)文章中曾提及到了WDL中表达式的解析。在container中,此方法主要解析的是网关的执行条件,配合next方法使用。

3、fill。这个方法是用于填充Container,该方法的调用时机是构造BpmnModelContainer的时候,当然是要借助WDL转换器实现。如下所示:

@Configuration
@EnableConfigurationProperties(WorkflowProperties.class)
public class BpmnXmlManager {

    /**
     * 构造BpmnModelContainer,解析WDL文件,填充BpmnModel
     */
    @Bean
    public BpmnModelContainer bpmnContainer(WorkflowProperties workflowProperties, BaseBpmnXMLConverter converter) throws Exception {
        BpmnModelContainer container = new BpmnModelContainer(workflowProperties, converter);
        container.fill();
        return container;
    }

    /**
     * 构造XML解析工厂,默认是Dom4j的解析方式
     */
    @Bean
    @ConditionalOnMissingBean(BpmnXMLParserFactory.class)
    public Dom4jParserFactory xmlParserFactory() {
        return new Dom4jParserFactory();
    }

    /**
     * 构造XML转换工厂,默认是Dom4j的转换方式
     */
    @Bean
    @ConditionalOnMissingBean(BaseBpmnXMLConverter.class)
    public Dom4jConverter xmlConverter() {
        return new Dom4jConverter();
    }
}

四、工单执行

前三部分工作完成之后,工单就可以正常流转了。
service
1、start,工单的发起;

2、accept,后台操作人员接单;

3、doTask,执行工单节点(用户任务);

4、getWorkOrderDetail,获取工单详情,包含工单基本信息,工单动态,工单客户信息,工单任务列表等;

5、addWorkOrderProgress,添加一条工单进度信息,方便客户端查看工单进度;

6、nextElement,查找下一个工单节点,查找范围涵盖了Process的所有子元素,是推动工单流程的重要方法。

对于WDL中的Task和Event都有设计其对应的Runner,用于执行其内在的业务逻辑。
runner

在Runner执行期间,会遇到一些定时任务,比如定时边界捕获事件,笔者使用了Quartz开发框架,整合了SpringBoot进行定时任务的管理。

五、总结

本篇文章对工单系统的程序设计做了较为详细的讲解,对于整个工单系统的设计也就到此为止了。如果笔者能通读工单系统设计系列的三篇文章,对于今后的工单系统设计也将会有所启发。

关注我们


Eric
227 声望36 粉丝

不想做领导的程序猿不是好的项目经理。