基础知识及入门示例见:工作流引擎-Activiti基础篇
1、 流程实例
1.1 什么是流程实例
参与者(可以是用户也可以是程序)按照流程定义内容发起一个流程,这就是一个流程实例。是动态的。
1.2 启动流程实例
流程定义部署在 activiti后,就可以在系统中通过 activiti去管理该流程的执行,执行流程表示流程的一次执行。比如部署系统请假流程后,如果某用户要申请请假这时就需要执行这个流程,如果另外一个用户也要申请请假则也需要执行该流程,每个执行互不影响,每个执行是单独的流程实例。执行流程首先需要启动流程实例。
@Test
public void startProcessInstance() {
// 流程定义key
String processDefinitionKey = "";
// 获取RunTimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 根据流程定义key启动流程
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey(processDefinitionKey);
System.out
.println(" 流 程 定 义 id : " +
processInstance.getProcessDefinitionId());
System.out.println("流程实例id:" + processInstance.getId());
System.out.println("当前活动Id:" + processInstance.getActivityId());
}
1.3 Businesskey( 业务标识)
启动流程实例时,指定的businesskey,就会在act_ru_execution #流程实例的执行表中存储businesskey。Businesskey:业务标识,通常为业务表的主键,业务标识和流程实例一一对应。业务标识来源于业务系统。存储业务标识就是根据业务标识来关联查询业务系统的数据。比如:请假流程启动一个流程实例,就可以将请假单的 id 作为业务标识存储到 activiti中,将来查询activiti的流程实例信息就可以获取请假单的 id 从而关联查询业务系统数据库得到请假单信息。
// 根据流程定义的key启动一个流程实例
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey(processDefinitionKey,businessKey);
Activiti中存储业务标识:
1.4 操作数据库表
启动流程实例,操作如下数据库表:
SELECT * FROM act_ru_execution #流程实例执行表,记录当前流程实例的执行情况
说明:流程实例执行,如果当前只有一个分支时,一个流程实例只有一条记录且执行表的主键 id 和流程实例 id 相同,如果当前有多个分支正在运行则该执行表中有多条记录,存在执行表的主键和流程实例id 不相同的记录。 不论 当前有几个分支总会有一条记录的执行表的主键和流程 实例 id 相同一个流程实例运行完成,此表中与流程实例相关的记录删除。
SELECT * FROM act_ru_task #任务执行表,记录当前执行的任务
说明:启动流程实例,流程当前执行到第一个任务结点,此表会插入一条记录表示当前任务的执行情况,如果任务完成则记录删除。
SELECT * FROM act_ru_identitylink #任务参与者,记录当前参与任务的用户或组
SELECT * FROM act_hi_procinst #流程实例历史表
流程实例启动,会在此表插入一条记录,流程实例运行完成记录也不会删除。
SELECT * FROM act_hi_taskinst #任务历史表,记录所有任务
开始一个任务,不仅在 act_ru_task 表插入记录,也会在历史任务表插入一条记录,任务历史表的主键就是任务 id,任务完成此表记录不删除。
SELECT * FROM act_hi_actinst #活动历史表,记录所有活动
活动包括任务,所以此表中不仅记录了任务,还记录了流程执行过程的其它活动,比如开始事件、结束事件。
1.5 查询流程实例
流程在运行过程中可以查询流程实例的状态,当前运行结点等信息。
@Test
public void queryProcessInstance() {
// 流程定义key
String processDefinitionKey = "holiday";
// 获取RunTimeService
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());
}
}
需求:在 activiti实际应用时,查询流程实例列表时可能要显示出业务系统的一些相关信息,比如:查询当前运行的请假流程列表需要将请假单名称、请假天数等信息显示出来,请假天数等信息在业务系统中存在,而并没有在 activiti数据库中存在,所以是无法通过 activiti的 api查询到请假天数等信息。
实现:在查询流程实例时,通过 businessKey(业务标识 )关联查询业务系统的请假单表,查询出请假天数等信息。
通过下面的代码就可以获取 activiti中所对应实例保存的业务 Key。而这个业务Key 一般都会保存相关联的业务操作表的主键,再通过主键 ID 去查询业务信息,比如通过请假单的 ID,去查询更多的请假信息(请假人,请假时间,请假天数,请假事由等)
String businessKey = processInstance.getBusinessKey();在 activiti的 act_ru_execution 表,字段 BUSINESS_KEY就是存放业务 KEY 的。
1.6 挂起、激活流程实例
某些情况可能由于流程变更需要将当前运行的流程暂停而不是直接删除,流程暂停后将不会继续执行。
1.6.1 全部流程实例挂起
操作流程定义为挂起状态,该流程定义下边所有的流程实例全部暂停:流程定义为挂起状态该流程定义将不允许启动新的流程实例,同时该流程定义下所有的流程实例将全部挂起暂停执行。
// 挂起激活流程定义
@Test
public void suspendOrActivateProcessDefinition() {
// 流程定义id
String processDefinitionId = "";
RepositoryService repositoryService = processEngine.getRepositoryService();
// 获得流程定义
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionId(processDefinitionId).singleResult();
//是否暂停
boolean suspend = processDefinition.isSuspended();
if(suspend){
//如果暂停则激活,这里将流程定义下的所有流程实例全部激活
repositoryService.activateProcessDefinitionById(processDefinitionId,true, null);
System.out.println("流程定义:"+processDefinitionId+"激活");
}else{
//如果激活则挂起,这里将流程定义下的所有流程实例全部挂起
repositoryService.suspendProcessDefinitionById(processDefinitionId,true, null);
System.out.println("流程定义:"+processDefinitionId+"挂起");
}
}
1.6.2 单个流程实例挂起
操作流程实例对象,针对单个流程执行挂起操作,某个流程实例挂起则此流程不再继续执行,完成该流程实例的当前任务将报异常。
@Test
public void suspendOrActiveProcessInstance() {
// 流程实例id
String processInstanceId = "";
// 获取RunTimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
//根据流程实例id查询流程实例
ProcessInstance processInstance =runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId).singleResult();
boolean suspend = processInstance.isSuspended();
if(suspend){
//如果暂停则激活
runtimeService.activateProcessInstanceById(processInstanceId);
System.out.println("流程实例:"+processInstanceId+"激活");
}else{
//如果激活则挂起
runtimeService.suspendProcessInstanceById(processInstanceId);
System.out.println("流程实例:"+processInstanceId+"挂起");
}
}
2、 个人任务
2.1 分配任务负责人
2.1.1 固定分配
在进行业务流程建模时指定固定的任务负责人,在 properties 视图中,填写 Assignee 项为任务负责人。由于固定分配方式,任务只管一步一步执行任务,执行到每一个任务将按照 bpmn 的配置去分配任务负责人。
2.1.2 表达式分配
2.1.2.1 UEL 表达式
Activiti使用 UEL 表达式,UEL 是 java EE6规范的一部分,UEL(Unified Expression Language)即统一表达式语言,activiti支持两个 UEL表达式:UEL-value 和 UEL-method。
UEL-value 定义如下:assignee 这个变量是 activiti的一个流程变量。
或:user 也是 activiti的一个流程变量,user.assignee 表示通过调用 user 的 getter方法获取值。
UEL-method方式如下:holidayBean是 spring容器中的一个 bean,表示调用该 bean 的 getHolidayId()方法。
UEL-method 与UEL-value 结合:
再比如: ${ldapService.findManagerForEmployee(emp)}
ldapService 是 spring容器的一个bean,findManagerForEmployee是该 bean 的一个方法,emp 是 activiti流程变量,emp作为参数传到 ldapService.findManagerForEmployee 方法中。
其它:表达式支持解析基础类型、bean、list、array 和 map,也可作为条件判断。
如下:${order.price > 100 && order.price < 250}
2.1.2.2 使用流程变量分配任务
定义任务分配流程变量
在启动流程实例时设置流程变量,如下:
//启动流程实例时设计流程变量
//定义流程变量
Map<String, Object> variables = new HashMap<String, Object>();
//设置流程变量assignee
variables.put("assignee", "张三");
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey(processDefinitionKey, variables);
由于使用了表达式分配,必须保证在任务执行过程表达式执行成功,比如:某个任务使用了表达式${order.price > 100 && order.price < 250},当执行该任务时必须保证 order 在流程变量中存在,否则 activiti异常。
2.1.3 监听器分配
任务监听器是发生对应的任务相关事件时执行自定义 java 逻辑 或表达式。任务相当事件包括:Create:任务创建后触发;Assignment:任务分配后触发;Delete:任务完成后触发;All:所有事件发生都触发。
java 逻辑 或表达式: 表达式参考上边的介绍的UEL 表达式,这里主要介绍监听类使用。定义任务监听类,且类必须实现 org.activiti.engine.delegate.TaskListener接口
public class MyTaskListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
//这里指定任务负责人
delegateTask.setAssignee("张三");
}
}
使用监听器分配方式,按照监听事件去执行监听类的 notify 方法,方法如果不能正常执行也会影响任务的执行。
2.2 查询任务
查询任务负责人的待办任务:
// 查询当前个人待执行的任务
@Test
public void findPersonalTaskList() {
// 流程定义key
String processDefinitionKey = "holiday";
// 任务负责人
String assignee = "zhangsan";
// 创建TaskService
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());
System.out.println("任务id:" + task.getId());
System.out.println("任务负责人:" + task.getAssignee());
System.out.println("任务名称:" + task.getName());
}
}
关联 businessKey 需求: 在 activiti实际应用时,查询待办任务可能要显示出业务系统的一些相关信息,比如:查询待审批请 假单任务列表需要将请假单的日期、请假天数等信息显示出来,请假天数等信息在业务系统中存在, 而并没有在 activiti数据库中存在,所以是无法通过 activiti的 api查询到请假天数等信息。
实现:
在查询待办任务时,通过 businessKey(业务标识 )关联查询业务系统的请假单表,查询出请假天数等信息。
@Test
public void findPersonalTaskList() {
//1.得到ProcessEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2.得到TaskService对象
TaskService taskService = processEngine.getTaskService();
//3.根据流程定义的key,负责人assignee来实现当前用户的任务
Task task = taskService.createTaskQuery()
.processDefinitionKey("holiday")
.taskAssignee("lishi")
.singleResult();
//根据task获取实例D
String processInstanceId = task.getProcessInstanceId();
//查询实例
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId).singleResult();
//获取businessKey
String businessKey = processInstance.getBusinessKey();
}
2.3 办理任务
指定任务 id,调用TaskService 完成任务:
// 完成任务
@Test
public void completTask() {
//任务id
String taskId = "10305";
// 创建TaskService
TaskService taskService = processEngine.getTaskService();
taskService.complete(taskId);
System.out.println("完成任务");
}
可以在完成任务之前查询一次,判断当前用户是否有权限操作当前任务。
//任务id
String taskId = "10305";
//任务人
String assignee = "zhangsan";
// 创建TaskService
TaskService taskService = processEngine.getTaskService();
//.查询当前用户的任务
Task task = taskService.createTaskQuery()
.taskId(taskId )
.taskAssignee("wangwu")
.singleResult();
if (task != null){
taskService.complete(taskId);
System.out.println("完成任务");
}
3、 流程变量
3.1 什么是流程变量
流程变量在 activiti中是一个非常重要的角色,流程运转有时需要靠流程变量,业务系统和 activiti结合时少不了流程变量,流程变量就是 activiti在管理工作流时根据管理需要而设置的变量。比如在请假流程流转时如果请假天数大于 3 天则由总经理审核,否则由人事直接审核,请假天数就可以设置为流程变量,在流程流转时使用。
注意:虽然流程变量中可以存储业务数据可以通过 activiti 的 的 api 查询流程变量从而实现 查询业务数据,但是不建议这样使用,因为业务数据查询由业务系统负责,activiti 设置流程变量是为了流程执行需要而创建。
3.2 流程变量类型
如果将 pojo 存储到流程变量中,必须实现序列化接口 serializable,为了防止由于新增字段无法反序列化,需要生成 serialVersionUID。
3.3 流程变量作用域
流程变量的作用域默认是一个流程实例(processInstance),也可以是一个任务(task)或一个执行实例(execution),这三个作用域流程实例的范围最大,可以 称为 global 变量,任务和执行实例仅仅是针对一个任务和一个执行实例范围,范围没有流程实例大, 称为 local 变量。
global变量中变量名不允许重复,设置相同名称的变量,后设置的值会覆盖前设置的变量值。
Local变量由于在不同的任务或不同的执行实例中,作用域互不影响,变量名可以相同没有影响。Local变量名也可以和 global变量名相同,没有影响。
3.4 流程变量的使用方法
第一步:设置流程变量
第二步:通过 UEL表达式使用流程变量
可以在 assignee 处设置 UEL表达式,表达式的值为任务的负责人
比如:${assignee},assignee 就是一个流程变量名称。Activiti获取 UEL 表达式的值 ,即流程变量 assignee 的值 ,将 assignee 的值作为任务的负责人进行任务分配
可以在连线上设置 UEL表达式,决定流程走向比如:${price>=10000}和${price<10000}: price 就是一个流程变量名称,uel 表达式结果类型为布尔类型如果 UEL表达式是 true,要决定 流程执行走向。
3.5 使用 Global 变量控制流程
3.5.1 需求:
员工创建请假申请单,由部门经理审核,部门经理审核通过后请假 3 天及以下由人事经理直接审核,3 天以上先由总经理审核,总经理审核通过再由人事经理存档。
3.5.2 流程程定 义
请假天数大于等于 3 连线条件:
请假天数小于 3 连线条件:
3.5.3 设置 global 流程变量
在部门经理审核前设置流程变量,变量值为请假单信息(包括请假天数),部门经理审核后可以根据流程变量的值决定流程走向。
3.5.3.1 启动流程时设置
在启动流程时设置流程变量,变量的作用域是整个流程实例。通过 map<key,value>设置流程变量,map 中可以设置多个变量,这个 key 就是流程变量的名字。
// 启动流程时设置流程变量
@Test
public void startProcessInstance() {
// 流程定义key
String processDefinitionKey = "";
Holiday holiday = new Holiday();
holiday.setNum(3);
// 定义流程变量
Map<String, Object> variables = new HashMap<String, Object>();
//变量名是num,变量值是holiday.getNum(),变量名也可以是一个对象
variables.put("num", holiday.getNum());
RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey(processDefinitionKey, variables);
System.out.println(" 流 程 实 例 id:" +processInstance.getProcessInstanceId());
}
startProcessInstanceByKey(processDefinitionKey, variables)流程变量作用域是一个流程实例,流程变量使用 Map存储,同一个流程实例设置变量map 中 key 相同,后者覆盖前者。
3.5.3.2 任务办理时设置
在完成任务时设置流程变量,该流程变量只有在该任务完成后其它结点才可使用该变量,它的作用域是整个流程实例,如果设置的流程变量的 key 在流程实例中已存在相同的名字则后设置的变量替换前边设置的变量。这里需要在创建请假单任务完成时设置流程变量
// 办理任务时设置流程变量
@Test
public void completTask() {
//任务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,如果当前执行的任务 id 不存在则抛出异常。任务办理时也是通过map<key,value>设置流程变量,一次可以设置多个变量。
3.5.3.3 通过当前流程实例设置
通过流程实例 id 设置全局变量,该流程实例必须未执行完成。
public void setGlobalVariableByExecutionId(){
//当前流程实例执行 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)
}
注意: executionId 必须当前未结束 流程实例的执行 id,通常此 id 设置流程实例 的 id。也可以通过 runtimeService.getVariable()获取流程变量。
3.5.3.4 通过当前任务设置
@Test
public void setGlobalVariableByTaskId(){
//当前待办任务id
String taskId="1404";
TaskService taskService = processEngine.getTaskService();
Holiday holiday = new Holiday();
holiday.setNum(3);
//通过任务设置流程变量
taskService.setVariable(taskId, "holiday", holiday);
//一次设置多个值
//taskService.setVariables(taskId, variables)
}
注意:
1、 如果 UEL表达式中流程变量名不存在则报错。
2、 如果 UEL表达式中流程变量值为空NULL,流程不按 UEL 表达式去执行,而流程结束 。
3、 如果 UEL表达式都不符合条件,流程结束。
4、 如果连线不设置条件,会走 flow 序号小的那条线。
设置流程变量会在当前执行流程变量表插入记录,同时也会在历史流程变量表也插入记录。 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变量。
3.6 设置 local 流程变量
3.6.1 任务办理时设置
任务办理时设置 local流程变量,当前运行的流程实例只能在该任务结束前使用,任务结束该变量无法在当前流程实例使用,可以通过查询历史任务查询。设置作用域为任务的 local变量,每个任务可以设置同名的变量,互不影响。
@Test
public void completTask() {
//任务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);
}
3.6.2 通过当前任务设置
@Test
public void setLocalVariableByTaskId(){
//当前待办任务id
String taskId="1404";
TaskService taskService = processEngine.getTaskService();
Holiday holiday = new Holiday ();
holiday.setNum(3);
//通过任务设置流程变量
taskService.setVariableLocal(taskId, "holiday", holiday);
//一次设置多个值
//taskService.setVariablesLocal(taskId, variables)
}
4、组任务
4.1 Candidate-users 候选人
在流程定义中在任务结点的 assignee 固定设置任务负责人,在流程定义时将参与者固定设置在.bpmn 文件中,如果临时任务负责人变更则需要修改流程定义,系统可扩展性差。针对这种情况可以给任务设置多个候选人,可以从候选人中选择参与者来完成任务。在流程图中任务节点的配置中设置 candidate-users(候选人),多个候选人之间用逗号分开。
4.2 办理组任务
4.2.1 组任务办理流程
第一步:查询组任务
指定候选人,查询该候选人当前的待办任务。候选人不能办理任务。
第二步:拾取(claim)任务
该组任务的所有候选人都能拾取。将候选人的组任务,变成个人任务。原来候选人就变成了该任务的负责人。如果拾取后不想办理该任务?需要将已经拾取的个人任务归还到组里边,将个人任务变成了组任务。
第三步:查询个人任务
查询方式同个人任务部分,根据 assignee 查询用户负责的个人任务。
第四步:办理个人任务
4.2.2 用户查询组任务
@Test
public void findGroupTaskList() {
// 流程定义key
String processDefinitionKey = "holiday4";
// 任务候选人
String candidateUser = "zhangsan";
// 创建TaskService
TaskService taskService = processEngine.getTaskService();
//查询组任务
List<Task> list = taskService.createTaskQuery()//
.processDefinitionKey(processDefinitionKey)//
.taskCandidateUser(candidateUser)//根据候选人查询
.list();
for (Task task : list) {
System.out.println("----------------------------");
System.out.println("流程实例id:" + task.getProcessInstanceId());
System.out.println("任务id:" + task.getId());
System.out.println("任务负责人:" + task.getAssignee());
System.out.println("任务名称:" + task.getName());
}
}
4.2.3 用户拾取组任务
候选人员拾取组任务后该任务变为自己的个人任务。
@Test
public void claimTask(){
TaskService taskService = processEngine.getTaskService();
//要拾取的任务id
String taskId = "6302";
//任务候选人id
String userId = "lisi";
//拾取任务
//即使该用户不是候选人也能拾取(建议拾取时校验是否有资格)
//校验该用户有没有拾取任务的资格
Task task = taskService.createTaskQuery().taskId(taskId)
.taskCandidateUser(userId).singleResult();//根据候选人查询
if(task!=null){
taskService.claim(taskId, userId);
System.out.println("任务拾取成功");
}
}
说明:即使该用户不是候选人也能拾取,建议拾取时校验是否有资格。组任务拾取后,该任务已有负责人,通过候选人将查询不到该任务。
4.2.4 用户查询个人待办任务
查询方式同个人任务查询
4.2.5 用户办理个人任务
同个人任务办理
4.2.6 归还组任务
如果个人不想办理该组任务,可以归还组任务,归还后该用户不再是该任务的负责人。
// 归还组任务,由个人任务变为组任务,还可以进行任务交接
@Test
public void setAssigneeToGroupTask() {
// 查询任务使用TaskService
TaskService taskService = processEngine.getTaskService();
// 当前待办任务
String taskId = "6004";
// 任务负责人
String userId = "zhangsan2";
// 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
Task task = taskService.createTaskQuery().taskId(taskId)
.taskAssignee(userId).singleResult();
if (task != null) {
// 如果设置为null,归还组任务,该 任务没有负责人
taskService.setAssignee(taskId, null);
}
}
说明:建议归还任务前校验该用户是否是该任务的负责人也可以通过 setAssignee 方法将任务委托给其它用户负责,注意被委托的用户可以不是候选人(建议不要这样使用)
4.2.7 任务交接
任务交接,任务负责人将任务交给其它候选人办理该任务.
@Test
public void setAssigneeToCandidateUser() {
// 查询任务使用TaskService
TaskService taskService = processEngine.getTaskService();
// 当前待办任务
String taskId = "6004";
// 任务负责人
String userId = "zhangsan2";
// 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
Task task = taskService.createTaskQuery().taskId(taskId)
.taskAssignee(userId).singleResult();
if (task != null) {
// 将此任务交给其它候选人办理该 任务
String candidateuser = "zhangsan";
// 根据候选人和组任务id查询,如果有记录说明该 候选人有资格拾取该 任务
Task task2 = taskService.createTaskQuery().taskId(taskId)
.taskCandidateUser(candidateuser).singleResult();
if (task2 != null) {
// 才可以交接
taskService.setAssignee(taskId, candidateuser);
}
}
}
5、 网关
5.1 排他网关
排他网关(也叫异或(XOR)网关,或叫基于数据的排他网关),用来在流程中实现决策。 当流程执行到这个网关,所有分支都会判断条件是否为 true,如果为 true则执行该分支,注意,排他网关只会选择一个为 true 的分支执行。(即使有两个分支条件都为 true,排他网关也会只选择一条分支去执行)
为什么要用排他网关?不用排他网关也可以实现分支,如下图:
上图中,在连线的 condition 条件上设置分支条件。
缺点: 如果条件都不满足,不使用排他网关,流程就结束了(是异常结束)。
如果 使用排他网关决定分支的走向,如下:
如果从网关出去的线所有条件都不满足则系统抛出异常。经过排他网关必须要有一条且只有一条分支走。
org.activiti.engine.ActivitiException: No outgoing sequence flow of the exclusive gateway 'exclusivegateway1' could be selected for continuing the process at
org.activiti.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehavior.leave(ExclusiveGatewayActivityBehavior.java:85)
5.2 并行网关
并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进入和外出顺序流的:
- fork 分支:并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
- join 汇聚:所有到达并行网关,在此等待的进入分支, 直到所有进入顺序流的分支都到达以后, 流程就会通过汇聚网关。
注意,如果同一个并行网关有多个进入和多个外出顺序流, 它就同时具有分支和汇聚功能。 这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。与其他网关的主要区别是,并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略。
5.3 包含 网关
包含网关可以看做是排他网关和并行网关的结合体。 和排他网关一样,你可以在外出顺序流上定义条件,包含网关会解析它们。 但是主要的区别是包含网关可以选择多于一条顺序流,这和并行网关一样。包含网关的功能是基于进入和外出顺序流的:
- 分支:所有外出顺序流的条件都会被解析,结果为true 的顺序流会以并行方式继续执行, 会为每个顺序流创建一个分支。
- 汇聚:所有并行分支到达包含网关,会进入等待状态, 直到每个包含流程 token 的进入顺序流的分支都到达。 这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。 在汇聚之后,流程会穿过包含网关继续执行。
6、Activiti7与 SpringBoot 整合开发
Activiti7 发布正式版之后,它与 SpringBoot2.x 已经完全支持整合开发。我们可以将Activiti7 与SpringBoot 整合开发的坐标引入到工程中,从而达到 SpringBoot 支持Activti7 整合。
6.1 Activiti7 新的 API 介绍
Activiti7 为了简化对工作流的操作,特别在原有 API 的基础上再次进行封闭,这样我们原来所学习的 Activiti基本 API 就被封闭起来了。具体要学习的包括:
- ProcessRuntime 接口
使用 Activiti7 开发时,只要注入 ProcessRuntime 的实现对象,就可以实现流程定义信息的操作。当然这个过程中因为 Activiti7 与 SpringSecurity 的强耦合,引导我们也必须将 SpringSecurity 加入进来。 - TaskRuntime 接口
TaskRuntime 本身就是对于TaskService 的一个封装。
6.2 SpringBoot 整合Activiti7 的依赖
<!--activiti7与SpringBoot整合的相关依赖-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.activiti/activiti-spring-boot-starter -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.0.0.Beta2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.27</version>
</dependency>
</dependencies>
6.3 SpringBoot 的 的 application.yml 文件配置
为了能够实现 Activiti7 生成的表放到 Mysql数据库中,需要在 springboot 的配置文件application.yml中添加相关的配置
spring:
datasource:
url:
jdbc:mysql://localhost:3306/activiti?useUnicode=true&characterEncoding=utf8&serverT
imezone=GMT
username : root
password : root
driver-class-name: com.mysql.jdbc.Driver
6.4 添加 SpringSecurity 安全框架整合配置
因为 Activiti7 与 SpringBoot 整合后,默认情况下,集成了 SpringSecurity 安全框架,这样我们就要去准备 SpringSecurity 整合进来的相关用户权限配置信息。可以查看一下整合 SpringBoot 的依赖包,发现同时也将 SpringSecurity 的依赖包也添加进项目中了,如下:
添加l SecurityUtil 类。 为了能够快速实现 SpringSecurity 安全框架的配置,所添加的一个组件。这个类可以从我们下载的 Activiti7 官方提供的 Example 中找到。
@Component
public class SecurityUtil {
private Logger logger = LoggerFactory.getLogger(SecurityUtil.class);
@Autowired
@Qualifier("myUserDetailsService")
private UserDetailsService userDetailsService;
public void logInAs(String username) {
UserDetails user = userDetailsService.loadUserByUsername(username);
if (user == null) {
throw new IllegalStateException("User " + username + " doesn't exist, please
provide a valid user");
}
logger.info("> Logged in as: " + username);
SecurityContextHolder.setContext(new SecurityContextImpl(new Authentication() {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getAuthorities();
}
@Override
public Object getCredentials() {
return user.getPassword();
}
@Override
public Object getDetails() {
return user;
}
@Override
public Object getPrincipal() {
return user;
}
@Override
public boolean isAuthenticated() {
return true;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws
IllegalArgumentException {
}
@Override
public String getName() {
return user.getUsername();
}
}));
org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username);
}
}
添加 DemoApplicationConfig类
在 Activiti7 官方下载的 Example 中找到 DemoApplicationConfig 类,它的作用是为了实现SpringSecurity 框架的用户权限的配置,这样我们就可以在系统中使用用户权限信息。本次项目中基本是在文件中定义出来的用户信息,当然也可以是数据库中查询的用户权限信息。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Configuration
@EnableWebSecurity
public class DemoApplicationConfiguration extends WebSecurityConfigurerAdapter {
private Logger logger = LoggerFactory.getLogger(DemoApplicationConfiguration.class);
@Override
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService());
}
@Bean
public UserDetailsService myUserDetailsService() {
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
String[][] usersGroupsAndRoles = {
{"salaboy", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"ryandawsonuk", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"erdemedeiros", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"other", "password", "ROLE_ACTIVITI_USER", "GROUP_otherTeam"},
{"admin", "password", "ROLE_ACTIVITI_ADMIN"},
};
for (String[] user : usersGroupsAndRoles) {
List<String> authoritiesStrings = Arrays.asList(Arrays.copyOfRange(user, 2, user.length));
logger.info("> Registering new user: " + user[0] + " with the following Authorities[" + authoritiesStrings + "]");
inMemoryUserDetailsManager.createUser(new User(user[0], passwordEncoder().encode(user[1]),
authoritiesStrings.stream().map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList())));
}
return inMemoryUserDetailsManager;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
6.4 使用 SpringBoot 整合 Junit 方式测试新特性
import org.activiti.api.process.model.ProcessDefinition;
import org.activiti.api.process.model.ProcessInstance;
import org.activiti.api.process.model.builders.ProcessPayloadBuilder;
import org.activiti.api.process.runtime.ProcessRuntime;
import org.activiti.api.runtime.shared.query.Page;
import org.activiti.api.runtime.shared.query.Pageable;
import org.activiti.api.task.model.Task;
import org.activiti.api.task.model.builders.TaskPayloadBuilder;
import org.activiti.api.task.runtime.TaskRuntime;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class Actviti7DemoApplicationTests {
@Autowired
private ProcessRuntime processRuntime;
@Autowired
private TaskRuntime taskRuntime;
@Autowired
private SecurityUtil securityUtil;
/**
* 查看流程定义
*/
@Test
public void contextLoads() {
securityUtil.logInAs("salaboy");
Page<ProcessDefinition> processDefinitionPage = processRuntime.processDefinitions(Pageable.of(0, 10));
System.out.println("可用的流程定义数量:" + processDefinitionPage.getTotalItems());
for (ProcessDefinition pd : processDefinitionPage.getContent()) {
System.out.println("流程定义:" + pd);
}
}
/**
* 启动流程实例
*/
@Test
public void testStartProcess() {
securityUtil.logInAs("system");
ProcessInstance pi = processRuntime.start(ProcessPayloadBuilder.start().withProcessDefinitionKey("myProcess")
.build());
System.out.println("流程实例ID:" + pi.getId());
}
/**
* 查询任务,并完成自己的任务
*/
@Test
public void testTask() {
securityUtil.logInAs("ryandawsonuk");
Page<Task> taskPage=taskRuntime.tasks(Pageable.of(0,10));
if (taskPage.getTotalItems()>0){
for (Task task:taskPage.getContent()){
taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(task.getId()).build());
System.out.println("任务:"+task);
taskRuntime.complete(TaskPayloadBuilder.complete().withTaskId(task.getId()).build());
}
}
Page<Task> taskPage2=taskRuntime.tasks(Pageable.of(0,10));
if (taskPage2.getTotalItems()>0){
System.out.println("任务:"+taskPage2.getContent());
}
}
}
6.5 使用 SpringBoot + SpringMVC
@RestController
public class TestController {
private Logger logger = LoggerFactory.getLogger(TestController.class);
@Autowired
private ProcessRuntime processRuntime;
@Autowired
private TaskRuntime taskRuntime;
@Autowired
private SecurityUtil securityUtil;
@RequestMapping(value = "/hello")
public void hello() {
// 首先,取出项目中的最多 10 个流程定义
Page<ProcessDefinition> processDefinitionPage = processRuntime.processDefinitions(Pageable.of(0, 10));
if (processDefinitionPage.getTotalItems() > 0) {
// 然后,对取出的流程进行启动
for (ProcessDefinition definition : processDefinitionPage.getContent()) {
logger.info(" 流程定义信息:" + definition);
processRuntime.start(ProcessPayloadBuilder.start().withProcessDefinitionId(definiti
on.getId()).build());
}
}
// 完成流程启动后,由于当前项目中只有 other.bpmn 一个流程,且该流程在设计时,已分配给
activitiTeam 组
// 因此我们登录一个 activitiTeam 组成员 , 该账号信息会被设置到 security 上下文中, activiti 会对其
信息进行读取
// 获取当前用户任务,最多 10 个
Page<Task> taskPage = taskRuntime.tasks(Pageable.of(0, 10));
// 由于目前只有一个流程,两个任务,我们尝试一下完成一个,看看会发生什么变化
if (taskPage.getTotalItems() > 0) {
for (Task task : taskPage.getContent()) {
logger.info(" 任务信息:" + task);
// 注意,完成任务前必须先声明
taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(task.getId()).build());
// 完成任务
taskRuntime.complete(TaskPayloadBuilder.complete().withTaskId(task.getId()).build()
);
}
}
// 上一轮任务完成,再看一下,现在流程是否走到了 second ?
Page<Task> taskPage2 = taskRuntime.tasks(Pageable.of(0, 10));
if (taskPage2.getTotalItems() > 0) {
logger.info(" 任务信息:" + taskPage2.getContent());
}
}
}
官方学习资源:
Activiti-Core 学习向导页面
https://github.com/Activiti/activiti-7-developers-guide/blob/51a1681c0e4bb5e2f96a6dea73516c9fd53
d8521/getting-started/getting-started-activiti-core.md
Activiti-Core 的 Example 示例
https://github.com/Activiti/activiti-examples