应公司要求,需要研究项目中的框架 activiti7 ,所以有了下面这些内容。
本文篇幅巨长,想简单点儿的可以从下面的链接去专题查看:
(后续只更新专题的内容,总篇幅太长不动了)
入门代码使用
业务实战代码使用
入门使用 mysql
业务实战使用 kingbase8
下面流程中涉及两个人和三个人的大致所需步骤:
报销流程和请假流程、采购—>入库
结论:若没有 Activiti ,我们会过度关心业务需求,且流程环节无法进行有机组合,不够灵活。
很多博客、教学视频都会说,用了工作流后,不管流程怎么变,你都不需要改代码,这其实是错误的,因为我们的流程充满了业务,改了流程环节,必定牵扯到业务,所以肯定会进行代码上的修改的。特别是你新增了步骤,修改了业务数据,这些都是必须要改代码的。
如果你每个步骤都是进行同样的操作,只是数据的值不同,就像请假的两次审核,还真是可以做到不管怎么变流程,都不用改代码。
Activiti 最大的作用是让我们不必“过分关注业务需求”,自动化让我们更多的关注于流程节点的合理性设计。
Activiti 是一套使用人数最多的、技术成熟的、适应多种数据库的工作流框架。所有需要流程管理的地方都要用到工作流。下面是一些相关的官方地址,点击进入即可获得:
Activiti 官网
Activiti 官方入门教程:Activiti User Guide
在线画流程图
Activiti 数据库表结构
BPMN2.0相对于BPMN1.0最大的区别就是定义、规范了流程引擎的执行语义和格式,利用标准的图元描述真实的业务发生过程,保证相同的流程在不同的流程引擎中得到一致的执行结果。在2.0的这套标准中,主要对流程执行定义了三类基本要素,分别为Activities(活动)、Gateways(网关)、Events(事件)。
空开始事件技术上意味着没有指定启动流程实例的触发条件。 这就是说引擎不能预计什么时候流程实例会启动。 空开始事件用于,当流程实例要通过API启动的场景, 通过调用startProcessInstanceByXXX方法,子流程都有一个空开始事件
ProcessInstance processInstance = runtimeService.startProcessInstanceByXXX();
定时开始事件用来在指定的时间创建流程实例。 它可以同时用于只启动一次的流程 和应该在特定时间间隔启动多次的流程,子流程不能使用定时开始事件,定时开始事件在流程发布后就会开始计算时间。 不需要调用startProcessInstanceByXXX,当包含定时开始事件的新版本流程部署时, 对应的上一个定时器就会被删除。
其中timeDuration是指定定时器之前要等待多长时间;timeDate是使用 ISO 8601 格式指定一个确定的时间,触发事件的时间;timeCycle指定重复执行的间隔, 可以用来定期启动流程实例,或为超时时间发送多个提醒。
消息开始事件可以用其使用一个命名的消息来启动流程实例, 这样可以帮助我们使用消息名称来选择正确的开始事件。
注意:
消息开始事件的名称在给定流程定义中不能重复。流程定义不能包含多个名称相同的消息开始事件。 如果两个或以上消息开始事件应用了相同的事件,或两个或以上消息事件引用的消息名称相同,activiti会在发布流程定义时抛出异常。
消息开始事件的名称在所有已发布的流程定义中不能重复。 如果一个或多个消息开始事件引用了相同名称的消息,而这个消息开始事件已经部署到不同的流程定义中, activiti就会在发布时抛出一个异常。
流程版本:在发布新版本的流程定义时,之前订阅的消息订阅会被取消。 如果新版本中没有消息事件也会这样处理。
ProcessInstance startProcessInstanceByMessage(String messageName);
ProcessInstance startProcessInstanceByMessage(String messageName, Map<String, Object> processVariables);
ProcessInstance startProcessInstanceByMessage(String messageName, String businessKey, Map<String, Object< processVariables);
错误开始事件可以用来触发一个事件子流程, 错误开始事件不能用来启动流程实例,错误开始事件都是中断事件。
信号开始事件,可以用来通过一个已命名的信号(signal)来启动一个流程实例。 信号可以在流程实例内部使用“中间信号抛出事务”触发, 也可以通过API(runtimService.signalEventReceivedXXX 方法)触发。两种情况下, 所有流程实例中拥有相同名称的signalStartEvent都会启动。
空结束事件意味着到达事件时不会指定抛出的结果。 这样,引擎会直接结束当前执行的分支,不会做其他事情。
当流程执行到错误结束事件, 流程的当前分支就会结束,并抛出一个错误。 这个错误可以被对应的中间边界错误事件捕获。 如果找不到匹配的边界错误事件,就会抛出一个异常。
终止结束事件表示为结束事件,具有terminateEventDefinition子元素。terminateAll属性是可选的,默认情况下为false。可以添加可选属性terminateAll。如果为true,则无论在流程定义中是否放置终止结束事件,并且无论是否处于子流程(甚至是嵌套)、(根)流程实例都将终止。
取消结束事件只能与BPMN事务子流程结合使用。 当到达取消结束事件时,会抛出取消事件,它必须被取消边界事件捕获。 取消边界事件会取消事务,并触发补偿机制。
关于边界事件,都是捕获事件,它会附在一个环节上,边界事件不可能触发事件,这意味着,当节点运行时, 事件会监听对应的触发类型。 当事件被捕获,节点就会中断, 同时执行事件的后续连线。
定时边界事件就是一个暂停等待警告的时钟。当流程执行到绑定了边界事件的环节, 会启动一个定时器。 当定时器触发时(比如,一定时间之后),环节就会中断, 并沿着定时边界事件的外出连线继续执行。
节点边界上的中间捕获错误事件, 或简写成边界错误事件, 它会捕获节点范围内抛出的错误。定义一个边界错误事件,大多用于内嵌子流程, 或调用节点,对于子流程的情况,它会为所有内部的节点创建一个作用范围。 错误是由错误结束事件抛出的。 这个错误会传递给上层作用域,直到找到一个错误事件定义向匹配的边界错误事件。当捕获了错误事件时,边界任务绑定的节点就会销毁, 也会销毁内部所有的执行分支 (比如,同步节点,内嵌子流程,等等)。 流程执行会继续沿着边界事件的外出连线继续执行。
在事务性子流程的边界上的中间捕获取消, 或简称为边界取消事件, 当事务取消时触发。当取消边界事件触发时,首先中断当前作用域的所有执行。 然后开始补偿事务内的所有激活的补偿边界事件。 补偿是同步执行的。例如,离开事务钱,边界事务会等待补偿执行完毕。 当补偿完成后,事务子流程会沿着取消边界事务的外出连线继续执行。
节点边界的中间捕获补偿, 或简称为补偿边界事件, 可以用来设置一个节点的补偿处理器。补偿边界事件必须使用直接引用设置唯一的补偿处理器。补偿边界事件与其他边界事件的策略不同。 其他边界事件(比如信号边界事件)当到达关联的节点就会被激活。 离开节点时,就会挂起,对应的事件订阅也会取消。 补偿边界事件则不同。补偿边界事件在关联的节点成功完成时激活。 当补偿事件触发或对应流程实例结束时,事件订阅才会删除。
信号边界事件, 它会捕获信号定义引用的相同信号名的信号。与其他事件(比如边界错误事件)不同,边界信号事件不只捕获 它绑定方位的信号。信号事件是一个全局的范围(广播语义),就是说信号可以在任何地方触发, 即便是不同的流程实例。捕获信号后,不会停止信号的传播。 如果你有两个信号边界事件,它们捕获相同的信号事件,两个边界事件都会被触发, 即使它们在不同的流程实例中。
定时中间事件作为一个监听器。当执行到达捕获事件节点, 就会启动一个定时器。 当定时器触发(比如,一段时间之后),流程就会沿着定时中间事件的外出节点继续执行。
中间捕获信号事件 通过引用信号定义来捕获相同信号名称的信号。与其他事件(比如错误事件)不同,信号不会在捕获之后被消费。 如果你有两个激活的信号边界事件捕获相同的信号事件,两个边界事件都会被触发, 即便它们在不同的流程实例中。
中间触发信号事件为定义的信号抛出一个信号事件。在activiti中,信号会广播到所有激活的处理器中(比如,所以捕获信号事件)。 信号可以通过同步和异步方式发布。
中间触发补偿事件 可以用来触发补偿。触发补偿是指补偿可以由特定节点或包含补偿事件的作用域触发。 补偿是通过分配给节点的补偿处理器来完成的。
在工作流Activiti的使用中,任务是不可或缺的元素,通过各种任务,来完成作业系统中各个环节的执行,任务分为用户任务、脚本任务、Java服务任务、邮件任务、手工任务、业务规则任务、调用活动(子流程)任务,下面就一一介绍。
用户任务用来设置必须由人员完成的工作。 当流程执行到用户任务,会创建一个新任务, 并把这个新任务加入到分配人或群组的任务列表中。
脚本任务是一个自动节点,当流程到达脚本任务, 会执行对应的脚本。
activiti强化了业务流程,支持了自动邮件任务,它可以发送邮件给一个或多个参与者, 包括支持cc, bcc, HTML内容等等。activiti引擎要通过支持SMTP功能的外部邮件服务器发送邮件。 为了实际发送邮件,引擎需知道如何访问邮件服务器。在activiti.cfg.xml配置文件中配置:
mailServerHost—邮件服务器的主机名
mailServerPort—邮件服务器上的SMTP传输端口。默认为25
mailServerDefaultFrom—如果用户没有指定发送邮件的邮件地址,默认设置的发送者的邮件地址。
mailServerUsername—邮件服务器认证用户名
mailServerPassword—邮件服务器认证密码
mailServerUseSSL—ssl交互。默认为false
mailServerUseTLS—是否需要支持TLS。默认为false。
手工任务定义了BPM引擎外部的任务。 用来表示工作需要某人完成,而引擎不需要知道,也没有对应的系统和UI接口。 对于引擎,手工任务是直接通过的活动, 流程到达它之后会自动向下执行。
接收任务是一个简单任务,它会等待对应消息的到达。 当前,我们只实现了这个任务的java语义。 当流程达到接收任务,流程状态会保存到存储里。 意味着流程会等待在这个等待状态, 直到引擎接收了一个特定的消息, 这会触发流程穿过接收任务继续执行。
业务规则用户用来同步执行一个或多个规则。activiti使用drools规则引擎执行业务规则。 目前,包含业务规则的.drl文件必须和流程定义一起发布,流程定义里包含了执行这些规则的业务规则任务。 意味着流程使用的所有.drl文件都必须打包在流程BAR文件里,比如任务表单。
调用节点引用流程定义外部的一个流程,使用调用节点的主要场景是需要重用流程定义, 这个流程定义需要被很多其他流程定义调用的时候。当流程执行到调用节点,会创建一个新分支,它是到达调用节点的流程的分支。 这个分支会用来执行子流程,默认创建并行子流程,就像一个普通的流程。 上级流程会等待子流程完成,然后才会继续向下执行。
网关用来控制流程的流向, 网关可以消费也可以生成token。网关显示成菱形图形,内部有有一个小图标。 图标表示网关的类型。
网关也可以表示流程中的并行情况。并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起。并行网关的功能是基于进入和外出的顺序流的:
分支: 并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
汇聚: 所有到达并行网关,在此等待的进入分支, 直到所有进入顺序流的分支都到达以后, 流程就会通过汇聚网关。
如果同一个并行网关有多个进入和多个外出顺序流, 它就同时具有分支和汇聚功能。 这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。与其他网关的主要区别是,并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略。
排他网关(也叫异或(XOR)网关,或更技术性的叫法 基于数据的排他网关), 用来在流程中实现决策。 当流程执行到这个网关,所有外出顺序流都会被处理一遍。 其中条件解析为true的顺序流(或者没有设置条件,概念上在顺序流上定义了一个’true’) 会被选中,让流程继续运行。
包含网关可以看做是排他网关和并行网关的结合体。 和排他网关一样,你可以在外出顺序流上定义条件,包含网关会解析它们。 但是主要的区别是包含网关可以选择多于一条顺序流,这和并行网关一样。包含网关的功能是基于进入和外出顺序流的:
分支: 所有外出顺序流的条件都会被解析,结果为true的顺序流会以并行方式继续执行, 会为每个顺序流创建一个分支。
汇聚: 所有并行分支到达包含网关,会进入等待, 直到每个包含流程token的进入顺序流的分支都到达。 这是与并行网关的最大不同。包含网关只会等待被选中执行了的进入顺序流。 在汇聚之后,流程会穿过包含网关继续执行。
如果同一个包含节点拥有多个进入和外出顺序流, 它就会同时含有分支和汇聚功能。 这时,网关会先汇聚所有拥有流程token的进入顺序流, 再根据条件判断结果为true的外出顺序流,为它们生成多条并行分支。
基于事件网关允许根据事件判断流向。网关的每个外出顺序流都要连接到一个中间捕获事件。 当流程到达一个基于事件网关,网关会进入等待状态:会暂停执行。 与此同时,会为每个外出顺序流创建相对的事件订阅。
注意基于事件网关的外出顺序流和普通顺序流不同。这些顺序流不会真的”执行”。 相反,它们让流程引擎去决定执行到基于事件网关的流程需要订阅哪些事件。 要考虑以下条件:
activiti的引擎配置文件,包括:ProcessEngineConfiguration的定义、数据源定义、事务管理器等。此文件其实就是一个spring配置文件。
通过 ProcessEngineConfiguration 可以创建工作流引擎 ProceccEngine,该类主要负责读取 activiti.cfg.xml 的配置。
public static ProcessEngineConfiguration createProcessEngineConfigurationFromResourceDefault() {
return createProcessEngineConfigurationFromResource("activiti.cfg.xml", "processEngineConfiguration");
}
相当于一个门面接口,通过ProcessEngineConfiguration创建processEngine,通过ProcessEngine创建各个service接口,具体代码逻辑由 ProcessEngineImpl.class 实现。
public interface ProcessEngine {
String VERSION = "7.0.0.0";
String getName();
void close();
RepositoryService getRepositoryService();
RuntimeService getRuntimeService();
TaskService getTaskService();
HistoryService getHistoryService();
ManagementService getManagementService();
DynamicBpmnService getDynamicBpmnService();
ProcessEngineConfiguration getProcessEngineConfiguration();
}
Activiti 流程框架有一套自己的数据库表,一共25张表,通过这些表从而实现流程管理。
这是官方数据库表的解释,文档适用于Activiti5~6:Activiti 数据库表结构
<properties>
<slf4j.version>1.6.6slf4j.version>
<log4j.version>1.2.12log4j.version>
<activiti.version>7.0.0.SR1activiti.version>
properties>
<dependencies>
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-engineartifactId>
<version>${activiti.version}version>
dependency>
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-springartifactId>
<version>${activiti.version}version>
dependency>
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-bpmn-modelartifactId>
<version>${activiti.version}version>
dependency>
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-bpmn-converterartifactId>
<version>${activiti.version}version>
dependency>
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-json-converterartifactId>
<version>${activiti.version}version>
dependency>
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-bpmn-layoutartifactId>
<version>${activiti.version}version>
dependency>
<dependency>
<groupId>org.activiti.cloudgroupId>
<artifactId>activiti-cloud-services-apiartifactId>
<version>7.0.0.Beta1version>
<exclusions>
<exclusion>
<artifactId>mybatisartifactId>
<groupId>org.mybatisgroupId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.27version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.4.5version>
dependency>
<dependency>
<groupId>commons-dbcpgroupId>
<artifactId>commons-dbcpartifactId>
<version>1.4version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>${log4j.version}version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>${slf4j.version}version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>${slf4j.version}version>
dependency>
dependencies>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.1.181/activiti?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="test@xhkj"/>
<property name="maxActive" value="3"/>
<property name="maxIdle" value="1"/>
bean>
<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="dataSource" ref="dataSource"/>
<property name="databaseSchemaUpdate" value="true"/>
bean>
直接见代码,相关注释,代码里都很详细
package com.activiti.test;
import org.activiti.engine.*;
import org.junit.Test;
public class ActivitiCreate {
/**
* 使用activiti提供的默认方式来创建mysql的表
*/
@Test
public void testCreateDbTable1() {
// 需要使用avtiviti提供的工具类 ProcessEngines ,使用方法getDefaultProcessEngine
// getDefaultProcessEngine会默认从resources下读取名字为activiti.cfg.xml的文件
// 创建processEngine时,就会创建mysql的表
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
System.out.println(processEngine);
}
@Test
public void testGetService() {
//获取各个service实例
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
RuntimeService runtimeService = processEngine.getRuntimeService();
TaskService taskService = processEngine.getTaskService();
HistoryService historyService = processEngine.getHistoryService();
}
/**
* 一般创建方式
*/
@Test
public void testCreateDbTable2() {
// ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
// 配置文件的名字可以自定义,bean的名字也可以自定义
ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml","processEngineConfiguration");
// 获取流程引擎对象
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
}
}
直接见代码,相关注释,代码里都很详细
package com.activiti.test;
import org.activiti.engine.*;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.List;
public class ActivitiDemo {
/**
* 流程部署
* `act_re_deployment` 流程部署表,每部署一次会增加一条记录
* `act_re_procdef` 流程定义表,张三会申请出差流程,李四也会申请出差流程,每个人申请都会增加一条记录
* act_re_deployment与act_re_procdef是一对多关系
* `act_ge_bytearray` 流程资源表,每个资源都会增加一条记录
*/
@Test
public void testDeployment() throws FileNotFoundException {
String evectionBpmnPath = ActivitiDemo.class.getClassLoader().getResource("bpmn/evection.bpmn").getFile();
File evectionBpmnFile = new File(evectionBpmnPath);
InputStream evectionBpmnIs = new FileInputStream(evectionBpmnFile);
String evectionPngPath = ActivitiDemo.class.getClassLoader().getResource("bpmn/evection.png").getFile();
File evectionPngFile = new File(evectionPngPath);
InputStream evectionPngIs = new FileInputStream(evectionPngFile);
// 1、创建ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2、获取RepositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
// 3、使用service进行流程的部署,定义一个流程的名字,把bpmn和png部署到数据中
Deployment deploy = repositoryService.createDeployment()
.name("出差申请流程")
.addInputStream("evection.bpmn", evectionBpmnIs)
.addInputStream("evection.png", evectionPngIs)
.deploy();
// 4、输出部署信息
System.out.println("流程部署id=" + deploy.getId());
System.out.println("流程部署名字=" + deploy.getName());
}
/**
* 启动流程实例
* `act_hi_actinst` 流程实例执行历史信息
* `act_hi_identitylink` 流程参与用户的历史信息
* `act_hi_procinst` 流程实例的历史信息
* `act_hi_taskinst` 流程任务的历史信息
* `act_ru_execution` 流程执行信息
* `act_ru_identitylink` 流程的正在参与用户信息
* `act_ru_task` 流程当前任务信息
*/
@Test
public void testStartProcess() {
// 1、创建ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2、获取RunTimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 3、根据流程定义的id启动流程
ProcessInstance instance = runtimeService.startProcessInstanceByKey("myEvection");
// 4、输出内容
System.out.println("流程定义ID:" + instance.getProcessDefinitionId());
System.out.println("流程实例ID:" + instance.getId());
System.out.println("当前活动的ID:" + instance.getActivityId());
}
/**
* 查询个人待执行的任务
*/
@Test
public void testFindPersonalTaskList() {
// 1、获取流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2、获取taskService
TaskService taskService = processEngine.getTaskService();
// 3、根据流程key 和 任务的负责人 查询任务
List<Task> taskList = taskService.createTaskQuery()
.processDefinitionKey("myEvection") //流程Key
.taskAssignee("李四") //要查询的负责人
.list();
// 4、输出
for (Task task : taskList) {
System.out.println("流程实例id=" + task.getProcessInstanceId());
System.out.println("任务Id=" + task.getId());
System.out.println("任务负责人=" + task.getAssignee());
System.out.println("任务名称=" + task.getName());
}
}
/**
* 完成个人任务 : 创建出差申请
* `act_ru_task` 这里的 '创建出差申请' 会变为 '经理审批'
* 也就是也是下一个任务: 经理审批
* `act_hi_taskinst` 这里的'创建出差申请'这条记录会有开始时间和结束时间
* 然后还会增加一条记录 '经理审批'
*/
@Test
public void completeTask() {
// 获取引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取操作任务的服务 TaskService
TaskService taskService = processEngine.getTaskService();
// 完成任务,参数:任务id,完成 '张三' 的任务
taskService.complete("2505");
}
// 改造: 不把id写死,通过查询,然后执行任务
// 完成经理'李四'的任务
@Test
public void completeTask2() {
// 获取引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取操作任务的服务 TaskService
TaskService taskService = processEngine.getTaskService();
// 完成 经理 '李四' 的任务
Task task = taskService.createTaskQuery()
.processDefinitionKey("myEvection")
.taskAssignee("李四")
.singleResult();
System.out.println("流程实例id=" + task.getProcessInstanceId());
System.out.println("任务Id=" + task.getId());
System.out.println("任务负责人=" + task.getAssignee());
System.out.println("任务名称=" + task.getName());
taskService.complete(task.getId());
}
// 完成总经理'老马'的任务
@Test
public void completeTask3() {
// 获取引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取操作任务的服务 TaskService
TaskService taskService = processEngine.getTaskService();
// 完成 经理 '李四' 的任务
Task task = taskService.createTaskQuery()
.processDefinitionKey("myEvection")
.taskAssignee("老马")
.singleResult();
System.out.println("流程实例id=" + task.getProcessInstanceId());
System.out.println("任务Id=" + task.getId());
System.out.println("任务负责人=" + task.getAssignee());
System.out.println("任务名称=" + task.getName());
taskService.complete(task.getId());
}
// 完成财务'小王'的任务
@Test
public void completeTask4() {
// 获取引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取操作任务的服务 TaskService
TaskService taskService = processEngine.getTaskService();
// 完成 经理 '李四' 的任务
Task task = taskService.createTaskQuery()
.processDefinitionKey("myEvection")
.taskAssignee("小王")
.singleResult();
System.out.println("流程实例id=" + task.getProcessInstanceId());
System.out.println("任务Id=" + task.getId());
System.out.println("任务负责人=" + task.getAssignee());
System.out.println("任务名称=" + task.getName());
taskService.complete(task.getId());
}
// 如果一个流程中有多个任务,可以用list
@Test
public void completeTask5() {
// 获取引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取操作任务的服务 TaskService
TaskService taskService = processEngine.getTaskService();
// 完成 经理 '李四' 的任务
List<Task> tasks = taskService.createTaskQuery()
.processDefinitionKey("myEvection")
.taskAssignee("李四")
.list(); // 多个任务,用list
for (Task task : tasks) {
if (true) {
// 判断是哪任务
System.out.println("流程实例id=" + task.getProcessInstanceId());
System.out.println("任务Id=" + task.getId());
System.out.println("任务负责人=" + task.getAssignee());
System.out.println("任务名称=" + task.getName());
taskService.complete(task.getId());
}
}
}
}
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="http://www.activiti.org/testm1625990337688" xmlns:xsd="http://www.w3.org/2001/XMLSchema" id="m1625990337688" name="" targetNamespace="http://www.activiti.org/testm1625990337688">
<process id="zhanleai_approve_base" name="测试出差申请流程" processType="None" isClosed="false" isExecutable="true">
<documentation />
<startEvent id="_2" name="StartEvent" />
<userTask id="_3" name="创建出差申请" activiti:assignee="张三" activiti:exclusive="true">
<documentation />
<extensionElements>
<activiti:taskListener event="create" class="com.gh.maintenance.controller.activiti_example.ActivitiApproveListener" />
extensionElements>
userTask>
<userTask id="_4" name="经理审批" activiti:assignee="李四" activiti:exclusive="true">
<documentation />
<extensionElements />
userTask>
<userTask id="_5" name="总经理审批" activiti:assignee="老马" activiti:exclusive="true">
<documentation />
<extensionElements />
userTask>
<userTask id="_6" name="财务审批" activiti:assignee="小王" activiti:exclusive="true" />
<endEvent id="_7" name="EndEvent" />
<sequenceFlow id="_8" sourceRef="_2" targetRef="_3" />
<sequenceFlow id="_9" sourceRef="_4" targetRef="_5" />
<sequenceFlow id="_10" sourceRef="_5" targetRef="_6" />
<sequenceFlow id="_11" sourceRef="_6" targetRef="_7" />
<sequenceFlow id="_12" sourceRef="_3" targetRef="_4" />
process>
<bpmndi:BPMNDiagram id="Diagram-_1" name="New Diagram" documentation="background=#3C3F41;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0">
<bpmndi:BPMNPlane bpmnElement="zhanleai_approve_base">
<bpmndi:BPMNEdge id="BPMNEdge__12" bpmnElement="_12" sourceElement="_3" targetElement="_4">
<di:waypoint x="650" y="13" />
<di:waypoint x="700" y="13" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="0" height="0" />
bpmndi:BPMNLabel>
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge__11" bpmnElement="_11" sourceElement="_6" targetElement="_7">
<di:waypoint x="1110" y="13" />
<di:waypoint x="1162" y="13" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="0" height="0" />
bpmndi:BPMNLabel>
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge__10" bpmnElement="_10" sourceElement="_5" targetElement="_6">
<di:waypoint x="960" y="13" />
<di:waypoint x="1010" y="13" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="0" height="0" />
bpmndi:BPMNLabel>
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge__9" bpmnElement="_9" sourceElement="_4" targetElement="_5">
<di:waypoint x="800" y="13" />
<di:waypoint x="860" y="13" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="0" height="0" />
bpmndi:BPMNLabel>
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge__8" bpmnElement="_8" sourceElement="_2" targetElement="_3">
<di:waypoint x="501" y="13" />
<di:waypoint x="550" y="13" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="0" height="0" />
bpmndi:BPMNLabel>
bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Shape-_2" bpmnElement="_2">
<dc:Bounds x="465" y="-5" width="32" height="32" />
<bpmndi:BPMNLabel>
<dc:Bounds x="393" y="6" width="53" height="14" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape-_3" bpmnElement="_3">
<dc:Bounds x="550" y="-27" width="100" height="80" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="85" height="55" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape-_4" bpmnElement="_4">
<dc:Bounds x="700" y="-27" width="100" height="80" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="85" height="55" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape-_5" bpmnElement="_5">
<dc:Bounds x="860" y="-27" width="100" height="80" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="85" height="55" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape-_6" bpmnElement="_6">
<dc:Bounds x="1010" y="-27" width="100" height="80" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="85" height="55" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape-_7" bpmnElement="_7">
<dc:Bounds x="1162" y="-5" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1215" y="6" width="49" height="14" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
bpmndi:BPMNPlane>
bpmndi:BPMNDiagram>
definitions>
/**
* 启动流程实例
* `act_hi_actinst` 流程实例执行历史信息
* `act_hi_identitylink` 流程参与用户的历史信息
* `act_hi_procinst` 流程实例的历史信息
* `act_hi_taskinst` 流程任务的历史信息
* `act_ru_execution` 流程执行信息
* `act_ru_identitylink` 流程的正在参与用户信息
* `act_ru_task` 流程当前任务信息
*/
@RequestMapping("/startProcess")
public String startProcess(String key) {
// 1、根据流程定义的id启动流程
ProcessInstance instance = runtimeService.startProcessInstanceByKey(key);
// 2、输出内容
log.info("流程定义ID:" + instance.getProcessDefinitionId());
log.info("流程实例ID:" + instance.getId());
log.info("当前活动的ID:" + instance.getActivityId());
return "出差申请流程启动成功!";
}
/**
* 查询个人待执行的任务
*/
@RequestMapping("/getUnfinishTaskList")
public List<Map<String,String>> getUnfinishTaskList(String key, String taskAssignee) {
// 1、根据流程key 和 任务的负责人 查询任务
List<Task> taskList = taskService.createTaskQuery()
.processDefinitionKey(key) //流程Key
.taskAssignee(taskAssignee) //要查询的负责人
.list();
List<Map<String,String>> taskMapList = new ArrayList<>();
// 2、输出
for (Task task : taskList) {
Map<String,String> taskMap = new HashMap<>();
taskMap.put("processInstanceId",task.getProcessInstanceId()); //流程实例
taskMap.put("id",task.getId()); //任务Id
taskMap.put("assignee",task.getAssignee()); //任务负责人
taskMap.put("name",task.getName()); //任务名称
//取流程中设置的变量值(基本类型)
// taskMap.put("taskAssignee", Objects.nonNull(taskService.getVariable(task.getId(),"出差申请人"))?taskService.getVariable(task.getId(),"出差申请人").toString():""); //出差申请人
// taskMap.put("days", Objects.nonNull(taskService.getVariable(task.getId(),"请假天数"))?taskService.getVariable(task.getId(),"请假天数").toString():""); //出差天数
// taskMap.put("evaluateFee", Objects.nonNull(taskService.getVariable(task.getId(),"出差预计费用"))?taskService.getVariable(task.getId(),"出差预计费用").toString():""); //出差预计费用
// taskMap.put("startDate", Objects.nonNull(taskService.getVariable(task.getId(),"出差开始日期"))?taskService.getVariable(task.getId(),"出差开始日期").toString():""); //出差开始日期
//取流程中设置的变量值(bean类型)
taskMap.put("approveInfo",Objects.nonNull(taskService.getVariable(task.getId(),"出差信息"))?JSONObject.toJSONString(taskService.getVariable(task.getId(),"出差信息")):"");
taskMapList.add(taskMap);
}
return taskMapList;
}
/**
* (以张三创建出差申请为例)
* 完成个人任务 : 创建出差申请
* `act_ru_task` 这里的 '创建出差申请' 会变为 '经理审批'
* 也就是也是下一个任务: 经理审批
* `act_hi_taskinst` 这里的'创建出差申请'这条记录会有开始时间和结束时间
* 然后还会增加一条记录 '经理审批'
*/
@RequestMapping("/completeTask")
public String completeTask(String key,String taskAssignee,String days,String evaluateFee, String startDate) {
// 查找该负责人下所有的任务
Task task = taskService.createTaskQuery()
.processDefinitionKey(key)
.taskAssignee(taskAssignee)
.singleResult();
//设置流程变量(基本类型),前提是该流程实例必须未执行完成 —— 存在 act_ru_variable 表中,对应取值即可
// taskService.setVariable(task.getId(), "请假人", taskAssignee);
// taskService.setVariable(task.getId(), "出差天数",days);
// taskService.setVariable(task.getId(), "出差预计费用", evaluateFee);
// taskService.setVariable(task.getId(), "出差开始日期", startDate);
//设置流程变量(JavaBean类型),前提是该流程实例必须未执行完成 —— 存在 act_ru_variable 表中,但是实际值存在 act_ge_bytearray 表中
ApproveInfo approveInfo = ApproveInfo.builder()
.taskAssignee(taskAssignee)
.days(days)
.evaluateFee(evaluateFee)
.startDate(startDate)
.build();
taskService.setVariable(task.getId(),"出差信息",approveInfo);
//执行流程
taskService.complete(task.getId());
return "当前," + task.getAssignee() + "已完成任务:" + task.getName() ;
}
/**
* 根据任务ID来完成任务
*/
@RequestMapping("/completeTaskByTaskId")
public String completeTaskByTaskId(String taskId) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
taskService.complete(taskId);
return "当前已完成任务:" + task.getName() ;
}
部分代码跟前面简单流程的有重复,我这部分就只贴流程图、bpmn文件和核心代码了
排他网关需要配置出口的表达式,然后通过 activiti 参数传值的形式会自动识别到该值,自动进行程序流转。
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="http://www.activiti.org/testm1625990337688" xmlns:xsd="http://www.w3.org/2001/XMLSchema" id="m1625990337688" name="" targetNamespace="http://www.activiti.org/testm1625990337688">
<process id="zhanleai_approve_reject" name="测试出差申请流程" processType="None" isClosed="false" isExecutable="true">
<documentation />
<startEvent id="_1" name="流程开始">
<documentation />
<extensionElements />
<outgoing>Flow_11ejhh5outgoing>
startEvent>
<userTask id="_2" name="创建出差申请" activiti:assignee="张三" activiti:exclusive="true">
<documentation />
<extensionElements />
<incoming>Flow_08ru13pincoming>
<incoming>Flow_11ejhh5incoming>
<outgoing>Flow_1b2032eoutgoing>
userTask>
<userTask id="_4" name="经理审批" activiti:assignee="李四" activiti:exclusive="true">
<documentation />
<extensionElements />
<incoming>Flow_1fc8hv7incoming>
<outgoing>Flow_13ro4z7outgoing>
userTask>
<userTask id="_5" name="总经理审批" activiti:assignee="老马" activiti:exclusive="true">
<documentation />
<extensionElements />
<incoming>Flow_13ro4z7incoming>
<outgoing>Flow_1jpug20outgoing>
userTask>
<userTask id="_7" name="财务审批" activiti:assignee="小王" activiti:exclusive="true">
<documentation />
<extensionElements />
<incoming>Flow_09owxd3incoming>
<outgoing>Flow_1m3z2leoutgoing>
userTask>
<sequenceFlow id="_11" sourceRef="_7" targetRef="_9" />
<userTask id="_8" name="大老板审批" activiti:assignee="老赵" activiti:exclusive="true">
<documentation />
<extensionElements />
<incoming>Flow_0p8i7yoincoming>
<outgoing>Flow_149ikimoutgoing>
<outgoing>Flow_1h2q7h5outgoing>
userTask>
<sequenceFlow id="Flow_149ikim" sourceRef="_8" targetRef="_9" />
<exclusiveGateway id="_6">
<documentation />
<incoming>Flow_1jpug20incoming>
<outgoing>Flow_09owxd3outgoing>
<outgoing>Flow_08ru13poutgoing>
exclusiveGateway>
<sequenceFlow id="Flow_1jpug20" sourceRef="_5" targetRef="_6" />
<sequenceFlow id="Flow_09owxd3" name="同意审批" sourceRef="_6" targetRef="_7">
<documentation />
<conditionExpression xsi:type="tFormalExpression">${status==1}conditionExpression>
sequenceFlow>
<sequenceFlow id="Flow_08ru13p" name="驳回申请" sourceRef="_6" targetRef="_2">
<documentation />
<conditionExpression xsi:type="tFormalExpression">${status==0}conditionExpression>
sequenceFlow>
<exclusiveGateway id="_3" name="">
<documentation />
<incoming>Flow_1b2032eincoming>
<outgoing>Flow_1fc8hv7outgoing>
<outgoing>Flow_0p8i7yooutgoing>
exclusiveGateway>
<sequenceFlow id="Flow_11ejhh5" sourceRef="_1" targetRef="_2" />
<sequenceFlow id="Flow_1b2032e" sourceRef="_2" targetRef="_3">
<documentation />
sequenceFlow>
<sequenceFlow id="Flow_1fc8hv7" name="非特殊情况" sourceRef="_3" targetRef="_4">
<documentation />
<conditionExpression xsi:type="tFormalExpression">${status==0}conditionExpression>
sequenceFlow>
<sequenceFlow id="Flow_0p8i7yo" name="特殊情况" sourceRef="_3" targetRef="_8">
<documentation />
<conditionExpression xsi:type="tFormalExpression">${status==1}conditionExpression>
sequenceFlow>
<sequenceFlow id="Flow_1h2q7h5" sourceRef="_8" targetRef="_9">
<documentation />
sequenceFlow>
<sequenceFlow id="Flow_1m3z2le" sourceRef="_7" targetRef="_9" />
<sequenceFlow id="Flow_13ro4z7" sourceRef="_4" targetRef="_5" />
<endEvent id="_9" name="EndEvent">
<documentation />
<incoming>Flow_1h2q7h5incoming>
<incoming>Flow_1m3z2leincoming>
<terminateEventDefinition id="TerminateEventDefinition_1q47fr4" />
endEvent>
process>
<bpmndi:BPMNDiagram id="Diagram-_1" name="New Diagram" documentation="background=#3C3F41;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0">
<bpmndi:BPMNPlane bpmnElement="zhanleai_approve_reject">
<bpmndi:BPMNEdge id="Flow_13ro4z7_di" bpmnElement="Flow_13ro4z7">
<di:waypoint x="485" y="310" />
<di:waypoint x="485" y="340" />
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1m3z2le_di" bpmnElement="Flow_1m3z2le">
<di:waypoint x="485" y="640" />
<di:waypoint x="485" y="750" />
<di:waypoint x="622" y="750" />
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1h2q7h5_di" bpmnElement="Flow_1h2q7h5">
<di:waypoint x="800" y="330" />
<di:waypoint x="800" y="750" />
<di:waypoint x="658" y="750" />
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0p8i7yo_di" bpmnElement="Flow_0p8i7yo">
<di:waypoint x="640" y="125" />
<di:waypoint x="640" y="190" />
<di:waypoint x="800" y="190" />
<di:waypoint x="800" y="250" />
<bpmndi:BPMNLabel>
<dc:Bounds x="698" y="165" width="44" height="14" />
bpmndi:BPMNLabel>
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1fc8hv7_di" bpmnElement="Flow_1fc8hv7">
<di:waypoint x="640" y="125" />
<di:waypoint x="640" y="190" />
<di:waypoint x="485" y="190" />
<di:waypoint x="485" y="230" />
<bpmndi:BPMNLabel>
<dc:Bounds x="535" y="165" width="55" height="14" />
bpmndi:BPMNLabel>
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1b2032e_di" bpmnElement="Flow_1b2032e">
<di:waypoint x="640" y="30" />
<di:waypoint x="640" y="75" />
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_11ejhh5_di" bpmnElement="Flow_11ejhh5">
<di:waypoint x="640" y="-82" />
<di:waypoint x="640" y="-50" />
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_08ru13p_di" bpmnElement="Flow_08ru13p">
<di:waypoint x="460" y="480" />
<di:waypoint x="360" y="480" />
<di:waypoint x="360" y="-10" />
<di:waypoint x="590" y="-10" />
<bpmndi:BPMNLabel>
<dc:Bounds x="353" y="225" width="44" height="14" />
bpmndi:BPMNLabel>
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_09owxd3_di" bpmnElement="Flow_09owxd3">
<di:waypoint x="485" y="505" />
<di:waypoint x="485" y="560" />
<bpmndi:BPMNLabel>
<dc:Bounds x="478" y="523" width="45" height="14" />
bpmndi:BPMNLabel>
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1jpug20_di" bpmnElement="Flow_1jpug20">
<di:waypoint x="485" y="420" />
<di:waypoint x="485" y="455" />
bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Shape-_2" bpmnElement="_1">
<dc:Bounds x="622" y="-118" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="618" y="-147" width="44" height="14" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape-_3" bpmnElement="_2">
<dc:Bounds x="590" y="-50" width="100" height="80" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="85" height="55" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape-_4" bpmnElement="_4">
<dc:Bounds x="435" y="230" width="100" height="80" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="85" height="55" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape-_5" bpmnElement="_5">
<dc:Bounds x="435" y="340" width="100" height="80" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="85" height="55" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape-_6" bpmnElement="_7">
<dc:Bounds x="435" y="560" width="100" height="80" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="85" height="55" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_19lsvak_di" bpmnElement="_8">
<dc:Bounds x="750" y="250" width="100" height="80" />
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_06brwuq_di" bpmnElement="_6" isMarkerVisible="true">
<dc:Bounds x="460" y="455" width="50" height="50" />
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_1izdxic_di" bpmnElement="_3" isMarkerVisible="true">
<dc:Bounds x="615" y="75" width="50" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="607" y="125" width="66" height="14" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1y7rj7q_di" bpmnElement="_9">
<dc:Bounds x="622" y="732" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="616" y="775" width="49" height="14" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
bpmndi:BPMNPlane>
bpmndi:BPMNDiagram>
definitions>
其实也就是流程图配置参数,然后调用的时候从前端传进来,后端代码需要将这个放到 variableMap 对象中,activiti 会自动根据 status 的值来判断,从而自动流转。
流程图和 bpmn 复用排他网关
用户在前端页面的操作如下:
/**
* 根据流程实例ID查询已执行的任务节点信息
*
* act_hi_taskinst:历史流程任务表
*
* @Date 2023/6/14 15:03
* @Param [processInstanceId]
* @return
**/
@RequestMapping("/getRunNodesByProcInstId")
public Map<String,String> getRunNodesByProcInstId(String processInstanceId) {
Map<String,String> map = new LinkedHashMap();
// 获取流程历史中已执行节点,并按照节点在流程中执行先后顺序排序
List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.activityType("userTask") //用户任务
.finished() //已经执行的任务节点
.orderByHistoricActivityInstanceEndTime()
.asc()
.list();
// 已执行的节点ID集合
if(Objects.nonNull(historicActivityInstanceList) && historicActivityInstanceList.size()>0){
for (HistoricActivityInstance historicActivityInstance:historicActivityInstanceList){
if(!map.containsKey(historicActivityInstance.getActivityId())){
map.put(historicActivityInstance.getActivityId(),historicActivityInstance.getActivityName());
}
}
}
return map;
}
/**
* 改变节点到指定节点
* @param rejectNodeId 指定节点ID
* @param processInstanceId 流程实例ID
* @param assignee 负责人
* @return
*/
@RequestMapping("/changeRunNode")
public String changeRunNode(String rejectNodeId, String processInstanceId, String assignee) {
// 查找该负责人下所有的任务
Task task = taskService.createTaskQuery()
.processInstanceId(processInstanceId)
.singleResult();
//如果当前节点处理人不是该用户,就无法进行改变节点操作
if (!assignee.equals(task.getAssignee())) {
return "当前用户无法改变流程节点";
}
//获取当前节点
String currActivityId = task.getTaskDefinitionKey();
String processDefinitionId = task.getProcessDefinitionId();
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
FlowNode currFlow = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currActivityId);
if (null == currFlow) {
List<SubProcess> subProcessList = bpmnModel.getMainProcess().findFlowElementsOfType(SubProcess.class, true);
for (SubProcess subProcess : subProcessList) {
FlowElement flowElement = subProcess.getFlowElement(currActivityId);
if (flowElement != null) {
currFlow = (FlowNode) flowElement;
break;
}
}
}
//获取目标节点
FlowNode targetFlow = (FlowNode) bpmnModel.getFlowElement(rejectNodeId);
//如果不是同一个流程(子流程)不能进行改变节点操作
if (!(currFlow.getParentContainer().equals(targetFlow.getParentContainer()))) {
return "不是同一个流程,无法进行改变节点操作";
}
//记录原活动方向
List<SequenceFlow> oriSequenceFlows = Lists.newArrayList();
oriSequenceFlows.addAll(currFlow.getOutgoingFlows());
//清理活动方向
currFlow.getOutgoingFlows().clear();
//建立新的方向
List<SequenceFlow> newSequenceFlows = Lists.newArrayList();
SequenceFlow newSequenceFlow = new SequenceFlow();
String uuid = UUID.randomUUID().toString().replace("-", "");
newSequenceFlow.setId(uuid);
newSequenceFlow.setSourceFlowElement(currFlow); //原节点
newSequenceFlow.setTargetFlowElement(targetFlow); //目标节点
newSequenceFlows.add(newSequenceFlow);
currFlow.setOutgoingFlows(newSequenceFlows);
//审批意见叠加
//variables 审批意见 act_ru_variable 变量表
Map<String, Object> variables = task.getProcessVariables();
variables.put("status","2");
variables.put("msg","资料填写有误,请重新填写!");
//完成节点任务
taskService.complete(task.getId(), variables);
//恢复原方向
currFlow.setOutgoingFlows(oriSequenceFlows);
return "改变流程到指定节点,已完成!";
}
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="http://www.activiti.org/testm1625990337688" xmlns:xsd="http://www.w3.org/2001/XMLSchema" id="m1625990337688" name="" targetNamespace="http://www.activiti.org/testm1625990337688">
<process id="zhanleai_approve_parallel" name="测试出差申请流程" processType="None" isClosed="false" isExecutable="true">
<documentation />
<startEvent id="_1" name="流程开始">
<documentation />
<extensionElements />
<outgoing>Flow_11ejhh5outgoing>
startEvent>
<userTask id="_2" name="创建出差申请" activiti:assignee="张三" activiti:exclusive="true">
<documentation />
<extensionElements />
<incoming>Flow_08ru13pincoming>
<incoming>Flow_11ejhh5incoming>
<outgoing>Flow_1b2032eoutgoing>
userTask>
<userTask id="_4" name="经理审批" activiti:assignee="李四" activiti:exclusive="true">
<documentation />
<extensionElements />
<incoming>Flow_1fc8hv7incoming>
<outgoing>Flow_13ro4z7outgoing>
userTask>
<userTask id="_5" name="总经理审批" activiti:assignee="老马" activiti:exclusive="true">
<documentation />
<extensionElements />
<incoming>Flow_13ro4z7incoming>
<outgoing>Flow_1jpug20outgoing>
userTask>
<userTask id="_7" name="财务审批" activiti:assignee="小王" activiti:exclusive="true">
<documentation />
<extensionElements />
<incoming>Flow_09owxd3incoming>
<outgoing>Flow_1m3z2leoutgoing>
userTask>
<sequenceFlow id="_11" sourceRef="_7" targetRef="_9" />
<userTask id="_8" name="大老板审批" activiti:assignee="老赵" activiti:exclusive="true">
<documentation />
<extensionElements />
<incoming>Flow_0p8i7yoincoming>
<outgoing>Flow_149ikimoutgoing>
<outgoing>Flow_1h2q7h5outgoing>
userTask>
<sequenceFlow id="Flow_149ikim" sourceRef="_8" targetRef="_9" />
<exclusiveGateway id="_6">
<documentation />
<incoming>Flow_1jpug20incoming>
<outgoing>Flow_09owxd3outgoing>
<outgoing>Flow_08ru13poutgoing>
exclusiveGateway>
<sequenceFlow id="Flow_1jpug20" sourceRef="_5" targetRef="_6" />
<sequenceFlow id="Flow_09owxd3" name="同意审批" sourceRef="_6" targetRef="_7">
<documentation />
<conditionExpression xsi:type="tFormalExpression">${status==1}conditionExpression>
sequenceFlow>
<sequenceFlow id="Flow_08ru13p" name="驳回申请" sourceRef="_6" targetRef="_2">
<documentation />
<conditionExpression xsi:type="tFormalExpression">${status==0}conditionExpression>
sequenceFlow>
<sequenceFlow id="Flow_11ejhh5" sourceRef="_1" targetRef="_2" />
<sequenceFlow id="Flow_1b2032e" sourceRef="_2" targetRef="_3">
<documentation />
sequenceFlow>
<sequenceFlow id="Flow_1fc8hv7" name="" sourceRef="_3" targetRef="_4">
<documentation />
sequenceFlow>
<sequenceFlow id="Flow_0p8i7yo" name="" sourceRef="_3" targetRef="_8">
<documentation />
sequenceFlow>
<sequenceFlow id="Flow_1h2q7h5" sourceRef="_8" targetRef="_9">
<documentation />
sequenceFlow>
<sequenceFlow id="Flow_1m3z2le" sourceRef="_7" targetRef="_9" />
<sequenceFlow id="Flow_13ro4z7" sourceRef="_4" targetRef="_5" />
<parallelGateway id="_3" name="">
<documentation />
<incoming>Flow_1b2032eincoming>
<outgoing>Flow_1fc8hv7outgoing>
<outgoing>Flow_0p8i7yooutgoing>
parallelGateway>
<endEvent id="_9" name="结束任务">
<documentation />
<incoming>Flow_1h2q7h5incoming>
<incoming>Flow_1m3z2leincoming>
<terminateEventDefinition id="TerminateEventDefinition_1gxwlzk" />
endEvent>
process>
<bpmndi:BPMNDiagram id="Diagram-_1" name="New Diagram" documentation="background=#3C3F41;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0">
<bpmndi:BPMNPlane bpmnElement="zhanleai_approve_parallel">
<bpmndi:BPMNEdge id="Flow_13ro4z7_di" bpmnElement="Flow_13ro4z7">
<di:waypoint x="485" y="310" />
<di:waypoint x="485" y="340" />
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1m3z2le_di" bpmnElement="Flow_1m3z2le">
<di:waypoint x="485" y="640" />
<di:waypoint x="485" y="750" />
<di:waypoint x="622" y="750" />
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1h2q7h5_di" bpmnElement="Flow_1h2q7h5">
<di:waypoint x="840" y="350" />
<di:waypoint x="840" y="750" />
<di:waypoint x="658" y="750" />
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0p8i7yo_di" bpmnElement="Flow_0p8i7yo">
<di:waypoint x="640" y="125" />
<di:waypoint x="640" y="190" />
<di:waypoint x="840" y="190" />
<di:waypoint x="840" y="270" />
<bpmndi:BPMNLabel>
<dc:Bounds x="718" y="165" width="44" height="14" />
bpmndi:BPMNLabel>
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1fc8hv7_di" bpmnElement="Flow_1fc8hv7">
<di:waypoint x="640" y="125" />
<di:waypoint x="640" y="190" />
<di:waypoint x="485" y="190" />
<di:waypoint x="485" y="230" />
<bpmndi:BPMNLabel>
<dc:Bounds x="535" y="165" width="55" height="14" />
bpmndi:BPMNLabel>
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1b2032e_di" bpmnElement="Flow_1b2032e">
<di:waypoint x="640" y="30" />
<di:waypoint x="640" y="75" />
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_11ejhh5_di" bpmnElement="Flow_11ejhh5">
<di:waypoint x="640" y="-82" />
<di:waypoint x="640" y="-50" />
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_08ru13p_di" bpmnElement="Flow_08ru13p">
<di:waypoint x="460" y="480" />
<di:waypoint x="360" y="480" />
<di:waypoint x="360" y="-10" />
<di:waypoint x="590" y="-10" />
<bpmndi:BPMNLabel>
<dc:Bounds x="353" y="225" width="44" height="14" />
bpmndi:BPMNLabel>
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_09owxd3_di" bpmnElement="Flow_09owxd3">
<di:waypoint x="485" y="505" />
<di:waypoint x="485" y="560" />
<bpmndi:BPMNLabel>
<dc:Bounds x="478" y="523" width="45" height="14" />
bpmndi:BPMNLabel>
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1jpug20_di" bpmnElement="Flow_1jpug20">
<di:waypoint x="485" y="420" />
<di:waypoint x="485" y="455" />
bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Shape-_2" bpmnElement="_1">
<dc:Bounds x="622" y="-118" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="618" y="-147" width="44" height="14" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape-_3" bpmnElement="_2">
<dc:Bounds x="590" y="-50" width="100" height="80" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="85" height="55" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape-_4" bpmnElement="_4">
<dc:Bounds x="435" y="230" width="100" height="80" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="85" height="55" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape-_5" bpmnElement="_5">
<dc:Bounds x="435" y="340" width="100" height="80" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="85" height="55" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape-_6" bpmnElement="_7">
<dc:Bounds x="435" y="560" width="100" height="80" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="85" height="55" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_19lsvak_di" bpmnElement="_8">
<dc:Bounds x="790" y="270" width="100" height="80" />
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_06brwuq_di" bpmnElement="_6" isMarkerVisible="true">
<dc:Bounds x="460" y="455" width="50" height="50" />
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_02haxla_di" bpmnElement="_3">
<dc:Bounds x="615" y="75" width="50" height="50" />
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1mmhxvi_di" bpmnElement="_9">
<dc:Bounds x="622" y="732" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="619" y="775" width="44" height="14" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
bpmndi:BPMNPlane>
bpmndi:BPMNDiagram>
definitions>
并行网关没有什么特别的设置,直接画流程图,正常流转就能结束。但是需要注意一点,必须将结束事件设置为:Terminate End Event,不然会出现整个流程无法结束的情况。
针对某个 userTask 设置监听器,选择“创建后”,java类设置全路径为:com.gh.maintenance.controller.activiti_example.ActivitiApproveListener
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="http://www.activiti.org/testm1625990337688" xmlns:xsd="http://www.w3.org/2001/XMLSchema" id="m1625990337688" name="" targetNamespace="http://www.activiti.org/testm1625990337688">
<process id="zhanleai_approve_listener" name="测试出差申请流程" processType="None" isClosed="false" isExecutable="true">
<documentation />
<startEvent id="_2" name="StartEvent" />
<userTask id="_3" name="创建出差申请" activiti:assignee="张三" activiti:exclusive="true">
<documentation />
<extensionElements>
<activiti:taskListener event="create" class="com.gh.maintenance.controller.activiti_example.ActivitiApproveListener" />
extensionElements>
userTask>
<userTask id="_4" name="经理审批" activiti:assignee="李四" activiti:exclusive="true">
<documentation />
<extensionElements />
userTask>
<userTask id="_5" name="总经理审批" activiti:assignee="老马" activiti:exclusive="true">
<documentation>documentation>
<extensionElements />
userTask>
<userTask id="_6" name="财务审批" activiti:assignee="小王" activiti:exclusive="true">
<documentation>documentation>
<extensionElements />
userTask>
<endEvent id="_7" name="EndEvent" />
<sequenceFlow id="_8" sourceRef="_2" targetRef="_3" />
<sequenceFlow id="_9" sourceRef="_4" targetRef="_5" />
<sequenceFlow id="_10" sourceRef="_5" targetRef="_6" />
<sequenceFlow id="_11" sourceRef="_6" targetRef="_7" />
<sequenceFlow id="_12" sourceRef="_3" targetRef="_4" />
process>
<bpmndi:BPMNDiagram id="Diagram-_1" name="New Diagram" documentation="background=#3C3F41;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0">
<bpmndi:BPMNPlane bpmnElement="zhanleai_approve_listener">
<bpmndi:BPMNEdge id="BPMNEdge__12" bpmnElement="_12" sourceElement="_3" targetElement="_4">
<di:waypoint x="477.5" y="160" />
<di:waypoint x="477.5" y="195" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="0" height="0" />
bpmndi:BPMNLabel>
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge__11" bpmnElement="_11" sourceElement="_6" targetElement="_7">
<di:waypoint x="481" y="440" />
<di:waypoint x="481" y="510" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="0" height="0" />
bpmndi:BPMNLabel>
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge__10" bpmnElement="_10" sourceElement="_5" targetElement="_6">
<di:waypoint x="480" y="340" />
<di:waypoint x="480" y="385" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="0" height="0" />
bpmndi:BPMNLabel>
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge__9" bpmnElement="_9" sourceElement="_4" targetElement="_5">
<di:waypoint x="477.5" y="250" />
<di:waypoint x="477.5" y="285" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="0" height="0" />
bpmndi:BPMNLabel>
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge__8" bpmnElement="_8" sourceElement="_2" targetElement="_3">
<di:waypoint x="481" y="27" />
<di:waypoint x="481" y="105" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="0" height="0" />
bpmndi:BPMNLabel>
bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Shape-_2" bpmnElement="_2">
<dc:Bounds x="465" y="-5" width="32" height="32" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="32" height="32" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape-_3" bpmnElement="_3">
<dc:Bounds x="435" y="105" width="85" height="55" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="85" height="55" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape-_4" bpmnElement="_4">
<dc:Bounds x="435" y="195" width="85" height="55" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="85" height="55" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape-_5" bpmnElement="_5">
<dc:Bounds x="435" y="285" width="85" height="55" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="85" height="55" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape-_6" bpmnElement="_6">
<dc:Bounds x="440" y="385" width="85" height="55" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="85" height="55" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape-_7" bpmnElement="_7">
<dc:Bounds x="465" y="510" width="32" height="32" />
<bpmndi:BPMNLabel>
<dc:Bounds x="0" y="0" width="32" height="32" />
bpmndi:BPMNLabel>
bpmndi:BPMNShape>
bpmndi:BPMNPlane>
bpmndi:BPMNDiagram>
definitions>
import lombok.extern.slf4j.Slf4j;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;
/**
* @Description: 监听器 【create-任务创建后触发 Assignment-任务分配后触发 Delete-任务完成后触发 All-所有任务都触发】
*
* @Author: zhanleai
* @Date:2023/6/16 11:37
*/
@Slf4j
public class ActivitiApproveListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
log.info(">>>>>>>>>>>>>>>>>>>>>>>>>此监听器触发机制为:"+delegateTask.getEventName());
log.info(">>>>>>>>>>>>>>>>>>>>>>>>>触发监听任务ID值为:"+delegateTask.getId());
log.info(">>>>>>>>>>>>>>>>>>>>>>>>>负责人为:"+delegateTask.getAssignee());
log.info(">>>>>>>>>>>>>>>>>>>>>>>>>任务名称"+delegateTask.getName());
}
}