Activiti7工作流
概述
工作流就是用来应对流程经常变动的情况,例如请假审批流程,如果使用硬编码方式实现审批流程就会非常被动,因为审批流程可能会经常变动,如果使用硬编码方式实现,如果流程一变动那编写的代码流程就要随着变动,所以直接使用硬编码实现流程是非常不明智的选择。使用工作流就是为了让我们写的代码能以不变应万变。
activiti 架构图

在新版本中,我们通过实验可以发现IdentityService,FormService两个Serivce都已经删除了。 所以后面我们对于这两个Service也不讲解了,但老版本中还是有这两个Service。
activiti.cfg.xml文件:配置信息,例如连接数据库等,可以通过编程式实现
ProcessEngineConfiguration接口:用于读取 activiti.cfg.xml配置文件
Service接口:用于对流程进行管理,其中前4个是最长用的
- RepositoryService——activiti的资源管理类
- RuntimeService——activiti的流程运行管理类
- TaskService——activiti的任务管理类
- HistoryService——activiti的历史管理类
- ManagerService——activiti的引擎管理类
基本使用
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.1.0.M6</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- druid 连接池管理 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!-- mySQL数据库驱动包管理 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- mybatis plus 集成Spring Boot启动器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
配置activiti数据库连接源
@Configuration
public class ActivitiConfig {
@Bean
@Autowired
public ProcessEngineConfiguration processEngineConfiguration(DataSource dataSource){
StandaloneProcessEngineConfiguration spec = new StandaloneProcessEngineConfiguration();
spec.setDataSource(dataSource);
spec.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
return spec;
}
}
测试,看ProcessEngine是否为空
@Component
public class MyTest {
@Autowired
ProcessEngine processEngine;
public void run(){
System.out.println(processEngine);
}
}
运行成功后,数据库中就会创建25张表
Activiti 的表都以ACT_开头。 第二部分是表示表的用途的两个字母标识。 用途也和服务的API对
应。
ACT_RE_: 'RE'表示repository。 这个前缀的表包含了流程定义和流程静态资源 (图片,
规则,等等)。
ACT_RU_: 'RU'表示runtime。 这些运行时的表,包含流程实例,任务,变量,异步任务,
等运行中的数据。 Activiti只在流程实例执行过程中保存这些数据, 在流程结束时就会删
除这些记录。 这样运行时表可以一直很小速度很快。
ACT_HI_: 'HI'表示history。 这些表包含历史数据,比如历史流程实例, 变量,任务等
等。
ACT_GE_: GE表示general。通用数据, 用于不同场景下。
安装Activti BPMN visualizer插件来可视化创建BPMN流程图。
安装后再resources中右键新增New Activiti 6.x BPMN 2.0 file,然后选择创建的xml文件右键选择View BPMN (Activiti) Diagram,就可以在插件中编辑BPMN流程图了

流程图有直接的id,还有流程中的任务有任务名称和执行人等,这些都可以通过界面来编写完成。

定义并部署一个流程
// 1.流程定义部署
// act_ge_bytearray(部署信息)、
// act_re_procdef(流程定义的一些信息)、
// act_re_deployment(流程定义的bmpn文件和png文件)
@Test
public void testProcessEngine(){
//创建ProcessEngine持久服务,用于写入数据库对象
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("test.bpmn20.xml")
.addClasspathResource("diagram.png")
.name("请假审批流程")
.deploy();
System.out.println(deployment.getName());
System.out.println(deployment.getId());
}
创建并启动一个流程实例
//2.启动一个流程实例(多个流程实例【对象实例】对应一个流程定义【类】)
// act_hi_actinst 活动历史表,记录所有活动
// act_hi_identitylink 参与者信息
// act_hi_procinst 流程实例历史表
// act_hi_taskinst 任务历史表,记录所有任务
// act_ru_executin 流程实例执行表
// act_ru_identitylink 任务参与者
// act_ru_task 任务执行表
@Test
public void startProcessInstance(){
RuntimeService runtimeService = processEngine.getRuntimeService();
//通过流程定义的key(可以在流程定义的xml中查看id)获取流程实例
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holidays");
System.out.println("流程定义id:"+processInstance.getProcessDefinitionId());//holidays:1:9c37ad72-434e-11f0-82ce-3cf011eabf6f
System.out.println("流程部署id:"+processInstance.getDeploymentId());//null
System.out.println("流程实例id:"+processInstance.getId());//f9868c91-434e-11f0-ad6a-3cf011eabf6f
System.out.println("当前活动id:"+processInstance.getActivityId());//null
}
查询当前流程的个人任务
//查询当前流程的个人任务
@Test
public void queryPersonalTaskList(){
// 流程定义key
String processDefinitionKey = "holidays";
// 任务负责人
String assignee = "bob";
//获取任务服务对象
TaskService taskService = processEngine.getTaskService();
//创建一个任务查询
List<Task> list = taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
.includeProcessVariables()
.taskAssignee(assignee).list();
for (Task task : list) {
System.out.println("----------------------------");
System.out.println("流程实例id:" + task.getProcessInstanceId());//f9868c91-434e-11f0-ad6a-3cf011eabf6f
System.out.println("任务id:" + task.getId());//f98be3c5-434e-11f0-ad6a-3cf011eabf6f
System.out.println("任务负责人:" + task.getAssignee());//bob
System.out.println("任务名称:" + task.getName());//填写请假申请
}
}
完成一个流程中的某个任务
//完成个人实例任务
//act_hi_actinst 已完成的活动信息和下一个待完成的任务
//act_hi_identitylink 参与者
//act_hi_taskinst 任务实例
//act_ru_executin 执行表
//act_ru_identitylink 参与者信息
// act_ru_task 任务
@Test
public void completTask(){
// 流程定义key
String processDefinitionKey = "holidays";
// 任务负责人
String assignee = "bob";
//获取任务服务对象
TaskService taskService = processEngine.getTaskService();
//创建一个任务查询
List<Task> list = taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
.includeProcessVariables()
.taskAssignee(assignee).list();
for (Task task : list) {
System.out.println("----------------------------");
System.out.println("流程实例id:" + task.getProcessInstanceId());//f9868c91-434e-11f0-ad6a-3cf011eabf6f
System.out.println("任务id:" + task.getId());//f98be3c5-434e-11f0-ad6a-3cf011eabf6f
System.out.println("任务负责人:" + task.getAssignee());//bob
System.out.println("任务名称:" + task.getName());//填写请假申请
}
//todo做别的一些事,例如保存填写信息等
//完成当前任务
taskService.complete(list.get(1).getId());//f98be3c5-434e-11f0-ad6a-3cf011eabf6f
System.out.println("完成任务id:"+list.get(1).getId());
}
删除成功部署的流程,会删除所有有关的流程部署表
//删除已经成功部署的流程定义
@Test
public void deleteDeployment(){
RepositoryService repositoryService = processEngine.getRepositoryService();
//流程删除分为两种:普通删除、级联删除,通过第二个参数区别,fasle为普通删除,true为级联删除
//如果该流程定义下没有正在运行的流程,则可以用普通删除
/*如果该流程定义下存在已经运行的流程,使用普通删除报错,可用级联删除方法将流程及相关
记录全部删除。项目开发中使用级联删除的情况比较多,删除操作一般只开放给超级管理员使
用。*/
repositoryService.deleteDeployment("9c109d6f-434e-11f0-82ce-3cf011eabf6f",true);
repositoryService.deleteDeployment("4120dca9-4351-11f0-b9ba-3cf011eabf6f",true);
repositoryService.deleteDeployment("e858dbf3-4369-11f0-a74e-3cf011eabf6f",true);
System.out.println("级联删除部署的流程—————成功");
}
进阶使用
流程实例(Businesskey)
参与者(可以是用户也可以是程序)按照流程定义内容发起一个流程,这就是一个流程实例,是动态的,流程实例就相当于类中的的实例一样。
流程定义部署在activiti后,就可以在系统中通过activiti去管理该流程的执行,执行流程表示流 程的一次执行。比如部署系统请假流程后,如果某用户要申请请假这时就需要执行这个流程,如果 另外一个用户也要申请请假则也需要执行该流程,每个执行互不影响,每个执行是单独的流程实例。
启动流程实例并指定业务表示( Businesskey)
启动流程实例时,指定的businesskey,就会在act_ru_execution #流程实例的执行表中存储businesskey。
//2.启动一个流程实例(多个流程实例【对象实例】对应一个流程定义【类】)
// act_hi_actinst 活动历史表,记录所有活动
// act_hi_identitylink 参与者信息
// act_hi_procinst 流程实例历史表
// act_hi_taskinst 任务历史表,记录所有任务
// act_ru_executin 流程实例执行表
// act_ru_identitylink 任务参与者
// act_ru_task 任务执行表
@Test
public void startProcessInstance(){
RuntimeService runtimeService = processEngine.getRuntimeService();
//通过流程定义的key(可以在流程定义的xml中查看id)和Businesskey(业务id)获取流程实例,其中业务id可以是请假单的具体内容表的id,方便后面去查看请假单具体内容
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holidays","112111");
System.out.println("流程定义id:"+processInstance.getProcessDefinitionId());//holidays:1:9c37ad72-434e-11f0-82ce-3cf011eabf6f
System.out.println("流程部署id:"+processInstance.getDeploymentId());//null
System.out.println("流程实例id:"+processInstance.getId());//f9868c91-434e-11f0-ad6a-3cf011eabf6f
System.out.println("当前活动id:"+processInstance.getActivityId());//null
}

流程实例运行时(getRuntimeService)
流程在运行过程中可以查询流程实例的状态,当前运行结点等信息。
//查询流程实例运行(执行)时的信息
@Test
public void queryProcessRuntimeInfoList(){
// 流程定义过程key
String processDefinitionKey = "holidays";
RuntimeService runtimeService = processEngine.getRuntimeService();
List<ProcessInstance> list = runtimeService.createProcessInstanceQuery()
.processDefinitionKey(processDefinitionKey)
.list();
for (ProcessInstance processInstance : list) {
System.out.println("----------------------------");
System.out.println("流程实例id:" + processInstance.getProcessInstanceId());
System.out.println("所属流程定义id:"+processInstance.getProcessDefinitionId());
System.out.println("是否执行完成:" + processInstance.isEnded());
System.out.println("是否暂停:" + processInstance.isSuspended());
System.out.println("当前活动标识:" + processInstance.getActivityId());
System.out.println("Businesskey(外部系统业务id):"+processInstance.getBusinessKey());
}
}
流程挂起与激活
操作流程实例对象,针对单个流程执行挂起操作,某个流程实例挂起则此流程不再继续执行,完成 该流程实例的当前任务将报异常。
1.流程定义挂起与激活
操作流程定义为挂起状态,该流程定义下边所有的流程实例全部暂停: 流程定义为挂起状态该流程定义将不允许启动新的流程实例,同时该流程定义下所有的流程实例将 全部挂起暂停执行
//流程定义的挂起与激活
@Test
public void suspendOrActivateProcessDefinition(){
String processDefinitionId="holidays:1:da0c89d0-4373-11f0-9533-3cf011eabf6f";
String processDefinitionKey="";
RepositoryService repositoryService = processEngine.getRepositoryService();
//获取流程定义对象
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionId(processDefinitionId)
.singleResult();
//查看是否时挂起状态
boolean suspended = processDefinition.isSuspended();
if (suspended){
//流程定义id、全部激活(该流程定义下的所有实例)、激活日期
repositoryService.activateProcessDefinitionById(processDefinitionId,true,null);
}else {
//流程定义id、全部挂起(该流程定义下的所有实例)、挂起日期
repositoryService.suspendProcessDefinitionById(processDefinitionId,true,null);
}
}
2.流程实例挂起与激活
操作流程实例对象,针对单个流程执行挂起操作,某个流程实例挂起则此流程不再继续执行,完成 该流程实例的当前任务将报异常。
//流程定义的挂起与激活
@Test
public void suspendOrActivateProcessInstance(){
String processInstanceId="670586b7-4377-11f0-abfe-3cf011eabf6f";
String processInstanceKey="";
RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
//查看是否时挂起状态
boolean suspended = processInstance.isSuspended();
if (suspended){
//流程实例id
runtimeService.activateProcessInstanceById(processInstanceId);
System.out.println("流程实例激活成功!流程实例id:"+processInstanceId);
}else {
//流程实例id
runtimeService.suspendProcessInstanceById(processInstanceId);
System.out.println("流程实例挂起成功!流程实例id:"+processInstanceId);
}
}
分配任务负责人(流程变量)
1.固定分配
在业务建模时指定固定的任务负责人,在基本使用章节,我们就是使用的固定分配。在properties 视图中,填写Assignee项为任务负责人。由于固定分配方式,任务只管一步一步执行任务,执行到每一个任务将按照 bpmn 的配置去分配任 务负责人,所以该方式属于硬编码不够灵活(不推荐使用)

2.表达式分配
1)UEL 表达式
Activiti 使用 UEL表达式,UEL是java EE6规范的一部分,UEL(Unified Expression Language)即统一表达式语言,activiti支持两个UEL表达式:UEL-value和UEL-method。
UEL-value:{userId}、{username}
UEL-method:${user.id},通过getter方法获取值。
UEL-method与UEL-value结合:${userService.getUserNameById(userId)}
其他:基础类型、bean、list、array和map
2)使用流程变量设置值


在启动流程实例时设置流程变量
//使用流程变量设置值
@Test
public void startProcessInstance(){
RuntimeService runtimeService = processEngine.getRuntimeService();
HashMap<String, Object> variables = new HashMap<>();
User user = new User();
user.id=594000L;
user.username="gc";
variables.put("user",user);
//通过流程变量设置值
runtimeService.startProcessInstanceByKey("holidays",variables);
System.out.println("开启流程实例成功!");
}
流程变量
在分配任务负责人章节已经使用过流程变量了,这里要详细介绍下流程变量
流程变量在activiti中是一个非常重要的角色,流程运转有时需要靠流程变量,业务系统和activiti 结合时少不了流程变量,流程变量就是activiti在管理工作流时根据管理需要而设置的变量。 比如在请假流程流转时如果请假天数大于3天则由总经理审核,否则由人事直接审核,请假天 数就可以设置为流程变量,在流程流转时使用。
如果将pojo存储到流程变量中,必须实现序列化接口serializable,为了防止由于新增字段无 法反序列化,需要生成serialVersionUID。
1.流程变量作用域
流程变量的作用域默认是一个流程实例(processInstance),也可以是一个任务(task)或一个执行实例 (execution),这三个作用域流程实例的范围最大,可以称为global变量
global 变量中变量名不允许重复,设置相同名称的变量,后设置的值会覆盖前设置的变量值。 Local变量由于在不同的任务或不同的执行实例中,作用域互不影响,变量名可以相同没有影响。 Local变量名也可以和global变量名相同,没有影响。
2.流程变量使用方法
1)设置流程变量:在流程可视化插件中设置或者在生成的xml中设置
2)通过UEL表达式使用流程变量
- 可以在assignee处设置UEL表达式,表达式的值为任务的负责人 比如:${assignee},assignee 就是一个流程变量名称
- 在连线上设置UEL表达式,决定流程走向 比如:{price>=10000}和{price<10000}: price 就是一个流程变量名称,uel表达式结果类型为 布尔类型
使用Global (全局)变量控制流程
所谓全局变量其实就是在启动流程时设置的变量,前面我们设置都是全局变量
现在需要根据申请的请假天数来决定是否交由总经理审批,当请假天数小于等于3天时,部门负责人审核完成后直接交由认识部存档入库,当请假天数大于3天时,除了部门负责人审核外还需总经理审批,总经理审核完之后才交由认识部存档入库。



启动流程时设置变量值
// 启动流程时设置变量值
@Test
public void startProcessInstance() {
String processDefinitionId="holidays2:1:ce496e69-43ac-11f0-80b4-3cf011eabf6f";
String processDefinitionKey="holidays2";
//外部业务id(比如可以是填写的申请表id,或者申请人id,或者外部业务的流程id)
String businessKey="formId_11991";
RuntimeService runtimeService = processEngine.getRuntimeService();
HashMap<String, Object> variables = new HashMap<>();
variables.put("username", "gc");
variables.put("num",10);
// 通过流程变量设置值
// runtimeService.startProcessInstanceByKey("holidays", variables);
// ProcessInstance processInstance
// = runtimeService.startProcessInstanceById(processDefinitionId,businessKey,variables);
//通过startProcessInstanceByKey开启流程实例(过程实例),使用的是最新版的流程定义版本
ProcessInstance processInstance
= runtimeService.startProcessInstanceByKey(processDefinitionKey,businessKey,variables);
System.out.println("开启流程实例成功!");
System.out.println("ProcessInstanceId:"+processInstance.getId());
System.out.println("ProcessDefinitionId:"+processInstance.getProcessDefinitionId());
System.out.println("ProcessDefinitionKey:"+processInstance.getProcessDefinitionKey());
System.out.println("DeploymentId:"+processInstance.getDeploymentId());
System.out.println("getBusinessKey:"+processInstance.getBusinessKey());
System.out.println("getName:"+processInstance.getName());
System.out.println("getActivityId():"+processInstance.getActivityId());
}
完成任务时设置变量值
//任务id
String taskId = "";
TaskService taskService = processEngine.getTaskService();
Holiday holiday = new Holiday();
holiday.setNum(4);
// 定义流程变量
Map<String, Object> variables = new HashMap<String, Object>();
//变量名是holiday,变量值是holiday对象
variables.put("holiday", holiday);
taskService.complete(taskId, variables);
通过当前流程实例设置
//当前流程实例执行 id,通常设置为当前执行的流程实例
String executionId="2601";
RuntimeService runtimeService = processEngine.getRuntimeService();
Holiday holiday = new Holiday();
holiday.setNum(3);
//通过流程实例 id设置流程变量
runtimeService.setVariable(executionId, "holiday", holiday);
//一次设置多个值
//runtimeService.setVariables(executionId, variables)
通过当前任务设置
//当前待办任务id
String taskId="1404";
TaskService taskService = processEngine.getTaskService();
Holiday holiday = new Holiday();
holiday.setNum(3);
//通过任务设置流程变量
taskService.setVariable(taskId, "holiday", holiday);
//一次设置多个值
//taskService.setVariables(taskId, variables)
使用变量时影响的相关表
SELECT * FROM act_ru_variable #当前流程变量表
记录当前运行流程实例可使用的流程变量,包括 global和local变量
Id_:主键_
Type_:变量类型
Name_:变量名称
Execution_id_:所属流程实例执行id,global和local变量都存储
Proc_inst_id_:所属流程实例id,global和local变量都存储
Task_id_:所属任务id,local变量存储 _
Bytearray_:serializable类型变量存储对应act_ge_bytearray表的id
Double_:double类型变量值 Long_:long类型变量值
Text_:text类型变量值
SELECT * FROM act_hi_varinst #历史流程变量表 记录所有已创建的流程变量,包括 global和local变量 字段意义参考当前流程变量表。
使用local流程变量
1.任务办理时设置
任务办理时设置local流程变量,当前运行的流程实例只能在该任务结束前使用,任务结束该变量无 法在当前流程实例使用,可以通过查询历史任务查询。
//任务id
String taskId = "";
TaskService taskService = processEngine.getTaskService();
// 定义流程变量
Map<String, Object> variables = new HashMap<String, Object>();
Holiday holiday = new Holiday ();
holiday.setNum(3);
// 定义流程变量
Map<String, Object> variables = new HashMap<String, Object>();
//变量名是holiday,变量值是holiday对象
variables.put("holiday", holiday);
// 设置local变量,作用域为该任务
taskService.setVariablesLocal(tasked, variables);
taskService.complete(taskId);
2.通过当前任务设置
//当前待办任务id
String taskId="1404";
TaskService taskService = processEngine.getTaskService();
Holiday holiday = new Holiday ();
holiday.setNum(3);
//通过任务设置流程变量
taskService.setVariableLocal(taskId, "holiday", holiday);
//一次设置多个值
//taskService.setVariablesLocal(taskId, variables)
候选人(Candidate-users)
在流程定义中在任务结点的assignee固定设置任务负责人,在流程定义时将参与者固定设置 在.bpmn文件中,如果临时任务负责人变更则需要修改流程定义,系统可扩展性差。 针对这种情况可以给任务设置多个候选人,可以从候选人中选择参与者来完成任务
在流程图中任务节点的配置中设置candidate-users(候选人),多个候选人之间用逗号分开

候选人办理组任务的主要步骤
第一步:候选人查询组任务,指定候选人,查询该候选人当前的待办任务,所有候选人都能查到该任务,候选人不能直接办理任务,需要候选人拾取组任务后,组任务变成个人任务,候选人就变成了该任务的负责人。
第二步:候选人拾取(claim)任务,将候选人的组任务,变成个人任务,此时候选人就变成了该任务的负责人。如果拾取后不想办理该任务? 需要将已经拾取的个人任务归还到组里边,将个人任务变成了组任务。
第三步:查询个人任务,和之前的一样查询即可
第四步:办理个人任务,和之前一样完成办理即可
候选人查询组任务
taskCandidateUser(candidateUser)
@Test
public void queryGroupTaskList(){
// 流程定义key
String processDefinitionKey = "holidays2";
// 任务候选人, activiti:candidateUsers="jack,bob,Jerry"
String candidateUser = "jack";
TaskService taskService = processEngine.getTaskService();
List<Task> list=null;
try {
list= taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
.taskCandidateUser(candidateUser)
.list();
} catch (Exception e) {
System.out.println("候选人名单不存在");
}
if (list==null){
return;
}
for (Task task : list) {
System.out.println("任务id:"+task.getId());
System.out.println("任务名字:"+task.getName());
System.out.println("任务负责人:"+task.getAssignee()); //负责人结果为空,只有拾取任务后才有值
System.out.println("外部业务id:"+task.getBusinessKey());
System.out.println("所属实例id:"+task.getProcessInstanceId());
}
}
如果候选人没被security加载,会报错没有找到用户,解决办法可以暂时设置候选人到内存中,设置完后查询就不会报错了
//报错信息:Cause: org.springframework.security.core.userdetails.UsernameNotFoundException:
@Configuration
public class SecurityConfig {
@Bean
public UserDetailsService userDetailsService() {
//这里配置用户信息,这里暂时使用这种方式将用户存储在内存中
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("jack").password("123456").authorities("activiti").build());
manager.createUser(User.withUsername("bob").password("123456").authorities("activiti").build());
manager.createUser(User.withUsername("Jerry").password("123456").authorities("activiti").build());
return manager;
}
}
候选人拾取(claim)任务
taskService.claim(taskId, user)
将候选人的组任务,变成个人任务,此时候选人就变成了该任务的负责人,此时查询组任务将查不到
//拾取任务
@Test
public void claimTask(){
TaskService taskService = processEngine.getTaskService();
//要拾取的任务id
String taskId = "600c90d3-441a-11f0-9be4-3cf011eabf6f";
//任务候选人id
String user = "jack";
//拾取任务
//即使该用户不是候选人也能拾取(建议拾取时校验是否有资格)
//校验该用户有没有拾取任务的资格
Task task = taskService.createTaskQuery()//
.taskId(taskId)
.taskCandidateUser(user)//根据候选人查询
.singleResult();
if(task!=null){
taskService.claim(taskId, user);
System.out.println("任务拾取成功");
}
}
任务负责人归还组任务
将任务负责人设置为null即可
如果个人不想办理该组任务,可以归还组任务,归还后该用户不再是该任务的负责人
// 归还组任务,由个人任务变为组任务,还可以进行任务交接
@Test
public void setAssigneeToGroupTask() {
// 查询任务使用TaskService
TaskService taskService = processEngine.getTaskService();
// 当前待办任务
String taskId = "600c90d3-441a-11f0-9be4-3cf011eabf6f";
// 任务负责人
String userId = "jack";
// 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
Task task = taskService.createTaskQuery().taskId(taskId)
.taskAssignee(userId).singleResult();
if (task != null) {
// 如果设置为null,归还组任务,该 任务没有负责人
taskService.setAssignee(taskId, null);
System.out.println("归还组任务成功");
}
}
负责人任务交接
直接设置任务负责人即可
任务交接,任务负责人将任务交给其它候选人办理该任务
//交接任务给其他候选人
@Test
public void setAssigneeToCandidateUser() {
// 查询任务使用TaskService
TaskService taskService = processEngine.getTaskService();
// 当前待办任务
String taskId = "600c90d3-441a-11f0-9be4-3cf011eabf6f";
// 任务负责人
String userId = "jack";
// 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
Task task = taskService.createTaskQuery().taskId(taskId)
.taskAssignee(userId).singleResult();
if (task != null) {
// 将此任务交给其它候选人办理该 任务
String candidateuser = "bob";
// 根据候选人和组任务id查询,如果有记录说明该 候选人有资格拾取该任务,无法实现,因为拾取任务后组任务无法查询到
// Task task2 = taskService.createTaskQuery().taskId(taskId)
// .taskCandidateUser(candidateuser).singleResult();
taskService.setAssignee(taskId, candidateuser);
System.out.println("交接任务成功");
}
}
影响的数据库表
SELECT * FROM act_ru_task #任务执行表,记录当前执行的任务,由于该任务当前是组任务,所有 assignee为空,当拾取任务后该字段就是拾取用户的id
SELECT * FROM act_ru_identitylink #任务参与者,记录当前参考任务用户或组,当前任务如果设置 了候选人,会向该表插入候选人记录,有几个候选就插入几个
于act_ru_identitylink对应的还有一张历史表act_hi_identitylink,向act_ru_identitylink插入记录的同 时也会向历史表插入记录。任务完成
网关(gateway)
之前使用流程变量已经可以实现分支判断了,但是里面还存在一些问题,例如num为空的情况,这时候流程就会出错,为了解决这个问题,就可以使用网关,网关可以保证整个流程分支的保底策略,从而避免流程执行分支判断时出现意外情况。
排他网关(Exclusive gateway)
排他网关(也叫异或(XOR)网关,或叫基于数据的排他网关),用来在流程中实现决策。 当流程 执行到这个网关,所有分支都会判断条件是否为true,如果为true则执行该分支, 注意,排他网关只会选择一个为true的分支执行。(即使有两个分支条件都为true,排他网关也会只选择一条分支去执行,如果所有分支条件都不是true,报错) ,如果不使用网关则流程直接结束

并行网关(ParallelGateway)
并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进 入和外出顺序流的:
- fork分支: 并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
- join汇聚: 所有到达并行网关,在此等待的进入分支, 直到所有进入顺序流的分支都到达以后, 流程就会通 过汇聚网关。
注意,如果同一个并行网关有多个进入和多个外出顺序流, 它就同时具有分支和汇聚功能。 这时, 网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。
与其他网关的主要区别是,并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略。

财务结算和入库是两个execution分支,在act_ru_execution表有两条记录分别是财务结算和入库, act_ru_execution还有一条记录表示该流程实例。 待财务结算和入库任务全部完成,在汇聚点汇聚,通过parallelGateway并行网关。 并行网关在业务应用中常用于会签任务,会签任务即多个参与者共同办理的任务。
包含网关(Inclusive gateway)
包含网关可以看做是排他网关和并行网关的结合体。 和排他网关一样,你可以在外出顺序流上 定义条件,包含网关会解析它们。 但是主要的区别是包含网关可以选择多于一条顺序流,这和并行网关一样。
包含网关的功能是基于进入和外出顺序流的:
- 分支: 所有外出顺序流的条件都会被解析,结果为true的顺序流会以并行方式继续执行, 会为每个顺序流创建一个分支。
- 汇聚: 所有并行分支到达包含网关,会进入等待状态, 直到每个包含流程token的进入顺序流的分支都到达。 这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。 在 汇聚之后,流程会穿过包含网关继续执行。

企业体检流程,公司全体员工进行常规项检查、抽血化验,公司管理层除常规检查和抽血化验还要 进行附加项体检。
员工类型: 通过流程变量userType来表示,如果等于1表示普通员工,如果等于2表示领导,通过包含网关的每个分支的连线上设置condition条件。
总结
Activiti工作流引擎相当于一个service层的服务,它提供了对外的调用service接口,一个流程包含:
- 流程定义:就是使用BPMN工具来绘制流程图,包含里面的分支判断变量等,最后生成BPMN流程xml文件
- 流程部署:将定义好的流程xml文件上传到activiti服务中,然后调用接口RepositoryService的Deployment对象来进行解析和部署一个流程定义,引擎会自动操作数据库表进行数据持久化。
- 启动流程实例开启任务:通过调用接口runtimeService.startProcessInstance*()方法启动一个流程实例,在启动流程实例时应当对分支判断变量值进行赋值、对流程任务负责人进行赋值、对传入的外部业务id进行赋值等。
- 查询流程进展和当前任务:通过接口RuntimeService来查询流程和任务数据,这个适用于展示给前端的数据
- 完成任务:通过调用taskService.complete()方法来完成具体的任务
因为Activiti工作流引擎相当于一个service层的服务,所以我们还需要对它进一步封装让它对外提供http服务,最终形成一个独立的微服务供外部服务调用,当然还有一些特别的功能例如流程节点驳回这些功能,activiti框架并未提供,所以需要自己封装一些方法来实现节点的跳转。