Activiti工作流引擎基础入门【收藏可做笔记系列】下

简介: Activiti工作流引擎基础入门【收藏可做笔记系列】

五、Activiti入门


在本章内容中,我们来创建一个Activiti工作流,并启动这个流程。


创建Activiti工作流主要包含以下几步:


1、定义流程,按照BPMN的规范,使用流程定义工具,用流程符号把整个流程描述出来


2、部署流程,把画好的流程定义文件,加载到数据库中,生成表的数据


3、启动流程,使用java代码来操作数据库表中的内容


5.1 流程符号


BPMN 2.0是业务流程建模符号2.0的缩写。


它由Business Process Management Initiative这个非营利协会创建并不断发展。作为一种标识,BPMN 2.0是使用一些符号来明确业务流程设计流程图的一整套符号规范,它能增进业务建模时的沟通效率。


目前BPMN2.0是最新的版本,它用于在BPM上下文中进行布局和可视化的沟通。


接下来我们先来了解在流程设计中常见的 符号。


BPMN2.0的基本符合主要包含:


事件 Event

1.png


活动 Activity


活动是工作或任务的一个通用术语。一个活动可以是一个任务,还可以是一个当前流程的子处理流程; 其次,你还可以为活动指定不同的类型。常见活动如下:

2.png



网关 GateWay

网关用来处理决策,有几种常用网关需要了解:

3.png


排他网关 (x)

——只有一条路径会被选择。流程执行到该网关时,按照输出流的顺序逐个计算,当条件的计算结果为true时,继续执行当前网关的输出流;


如果多条线路计算结果都是 true,则会执行第一个值为 true 的线路。如果所有网关计算结果没有true,则引擎会抛出异常。


排他网关需要和条件顺序流结合使用,default 属性指定默认顺序流,当所有的条件不满足时会执行默认顺序流。


并行网关 (+)

——所有路径会被同时选择


拆分 —— 并行执行所有输出顺序流,为每一条顺序流创建一个并行执行线路。


合并 —— 所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才继续向下执行。


包容网关 (+)

—— 可以同时执行多条线路,也可以在网关上设置条件


拆分 —— 计算每条线路上的表达式,当表达式计算结果为true时,创建一个并行线路并继续执行


合并 —— 所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才继续向下执行。


事件网关 (+)

—— 专门为中间捕获事件设置的,允许设置多个输出流指向多个不同的中间捕获事件。当流程执行到事件网关后,流程处于等待状态,需要等待抛出事件才能将等待状态转换为活动状态。


流向 Flow


流是连接两个流程节点的连线。常见的流向包含以下几种:


4.png


5.2 流程设计器使用


Activiti-Designer使用


Palette(画板)

在idea中安装插件即可使用,画板中包括以下结点:


Connection—连接


Event—事件


Task—任务


Gateway—网关


Container—容器


Boundary event—边界事件


Intermediate event- -中间事件


流程图设计完毕保存生成.bpmn文件


新建流程(IDEA工具)

首先选中存放图形的目录(选择resources下的bpmn目录),点击菜单:New -> BpmnFile,如图:


1.png


弹出如下图所示框,输入evection 表示 出差审批流程:


2.png


起完名字evection后(默认扩展名为bpmn),就可以看到流程设计页面,如图所示:

3.png


左侧区域是绘图区,右侧区域是palette画板区域


鼠标先点击画板的元素即可在左侧绘图


绘制流程


使用滑板来绘制流程,通过从右侧把图标拖拽到左侧的画板,最终效果如下:

1.png



指定流程定义Key


流程定义key即流程定义的标识,通过properties视图查看流程的key

1.png


指定任务负责人


在properties视图指定每个任务结点的负责人,如:填写出差申请的负责人为 zhangsan

2.png



经理审批负责人为 jerry


总经理审批负责人为 jack


财务审批负责人为 rose


六、流程操作


6.1 流程定义


概述


流程定义是线下按照bpmn2.0标准去描述 业务流程,通常使用idea中的插件对业务流程进行建模。


使用idea下的designer设计器绘制流程,并会生成两个文件:.bpmn和.png


.bpmn文件

使用activiti-desinger设计业务流程,会生成.bpmn文件,上面我们已经创建好了bpmn文件


BPMN 2.0根节点是definitions节点。 这个元素中,可以定义多个流程定义(不过我们建议每个文件只包含一个流程定义, 可以简化开发过程中的维护难度)。 注意,definitions元素 最少也要包含xmlns 和 targetNamespace的声明。 targetNamespace可以是任意值,它用来对流程实例进行分类。


流程定义部分:定义了流程每个结点的描述及结点之间的流程流转。


流程布局定义:定义流程每个结点在流程图上的位置坐标等信息。


生成.png图片文件


IDEA工具中的操作方式


1、修改文件后缀为xml

首先将evection.bpmn文件改名为evection.xml,如下图:

1.png



evection.xml修改前的bpmn文件,效果如下:

2.png



2、使用designer设计器打开.xml文件

在evection.xml文件上面,点右键并选择Diagrams菜单,再选择Show BPMN2.0 Designer…

22.png



3、查看打开的文件

打开后,却出现乱码,如图:


3.png


4、解决中文乱码

1、打开Settings,找到File Encodings,把encoding的选项都选择UTF-8

41.png



2、打开IDEA安装路径,找到如下的安装目录


42.png


根据自己所安装的版本来决定,我使用的是64位的idea,所以在idea64.exe.vmoptions文件的最后一行追加一条命令: -Dfile.encoding=UTF-8


如下所示:


43.png


一定注意,不要有空格,否则重启IDEA时会打不开,然后 重启IDEA。


如果以上方法已经做完,还出现乱码,就再修改一个文件,并在文件的末尾添加: -Dfile.encoding=UTF-8,然后重启idea,如图:

2.png



最后重新在evection.xml文件上面,点右键并选择Diagrams菜单,再选择Show BPMN2.0 Designer…,看到生成图片,如图:


1.png


到此,解决乱码问题


5、导出为图片文件

点击Export To File的小图标,打开如下窗口,注意填写文件名及扩展名,选择好保存图片的位置:

3.png



然后,我们把png文件拷贝到resources下的bpmn目录,并且把evection.xml改名为evection.bpmn。


6.2 流程定义部署


概述


将上面在设计器中定义的流程部署到activiti数据库中,就是流程定义部署。


通过调用activiti的api将流程定义的bpmn和png两个文件一个一个添加部署到activiti中,也可以将两个文件打成zip包进行部署。


单个文件部署方式


分别将bpmn文件和png图片文件部署。


package com.itheima.test;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.junit.Test;
public class ActivitiDemo {
    /**
     * 部署流程定义
     */
    @Test
    public void testDeployment(){
//        1、创建ProcessEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        2、得到RepositoryService实例
        RepositoryService repositoryService = processEngine.getRepositoryService();
//        3、使用RepositoryService进行部署
        Deployment deployment = repositoryService.createDeployment()
                .addClasspathResource("bpmn/evection.bpmn") // 添加bpmn资源
                .addClasspathResource("bpmn/evection.png")  // 添加png资源
                .name("出差申请流程")
                .deploy();
//        4、输出部署信息
        System.out.println("流程部署id:" + deployment.getId());
        System.out.println("流程部署名称:" + deployment.getName());
    }
}

执行此操作后activiti会将上边代码中指定的bpm文件和图片文件保存在activiti数据库。


压缩包部署方式


将evection.bpmn和evection.png压缩成zip包。


@Test
  public void deployProcessByZip() {
    // 定义zip输入流
    InputStream inputStream = this
        .getClass()
        .getClassLoader()
        .getResourceAsStream(
            "bpmn/evection.zip");
    ZipInputStream zipInputStream = new ZipInputStream(inputStream);
    // 获取repositoryService
    RepositoryService repositoryService = processEngine
        .getRepositoryService();
    // 流程部署
    Deployment deployment = repositoryService.createDeployment()
        .addZipInputStream(zipInputStream)
        .deploy();
    System.out.println("流程部署id:" + deployment.getId());
    System.out.println("流程部署名称:" + deployment.getName());
  }

执行此操作后activiti会将上边代码中指定的bpm文件和图片文件保存在activiti数据库。


操作数据表


流程定义部署后操作activiti的3张表如下:


act_re_deployment 流程定义部署表,每部署一次增加一条记录


act_re_procdef 流程定义表,部署每个新的流程定义都会在这张表中增加一条记录


act_ge_bytearray 流程资源表


接下来我们来看看,写入了什么数据:


SELECT * FROM act_re_deployment #流程定义部署表,记录流程部署信息

结果:

1.png



SELECT * FROM act_re_procdef #流程定义表,记录流程定义信息

结果:


注意,KEY 这个字段是用来唯一识别不同流程的关键字

2.png



SELECT * FROM act_ge_bytearray #资源表 

结果:

1.png



注意:


act_re_deployment和act_re_procdef一对多关系,一次部署在流程部署表生成一条记录,但一次部署可以部署多个流程定义,每个流程定义在流程定义表生成一条记录。每一个流程定义在act_ge_bytearray会存在两个资源记录,bpmn和png。


建议:一次部署一个流程,这样部署表和流程定义表是一对一有关系,方便读取流程部署及流程定义信息。


6.3 启动流程实例


流程定义部署在activiti后就可以通过工作流管理业务流程了,也就是说上边部署的出差申请流程可以使用了。


针对该流程,启动一个流程表示发起一个新的出差申请单,这就相当于java类与java对象的关系,类定义好后需要new创建一个对象使用,当然可以new多个对象。对于请出差申请流程,张三发起一个出差申请单需要启动一个流程实例,出差申请单发起一个出差单也需要启动一个流程实例。


代码如下:


 

    /**
     * 启动流程实例
     */
    @Test
    public void testStartProcess(){
//        1、创建ProcessEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        2、获取RunTimeService
        RuntimeService runtimeService = processEngine.getRuntimeService();
//        3、根据流程定义Id启动流程
        ProcessInstance processInstance = runtimeService
                .startProcessInstanceByKey("myEvection");
//        输出内容
        System.out.println("流程定义id:" + processInstance.getProcessDefinitionId());
        System.out.println("流程实例id:" + processInstance.getId());
        System.out.println("当前活动Id:" + processInstance.getActivityId());
    }

输出内容如下:

1.png



操作数据表


act_hi_actinst 流程实例执行历史


act_hi_identitylink 流程的参与用户历史信息


act_hi_procinst 流程实例历史信息


act_hi_taskinst 流程任务历史信息


act_ru_execution 流程执行信息


act_ru_identitylink 流程的参与用户信息


act_ru_task 任务信息


6.4 任务查询


流程启动后,任务的负责人就可以查询自己当前需要处理的任务,查询出来的任务都是该用户的待办任务。


/**
     * 查询当前个人待执行的任务
      */
    @Test
    public void testFindPersonalTaskList() {
//        任务负责人
        String assignee = "zhangsan";
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        创建TaskService
        TaskService taskService = processEngine.getTaskService();
//        根据流程key 和 任务负责人 查询任务
        List<Task> list = taskService.createTaskQuery()
                .processDefinitionKey("myEvection") //流程Key
                .taskAssignee(assignee)//只查询该任务负责人的任务
                .list();
        for (Task task : list) {
            System.out.println("流程实例id:" + task.getProcessInstanceId());
            System.out.println("任务id:" + task.getId());
            System.out.println("任务负责人:" + task.getAssignee());
            System.out.println("任务名称:" + task.getName());
        }
    }

输出结果如下:


流程实例id:2501
任务id:2505
任务负责人:zhangsan
任务名称:创建出差申请

6.5 流程任务处理


任务负责人查询待办任务,选择任务进行处理,完成任务。


// 完成任务
    @Test
    public void completTask(){
//        获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        获取taskService
        TaskService taskService = processEngine.getTaskService();
//        根据流程key 和 任务的负责人 查询任务
//        返回一个任务对象
        Task task = taskService.createTaskQuery()
                .processDefinitionKey("myEvection") //流程Key
                .taskAssignee("zhangsan")  //要查询的负责人
                .singleResult();
//        完成任务,参数:任务id
        taskService.complete(task.getId());
    }

注意:这里使用的是.singleResult();,此时就必须保证数据库中唯一存在,不然就需要使用.list()来返回一个List集合,遍历集合再进行信息的获取。下面使用到.singleResult();的地方同理。


6.6 流程定义信息查询


查询流程相关信息,包含流程定义,流程部署,流程定义版本


 

    /**
     * 查询流程定义
     */
    @Test
    public void queryProcessDefinition(){
        //        获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        repositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
//        得到ProcessDefinitionQuery 对象
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
//          查询出当前所有的流程定义
//          条件:processDefinitionKey =evection
//          orderByProcessDefinitionVersion 按照版本排序
//        desc倒叙
//        list 返回集合
        List<ProcessDefinition> definitionList = processDefinitionQuery.processDefinitionKey("myEvection")
                .orderByProcessDefinitionVersion()
                .desc()
                .list();
//      输出流程定义信息
        for (ProcessDefinition processDefinition : definitionList) {
            System.out.println("流程定义 id="+processDefinition.getId());
            System.out.println("流程定义 name="+processDefinition.getName());
            System.out.println("流程定义 key="+processDefinition.getKey());
            System.out.println("流程定义 Version="+processDefinition.getVersion());
            System.out.println("流程部署ID ="+processDefinition.getDeploymentId());
        }
    }

输出结果:


流程定义id:myEvection:1:4
流程定义名称:出差申请单
流程定义key:myEvection
流程定义版本:1

6.7 流程删除


public void deleteDeployment() {
    // 流程部署id
    String deploymentId = "1";
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 通过流程引擎获取repositoryService
    RepositoryService repositoryService = processEngine
        .getRepositoryService();
    //删除流程定义,如果该流程定义已有流程实例启动则删除时出错
    repositoryService.deleteDeployment(deploymentId);
    //设置true 级联删除流程定义,即使该流程有流程实例启动也可以删除,设置为false非级别删除方式,如果流程
    //repositoryService.deleteDeployment(deploymentId, true);
  }

说明:


 使用repositoryService删除流程定义,历史表信息不会被删除


 如果该流程定义下没有正在运行的流程,则可以用普通删除。


如果该流程定义下存在已经运行的流程,使用普通删除报错,可用级联删除方法将流程及相关记录全部删除。


先删除没有完成流程节点,最后就可以完全删除流程定义信息


项目开发中级联删除操作一般只开放给超级管理员使用.


6.8 流程资源下载


现在我们的流程资源文件已经上传到数据库了,如果其他用户想要查看这些资源文件,可以从数据库中把资源文件下载到本地。


解决方案有:


1、jdbc对blob类型,clob类型数据读取出来,保存到文件目录


2、使用activiti的api来实现


使用commons-io.jar 解决IO的操作


引入commons-io依赖包


<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

通过流程定义对象获取流程定义资源,获取bpmn和png


import org.apache.commons.io.IOUtils;
@Test
    public void deleteDeployment(){
//        获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        获取repositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
//        根据部署id 删除部署信息,如果想要级联删除,可以添加第二个参数,true
        repositoryService.deleteDeployment("1");
    }
    public void  queryBpmnFile() throws IOException {
//        1、得到引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        2、获取repositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
//        3、得到查询器:ProcessDefinitionQuery,设置查询条件,得到想要的流程定义
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey("myEvection")
                .singleResult();
//        4、通过流程定义信息,得到部署ID
        String deploymentId = processDefinition.getDeploymentId();
//        5、通过repositoryService的方法,实现读取图片信息和bpmn信息
//        png图片的流
        InputStream pngInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getDiagramResourceName());
//        bpmn文件的流
        InputStream bpmnInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());
//        6、构造OutputStream流
        File file_png = new File("d:/evectionflow01.png");
        File file_bpmn = new File("d:/evectionflow01.bpmn");
        FileOutputStream bpmnOut = new FileOutputStream(file_bpmn);
        FileOutputStream pngOut = new FileOutputStream(file_png);
//        7、输入流,输出流的转换
        IOUtils.copy(pngInput,pngOut);
        IOUtils.copy(bpmnInput,bpmnOut);
//        8、关闭流
        pngOut.close();
        bpmnOut.close();
        pngInput.close();
        bpmnInput.close();
    }

说明:


deploymentId为流程部署ID


resource_name为act_ge_bytearray表中NAME_列的值


使用repositoryService的getDeploymentResourceNames方法可以获取指定部署下得所有文件的名称


使用repositoryService的getResourceAsStream方法传入部署ID和资源图片名称可以获取部署下指定名称文件的输入流


最后的将输入流中的图片资源进行输出。


6.9 流程历史信息的查看


即使流程定义已经删除了,流程执行的历史信息通过前面的分析,依然保存在activiti的act_hi_*相关的表中。所以我们还是可以查询流程执行的历史信息,可以通过HistoryService来查看相关的历史记录。


    /**
     * 查看历史信息
     */
    @Test
    public void findHistoryInfo(){
//      获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        获取HistoryService
        HistoryService historyService = processEngine.getHistoryService();
//        获取 actinst表的查询对象
        HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery();
//        查询 actinst表,条件:根据 InstanceId 查询
//        instanceQuery.processInstanceId("2501");
//        查询 actinst表,条件:根据 DefinitionId 查询
        instanceQuery.processDefinitionId("myEvection:1:4");
//        增加排序操作,orderByHistoricActivityInstanceStartTime 根据开始时间排序 asc 升序
        instanceQuery.orderByHistoricActivityInstanceStartTime().asc();
//        查询所有内容
        List<HistoricActivityInstance> activityInstanceList = instanceQuery.list();
//        输出
        for (HistoricActivityInstance hi : activityInstanceList) {
            System.out.println(hi.getActivityId());
            System.out.println(hi.getActivityName());
            System.out.println(hi.getProcessDefinitionId());
            System.out.println(hi.getProcessInstanceId());
            System.out.println("<==========================>");
        }
    }
相关文章
|
6月前
|
Android开发
Android游戏引擎AndEngine入门资料
Android游戏引擎AndEngine入门资料
42 3
|
6月前
|
测试技术 持续交付 C#
C#程序基础开发入门学习笔记
C#是一种现代的、面向对象的编程语言,广泛应用于Windows应用程序开发、游戏开发(尤其是Unity引擎)、Web应用程序以及跨平台应用等。
86 0
|
Java 开发工具 Android开发
插件化 · 入门篇 · 2023年插件化学习,从Activity开始
插件化 · 入门篇 · 2023年插件化学习,从Activity开始
278 0
插件化 · 入门篇 · 2023年插件化学习,从Activity开始
|
存储 SQL 分布式计算
Flink 引擎简介 | 青训营笔记
从产品技术来看,Flink 具备如下流计算技术特征:完全一次保证:故障后应正确恢复有状态运算符中的状态;低延迟:越低越好。许多应用程序需要亚秒级延迟;高吞吐量:随着数据速率的增长,通过管道推送大量数据至关重要;强大的计算模型:框架应该提供一种编程模型,该模型不限制用户并允许各种各样的应用程序在没有故障的情况下,容错机制的开销很低;流量控制:来自慢速算子的反压应该由系统和数据源自然吸收,以避免因消费者缓慢而导致崩溃或降低性能;乱序数据的支持:支持由于其他原因导致的数据乱序达到、延迟到达后,计算出正确的结果;完备的流式语义:支持窗口等现代流式处理语义抽象;
219 0
Flink 引擎简介 | 青训营笔记
|
Java 关系型数据库 MySQL
入门案例(开发环境配置)|学习笔记
快速学习入门案例(开发环境配置)
101 0
入门案例(开发环境配置)|学习笔记
|
存储 边缘计算 网络协议
LinkPlatform 简介与开发入门(二)|学习笔记
快速学习 LinkPlatform 简介与开发入门
LinkPlatform 简介与开发入门(二)|学习笔记
|
存储 物联网 关系型数据库
LinkPlatform 简介与开发入门(一)|学习笔记
快速学习 LinkPlatform 简介与开发入门
LinkPlatform 简介与开发入门(一)|学习笔记
|
存储 JSON 物联网
LinkPlatform 简介与开发入门(三)|学习笔记
快速学习 LinkPlatform 简介与开发入门
LinkPlatform 简介与开发入门(三)|学习笔记
|
XML 数据可视化 Java
史上最全的工作流引擎 Activiti 学习教程(值得收藏)(三)
史上最全的工作流引擎 Activiti 学习教程(值得收藏)(三)
史上最全的工作流引擎 Activiti 学习教程(值得收藏)(三)
|
XML 存储 架构师
史上最全的工作流引擎 Activiti 学习教程(值得收藏)(一)
史上最全的工作流引擎 Activiti 学习教程(值得收藏)(一)
史上最全的工作流引擎 Activiti 学习教程(值得收藏)(一)