Activiti7工作流入门+案例应用

一、Activiti7相关定义

Activiti7 是基于Java的开源工作流引擎,用于业务流程的定义、执行与管理;就是通过计算机对业务流程自动化执行管理;使用专门的建模语言(BPMN2.0)进行定义

  • BPMN:(Business Process Model And Notation),业务流程模型和符号,是由BPMI(Business Process Management Initiative)开发的一套的业务流程建模符号,使用BPMN提供的符号可以创建业务流程

二、集成Activiti7

1.创建Maven项目

2.添加Maven依赖

xml 复制代码
 <properties>
        <slf4j.version>1.6.6</slf4j.version>
        <log4j.version>1.2.12</log4j.version>
        <activiti.version>7.0.0.SR1</activiti.version>
    </properties>
    <dependencies>
        <!-- activiti引擎 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-engine</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!-- 整合Spring -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!-- bpmn 模型处理 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-model</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!-- bpmn 转换 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-converter</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!-- bpmn json数据转换 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-json-converter</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!-- bpmn 布局 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-layout</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.40</version>
        </dependency>
        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        <!-- 单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!-- log start -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <!-- log end -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
        <!--数据库连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.4</version>
        </dependency>
    </dependencies>

3.添加log4j日志配置

添加日志配置,需要在resources目录下,添加log4j.properties

xml 复制代码
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r[%15.15t] %-5p %30.30c %x - %m\n
# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=./activiti.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r[%15.15t] %-5p %30.30c %x - %m\n

4. 添加核心配置文件

需要在resource目录中添加配置文件activiti.cfg.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--数据库连接池-->
    <bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql:///activiti_java73" />
        <property name="username" value="用户名" />
        <property name="password" value="密码" />
    </bean>
    <!-- 流程引擎对象 默认id对应的值 为processEngineConfiguration -->
    <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
        <!-- 关联数据源 -->
        <property name="dataSource" ref="dataSource1"/>
        <!--  数据库更新规则
            activiti数据库表处理策略
                false(默认值):检查数据库的版本和依赖库的版本,如果不匹配就抛出异常
                true:构建流程引擎时,执行检查,如果需要就执行更新。如果表不存在,就创建。
                create-drop:构建流程引擎时创建数据库报表,关闭流程引擎时就删除这些表。
                drop-create:先删除表再创建表。
                create:构建流程引擎时创建数据库表,关闭流程引擎时不删除这些表
        -->
        <property name="databaseSchemaUpdate" value="true"/>
    </bean>
</beans>

三、流程引擎API

1.RepositoryService--资源管理

(部署阶段)负责流程定义的部署、查询等,是管理流程资源的入口

2.RuntimeService--流程运行管理

(运行阶段)负责启动流程实例,并对运行中的实例进行挂起、恢复、删除等控制操作

3.TaskSerive--任务管理

(任务处理阶段)负责人工任务的全生命周期管理,包括任务的查询、分派、完成等

4.HistoryService--历史管理

(历史追溯阶段)负责查询已结束或仍在运行的流程实例的历史执行数据;比如流程实例启动时间,任务的参与者,完成任务的时间等

5.ManagementSerivce--引擎管理

主要用于Activiti系统的日常维护

四、Activiti7入门

1.业务流程建模

resource目录下新建bpmn目录用于存放所有的流程文件 ,在bpmn目录下新建

打开创建的文件,在文件中右键选择该选项打开,进行流程图绘制

绘制这样一个简单的报销流程图

2.部署流程定义

java 复制代码
/*
获取流程引擎对象
   ProcessEngines.getDefaultProcessEngine():获取 Activiti 流程引擎默认实例的核心方法。
   它会在第一次调用时初始化并创建一个流程引擎,以后再调用就会返回相同的流程引擎实例
*/
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
System.out.println(processEngine);
//通过获取的流程引擎对象,获取仓库
RepositoryService repositoryService = processEngine.getRepositoryService();
//通过获取的仓库调用api,createDeployment-创建部署,addClasspathResource--进行部署资源的添加,deploy-返回一个部署对象
Deployment deployment = repositoryService.createDeployment().addClasspathResource("bpmn/文件名.bpmn20.xml")               .addClasspathResource("bpmn/文件名.png")               .deploy();
System.out.println("部署id = " + deployment.getId());
System.out.println("部署时间 = " + deployment.getDeploymentTime());

3.启动流程实例

一个实例,就是一位用户提交的一个报销单,并进入报销流程

java 复制代码
//通过流程引擎对象,获取运行时service
RuntimeService runtimeService = processEngine.getRuntimeService();
// startProcessInstanceByKey-通过流程名key 启动流程,启动流程后可以获取到流程实例id,流程实例对应的流程定义id+key
ProcessInstance leaveProcess = runtimeService.startProcessInstanceByKey("流程定义名称");
System.out.println("实例id = " + leaveProcess.getProcessInstanceId());
 System.out.println("流程id = " + leaveProcess.getProcessDefinitionId());
System.out.println("流程key = " + leaveProcess.getProcessDefinitionKey());

4.任务查询

java 复制代码
//获取任务 TaskService
TaskService taskService = processEngine.getTaskService();
//模拟负责人
String user = "负责人id";
/*
 * 所有的查询  XxxService.createXxxQuery();
 * taskService.createTaskQuery()  -> select * from act_ru_task
 * .processDefinitionKey("leaveProcess") --> 连表处理  where processDefinitionKey = "leaveProcess"
 * .taskAssignee(userId)  --> where processDefinitionKey = "leaveProcess" and assignee="#{userId}"
 *
 * .singleResult() -->  返回一条数据
 * .list()  -->  返回多条数据
 * .listPage()  -->  返回多条并分页
 */
//查询该负责人的待办任务(查询本质就是创建sql)
List<Task> process = taskService.createTaskQuery().processDefinitionKey("流程定义名称").taskAssignee(user).list();

process.forEach(pro ->{
//拿到list 可以通过循环拿到每一个任务,可以获取本级的属性,也可以获取父类的属性
System.out.println("任务id = " + pro.getId());
System.out.println("任务名称 = " + pro.getName());
System.out.println("任务负责人 = " + pro.getAssignee());
System.out.println("任务创建时间 = " + pro.getCreateTime());
System.out.println("对应流程实例id = " + pro.getProcessInstanceId());
System.out.println("对应流程定义id = " + pro.getProcessDefinitionId());
});

5.任务审批

java 复制代码
 //方式1:直接审批:进行任务审批 模拟任务id
String taskId = "9";
taskService.complete(taskId);

//方式2:添加批注并审批
String taskId1 = "2502";
//模拟流程实例
String processInstanceId = "5";
String msg = "同意审批";
//可以对任务添加批注信息taskService.addComment,通过任务id和流程实例id去确定添加批注的任务
taskService.addComment(taskId1,processInstanceId,msg);
//完成任务 complete(任务id)
taskService.complete(taskId1);

6.历史查询

java 复制代码
//查询历史任务
//获取历史任务--HistoryService
HistoryService historyService = processEngine.getHistoryService();

//查询 createHistoricTaskInstanceQuery-- 灵活添加查询条件
List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery()
        .processInstanceId(processInstanceId)
        .list();
//遍历历史任务拿到信息
list.forEach(task -> {
    System.out.println("任务id = " + task.getId());
    System.out.println("任务名称 = " + task.getName());
    System.out.println("任务负责人 = " + task.getAssignee());
    System.out.println("任务创建时间 = " + task.getCreateTime());
    System.out.println("任务开始时间 = " + task.getStartTime());
    System.out.println("任务总耗时 = " + task.getDurationInMillis());
    System.out.println("实例id = " + task.getProcessInstanceId());
    System.out.println("流程定义id = " + task.getProcessDefinitionId());

    //需要通过任务-TaskService 去获取任务对应的批注信息,
    //可以查询批注信息        通过历史任务的id,获取批注信息
    List<Comment> taskComments = taskService.getTaskComments(task.getId());
    //进行判空,不为空,进行遍历显示
    if (taskComments != null && !taskComments.isEmpty()){
        System.out.println("getFullMessage() = " + taskComments.get(0).getFullMessage());
    }
});

五、Activiti7进阶

1.流程定义相关

流程定义查询

java 复制代码
@Test
public void testQueryDefinition(){
   // 获取流程引擎对象
   ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
   // 获取仓库 RepositoryService
   RepositoryService repositoryService = processEngine.getRepositoryService();
   // 查询流程定义  repositoryService.createProcessDefinitionQuery()
   // 查询的条件是根据业务需求改变的。若是查询所有则不需要添加任何参数。
   List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().processDefinitionKey("流程定义名称")
           .list();
   list.forEach(processDefinition -> {
       System.out.println(processDefinition.getName());
       System.out.println("对应的部署ID:" + processDefinition.getDeploymentId());
   });
}

流程定义删除

java 复制代码
@Test
public void deleteDefinition(){
    // 获取流程引擎对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 获取仓库 RepositoryService
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 调用 API 来实现流程定义的删除
    // 注意:参数为部署 ID。

    // 需要删除的部署ID
    String deployId = "17501";
    // 删除时,若该流程定义下没有正在执行的流程实例,则可以直接删除。
    // repositoryService.deleteDeployment(deployId);
    // 删除时,若该流程定义下存在正在执行的流程实例,则会跑异常。
    // 需要添加第二个参数。 本质上就是更改数据库外键的表处理策略为级联删除。
    repositoryService.deleteDeployment(deployId,true);
}

流程资源下载

java 复制代码
@Test
public void testDownloadFile() throws Exception {
   // 获取流程引擎对象
   ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
   // 获取仓库 RepositoryService
   RepositoryService repositoryService = processEngine.getRepositoryService();
   // 查询对应的流程定义
   ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
           .processDefinitionKey("流程定义名称")
           .latestVersion()
           .singleResult();
   // 获取到流程定义内的  文件名 + 部署ID
   String xmlFileName = processDefinition.getResourceName();
   String pngFileName = processDefinition.getDiagramResourceName();
   String deploymentId = processDefinition.getDeploymentId();
   // 调用 API 来获取到资源  -->  获取 InputStream
   InputStream xmlInputStream = repositoryService.getResourceAsStream(deploymentId, xmlFileName);
   InputStream pngInputStream = repositoryService.getResourceAsStream(deploymentId, pngFileName);
   // 定义一个 FileOutputStream 指定输出位置及文件名
   FileOutputStream xmlFileOutputStream =
           new FileOutputStream("绝对路径\\文件名.文件扩展名");
   FileOutputStream pngFileOutputStream =
           new FileOutputStream("绝对路径\\文件名.文件扩展名");
   // 实现下载(通过 commons-io) 来实现文件下载
   IOUtils.copy(xmlInputStream,xmlFileOutputStream);
   IOUtils.copy(pngInputStream,pngFileOutputStream);
   // 释放资源
   pngFileOutputStream.close();
   xmlFileOutputStream.close();
   pngInputStream.close();
   xmlInputStream.close();
}

流程定义版本问题

  • 当部署的两个流程定义的文件ID是相同的,不会进行覆盖,也不会抛异常;会存在两条流程定义记录
  • 使用version字段来进行区分,后部署的版本号更大
  • 启动时按照版本号大的启动
  • 当低版本的流程定义已经存在正在执行的流程实例,按照旧版本的流程定义来进行
  • 新发起的流程实例,按照版本号大的流程定义来执行

2.流程实例相关

BusinessKey(业务标识)

  • 也就是某个员工提交的一个报销流程的id,在启动流程实例之前,将 BusinessKey 绑定到流程实例中

  • 绑定BusinessKey

java 复制代码
// 启动流程 -->  绑定BusinessKey
@Test
public void testStartInstanceBindBusinessKey(){
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = processEngine.getRuntimeService();
    // 模拟业务Key
    String businessKey = "2312";
    // 启动流程实例并绑定BusinessKey
    ProcessInstance processInstance =
            runtimeService.startProcessInstanceByKey("六流程定义名称",businessKey);
    System.out.println("流程实例ID:" + processInstance.getId());            // 获取ID
}
  • 可以查询到当前任务 --> 通过当前任务获取到对应的流程实例 id --> 查询到流程实例对象 --> 获取到绑定的BusinessKey
java 复制代码
// 获取 BusinessKey
@Test
 public void testQueryTaskAndBusinessKey(){
     // 获取流程引擎对象
     ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
     // 获取任务Service
     TaskService taskService = processEngine.getTaskService();
     // 获取运行时 RuntimeService
     RuntimeService runtimeService = processEngine.getRuntimeService();
     // 审批人ID
     String userId = "张三";
     // 查询待办任务
     List<Task> taskList = taskService.createTaskQuery()
             .processDefinitionKey("leaveProcess")
             .taskAssignee(userId)
             .list();
     // 遍历任务
     taskList.forEach(task -> {
         // 获取每个任务的流程实例 ID
         String processInstanceId = task.getProcessInstanceId();
         // 查询对应的流程实例对象
         ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                 .processInstanceId(processInstanceId)
                 .singleResult();
         if(processInstance != null && processInstance.getBusinessKey() != null){
             System.out.println("任务id:" + task.getId() + ",任务名称:" + task.getName() +
                     "的Businesskey为:" + processInstance.getBusinessKey());
         } else {
             System.out.println("不存在 BusinessKey");
         }
     });
 }

流程定义挂起/激活

java 复制代码
//挂起/激活流程定义
@Test
public void testSuspendProcessDefinition(){
    // 获取流程引擎对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 获取仓库 RepositoryService
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 查询流程定义
    ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
            .processDefinitionKey("leaveProcess")
            .latestVersion()
            .singleResult();
    // 查询当前流程定义的状态(是否被挂起)
    System.out.println("当前流程定义是否被挂起:" + processDefinition.isSuspended());
    if(processDefinition.isSuspended()){
        // 当前流程定义已被挂起,我们需要激活流程定义
        repositoryService.activateProcessDefinitionById(processDefinition.getId());
    } else {
        // 当前流程定义未被挂起,我们需要挂起流程定义
        // 第一个参数:流程定义ID   第二个参数:是否挂起对应的所有流程实例    第三个参数:挂起时间,null为立即挂起。
		repositoryService.suspendProcessDefinitionById(processDefinition.getId(),true,null);
    }
}

流程实例挂起/激活

java 复制代码
@Test
public void suspendInstance(){
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = processEngine.getRuntimeService();
    ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId("17501").singleResult();
    if (processInstance.isSuspended()) {
        //激活
        runtimeService.activateProcessInstanceById(processInstance.getId());
    }else {
        //挂起
        runtimeService.suspendProcessInstanceById(processInstance.getId());
    }
}

3.任务分配负责人

任务分配负责人,可以直接在流程图中固定,也可以选择UEL表达式${变量名}进行赋值

java 复制代码
@Test
public void assigneeBind(){
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

    RuntimeService runtimeService = processEngine.getRuntimeService();
    Map<String,Object> map = new HashMap<>();
    map.put("assignee1","主管1");
    map.put("assignee2","财务1");

    runtimeService.startProcessInstanceByKey("leaveProcessBind",map);
}

4.任务候选人

查询候选人待办任务

java 复制代码
@Test
public void candidate(){
     ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
     RepositoryService repositoryService = processEngine.getRepositoryService();
     repositoryService.createDeployment().addClasspathResource("bpmn/文件名.bpmn20.xml").deploy();
     RuntimeService runtimeService = processEngine.getRuntimeService();
     ProcessInstance leaveProcess = runtimeService.startProcessInstanceByKey("流程定义名称");
     System.out.println("流程实例id = " + leaveProcess.getId());
     //查询候选人任务,传入候选人名称,查询待办
     TaskService taskService = processEngine.getTaskService();
     for (Task task : taskService.createTaskQuery().processDefinitionKey("leaveProcess")
             .taskCandidateUser("经理1").list()) {
         System.out.println("task.getId() = " + task.getId());
         System.out.println("任务名称=" + task.getName()+",任务负责人:"+ task.getAssignee());
     }
 }

为候选人分配为任务负责人

java 复制代码
//分配候选人为负责人,其他候选人失效
@Test
 public void claim(){
     ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
     TaskService taskService = processEngine.getTaskService();
     taskService.claim("27508","经理1");
 }

六、网关

1.排他网关

  • 核心作用‌:流程走到网关时,‌只选择一条符合条件的路径‌继续执行。如果多条路径都满足条件,会按定义顺序选第一条
  • 现在的插件模式:两条连线,谁先画的谁先执行
  • 若创建了流程变量,但没有赋值,在流程启动时,不会报错,在走到该流程,有应用时会报错,找不到该变量
  • 若连线上的条件都不满足,会抛异常,显示找不到出口

2.并行网关

  • 并行网关不会解析条件。即使顺序流中定义了条件,也会被忽略
  • 并行网关的功能是基于进入和外出的顺序流的。网关内的节点必须都审批通过后,流程才会继续执行
  • 若连线上创建了流程变量但没有赋值,并行网关会同时指向创建的每个任务
  • 若连线上的条件都满足或都不满足,会不解析流程变量

3.包含网关

  • 包含网关可以看做是排他网关和并行网关的结合体
  • 分支时,会执行所有条件为真的路径;汇聚时,会等待所有已触发的路径都到达后才继续
  • 若没有定义流程变量,程序启动不会报错,会在执行到流程时报错,显示未找到该变量
  • 若没有符合条件的分支,则走没有条件的分支
相关推荐
Z_W_H_1 小时前
minikube start --driver=docker --memory=4000mb出现PROVIDER_DOCKER_NEWGRP
java·docker·minikube
折哥的程序人生 · 物流技术专研1 小时前
第3篇:手写促销策略引擎(满减、打折、立减)
java·设计模式·策略模式·开闭原则·编程实战·扩充系列·促销引擎
郑州光合科技余经理2 小时前
海外外卖平台源码改造实战——多语言核心代码实现
java·开发语言·前端·后端·mysql·架构·php
耀耀_很无聊2 小时前
12_Redis Lua 脚本踩坑:为什么 tonumber(ARGV[x]) 会得到 nil?
java·redis·lua
奶油话梅糖2 小时前
锐捷常用命令速查表
java·网络·windows
code_std2 小时前
java WebSocket 使用
java·开发语言·websocket
落叶-IT3 小时前
Java Scanner 类精讲:控制台交互
java·开发语言
chh5633 小时前
C++--string
java·开发语言·网络·c++·学习
Listen·Rain3 小时前
向量化详解
java·人工智能·spring·机器学习·ai