activiti入门

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

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

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

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

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

1.流程符号

BPMN2.0就是使用一些符号来明确业务流程设计流程图的一整套符号规范。

基本流程符号:

事件Event

java activiti 模板绘制_activiti

活动Activity

活动是一个工作或任务的通用术语。一个活动可以是一个任务,也可以是一个当前流程的子处理流程。

常见的活动如下:

java activiti 模板绘制_System_02

网关Getway

网关用来处理决策,常用的网关如下:

java activiti 模板绘制_System_03

  • 排他网关

只有一条路径会被选择。流程执行到该网关时,按照输出流的顺序逐个计算,当条件的计算结果为true时,继续执行当前网关的输出流。如果多条线路计算结果都是true,则会执行第一个值为true的线路。如果所有网关计算结果没有true,则引擎会抛出异常。

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

  • 并行网关

所有路径都会被同时选择。

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

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

  • 包容网关

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

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

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

  • 事件网关

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

流向Flow

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

java activiti 模板绘制_Test_04

2.流程设计器使用

在idea中已经安装好bpmn插件,安装好的插件面板(可以自行百度,我这里装的是activiti bpmn visualizer):

java activiti 模板绘制_activiti_05

2.1新建流程

在resource目录下新建bpmn目录,然后点击右键菜单新建:

java activiti 模板绘制_java activiti 模板绘制_06

文件名随意,默认生成一个xml文件,右键文件:

java activiti 模板绘制_activiti_07

弹出绘图层

java activiti 模板绘制_System_08

2.2绘制流程

在面板右键,选择Activities > User Task,创建一个用户任务,并定义属性值:该节点为zhangsan创建出差申请单

java activiti 模板绘制_Test_09

再次选择Activities > User Task,创建下一个任务,并定义属性值:部门经理审批人为lisi

java activiti 模板绘制_java activiti 模板绘制_10

再创建一个任务,定义属性值:总经理审批人为wangwu

java activiti 模板绘制_System_11

再创建一个任务。定义属性值:财务审批为zhaoliu

java activiti 模板绘制_java activiti 模板绘制_12

最后创建一个结束事件:

java activiti 模板绘制_activiti_13

然后将所有的流程符号连接起来,如下:

java activiti 模板绘制_System_14

java activiti 模板绘制_Test_15

java activiti 模板绘制_activiti_16

3.流程操作

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

上面步骤,创建的流程文件是.xml文件,我们需要改成bpmn文件,然后还需要该流程的png文件

png文件,直接右键导出保存到resource/bpmn路径下

java activiti 模板绘制_java activiti 模板绘制_17

bnmn文件直接将上面创建的xml文件改后缀名即可

java activiti 模板绘制_activiti_18

最后在resource/bpmn路径下有两个文件,evection.png,evection.bpmn

4.流程部署

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

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

4.1单文件部署方式
/**
     * 流程部署
     */
    @Test
    public void testDeployment() {
        //创建ProcessEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //得到RepositoryService实例,该实例是activiti的资源管理类
        RepositoryService repositoryService = processEngine.getRepositoryService();
        //使用repositoryService进行部署
        Deployment deployment = repositoryService.createDeployment()
                //添加bpmn资源
                .addClasspathResource("bpmn/evection.bpmn")
                //添加png
                .addClasspathResource("bpmn/evection.png")
                .name("出差申请流程")
                .deploy();
        //输出部署信息
        System.out.println("流程id:" + deployment.getId());
        System.out.println("流程部署名:" + deployment.getName());

    }

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

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

java activiti 模板绘制_activiti_19

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

java activiti 模板绘制_数据库_20

act_ge_bytearray 流程资源表

java activiti 模板绘制_Test_21

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

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

4.2压缩包部署方式
/**
     * 压缩包方式部署
     */
    @Test
    public void testDeploymentByZip() {
        //定义zip流
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("bpmn/evection.zip");
        ZipInputStream zipInputStream = new ZipInputStream(inputStream);
        //创建ProcessEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //获取RepositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
        //使用repositoryService进行部署
        Deployment deployment = repositoryService.createDeployment()
                //添加zip
                .addZipInputStream(zipInputStream)
                .name("出差申请流程")
                .deploy();
        //输出部署信息
        System.out.println("流程id:" + deployment.getId());
        System.out.println("流程部署名:" + deployment.getName());
    }
5.启动流程

流程定义部署在activiti后就可以通过工作流管理业务流程了。

针对该流程,启动一个流程表示发起一个新的出差申请单。张三发起一个出差申请单需要启动一个流程实例,出差申请单发起一个出差单也需要启动一个流程实例。

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

这里根据的key查询:就是act_re_procdef的key字段值,也是evection.bpmn20.xmlprocess标签的id值,后续的查询key同样

java activiti 模板绘制_java activiti 模板绘制_22

操作数据表

act_hi_actinst 流程实例执行历史

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

act_hi_procinst 流程实例历史信息

act_hi_taskinst 流程任务历史信息

act_ru_execution 流程执行信息

act_ru_identitylink 流程的参与用户信息

act_ru_task 任务信息

6.任务查询

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

/**
     * 查询当前个人待执行任务
     */
    @Test
    public void queryTask() {
        //任务负责人
        String assignee = "zhagnsan";
        //创建processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //创建taskService
        TaskService taskService = processEngine.getTaskService();
        //根据流程任务key和任务负责人查询任务
        List<Task> taskList = taskService.createTaskQuery()
                .processDefinitionKey("evection") //流程key
                .taskAssignee(assignee) //只查询该任务负责人的任务
                .list();

        taskList.forEach(task -> {
            System.out.println("流程实例id:" + task.getProcessInstanceId());
            System.out.println("任务id:" + task.getId());
            System.out.println("任务负责人:" + task.getAssignee());
            System.out.println("任务名称:" + task.getName());
        });
    }
流程实例id:15001
任务id:15005
任务负责人:zhagnsan
任务名称:创建出差申请单
7.流程任务处理

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

/**
     * 完成任务
     */
    @Test
    public void completeTask() {
        //获取processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //获取taskService
        TaskService taskService = processEngine.getTaskService();
        //根据流程key和任务负责人查询任务
        List<Task> taskList = taskService.createTaskQuery()
                .processDefinitionKey("evection") //流程key
                .taskAssignee("zhagnsan") //要查询的负责人
                .list();

        //完成任务,参数任务id
        taskList.forEach(task -> {
            taskService.complete(task.getId());
        });
    }
8.流程定义信息查询

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

/**
     * 查询流程定义
     */
    @Test
    public void queryProcessDefinition() {
        //获取processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //获取RepositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
        //获取ProcessDefinitionQuery
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
        /**
         * 查询到当前流程的所有定义
         * 条件:processDefinitionKey = evection
         *      orderByProcessDefinitionVersion desc 按照版本倒序
         * 返回list集合
         */
        List<ProcessDefinition> definitionList = processDefinitionQuery
                .processDefinitionKey("evection")
                .orderByProcessDefinitionVersion().desc()
                .list();

        //输出流程定义信息
        definitionList.forEach(processDefinition -> {
            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:evection:1:7504
流程定义name:evection
流程定义key:evection
流程定义version:1
流程部署id:7501
9.流程删除
/**
     * 流程删除
     */
    @Test
    public void deleteProcess() {
        //流程部署id
        String deploymentId = "20001";
        //获取processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //获取RepositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
        //删除流程定义,如果该流程定义已有流程实例启动,则删除报错
        repositoryService.deleteDeployment(deploymentId);

        //级联删除,即时流程已有流程实例启动也可以删除
        //repositoryService.deleteDeployment(deploymentId, true); //true为级联删除
    }

说明:

  1. 使用repositoryService删除流程定义,历史表信息不会被删除
  2. 如果流程定义下没有流程实例启动,使用普通删除,只传流程id即可
  3. 如果流程定义下有流程实例启动,使用普通删除会报错,需要使用级联删除且级联删除会将相关记录全部删除
    项目开发中级联删除操作一般只开放给超级管理员使用
10.流程资源下载

上面流程部署的时候,已经将流程资源文件bpmn和png文件上传到数据库中,如果需要查看这些资源文件的话,可以从数据库将资源文件下载到本地。

解决方案有:

  1. act_ge_bytearray表中可以看到资源文件是以BLOB的格式存储的,可以通过jdbc操作进行读取再存储到本地
  2. 直接使用activiti的api来实现

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

引入commons-io依赖包

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

使用api的方式实现:

/**
     * 下载流程资源文件 bpmn png
     */
    @Test
    public void downloadFile() throws Exception {
        //得到processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //得到repositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
        //得到流程定义查询器,设置查询条件查询想要的流程定义
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey("evection").singleResult();
        //获取到流程部署id
        String deploymentId = processDefinition.getDeploymentId();
        //通过deploymentId获取到png和bpmn
        InputStream pngInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getDiagramResourceName());
        InputStream bpmnInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());

        //构建outputStream
        File pngFile = new File("d:/data/evection.png");
        File bpmnFile = new File("d:/data/evection.bpmn");
        FileOutputStream pngOutput = new FileOutputStream(pngFile);
        FileOutputStream bpmnOutput = new FileOutputStream(bpmnFile);
        //输入流输出流转换
        IOUtils.copy(pngInput, pngOutput);
        IOUtils.copy(bpmnInput, bpmnOutput);
        //关闭流
        pngOutput.close();
        bpmnOutput.close();
        pngInput.close();
        bpmnInput.close();
    }

说明:

  1. deploymentId是流程部署id
  2. resource_name是act_ge_bytearray表中的NAME_列的值
  3. 使用repositoryService的getDeploymentResourceNames方法可以获取指定部署下的所有文件的名称
  4. 使用repositoryService的getResourceAsStream方法传入部署id和资源文件名称可以获取指定文件的输入流
  5. 最后将资源文件的输入流输出到本地
11.流程历史信息查询

即时流程定义已经删除,流程执行的历史信息还是可以在act_hi_*相关的表中查询到。级联删除的不可以。

/**
     * 历史信息查询
     */
    @Test
    public void queryHistory() {
        //获取processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //获取historyService
        HistoryService historyService = processEngine.getHistoryService();
        //获取actinst表的查询对象
        HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery();
        //排序,根据开始时间排序
        instanceQuery.orderByHistoricActivityInstanceStartTime().asc();
        //查询actinst表,条件:根据instanceId查询
        //instanceQuery.processInstanceId("25001");
        //查询 actinst表,条件:根据 DefinitionId 查询
        instanceQuery.processDefinitionId("evection:1:22504");

        List<HistoricActivityInstance> instanceList = instanceQuery.list();
        instanceList.forEach(instance -> {
            System.out.println(instance.getId());
            System.out.println("流程活动id:" + instance.getActivityId());
            System.out.println("流程活动名称:" + instance.getActivityName());
            System.out.println("流程实例id:" + instance.getProcessInstanceId());
            System.out.println("流程定义id:" + instance.getProcessDefinitionId());
            System.out.println("===================================");
        });
    }
25003
流程活动id:sid-07bae378-ef8d-4dab-83a7-ea0943ba27f3
流程活动名称:开始出差申请单任务
流程实例id:25001
流程定义id:evection:1:22504
===================================
25004
流程活动id:sid-60d04e4f-b36f-4e69-ad4f-4a51be3d1c3c
流程活动名称:创建出差申请单
流程实例id:25001
流程定义id:evection:1:22504
===================================
27501
流程活动id:sid-cc56cae5-1d4f-48a1-9df1-c7987b1e3ce6
流程活动名称:部门经理审批
流程实例id:25001
流程定义id:evection:1:22504
===================================