2021.12.28activiti
1.使用流程
-
部署activiti
activiti是一个工作流引擎(其实就是一堆jar包api),业务系统访问(操作)activiti的接口,就可以方便的操作流程相关数据,这样就可以把工作流环境与业务系统的环境集成在一起。
-
流程定义
使用activiti流程建模工具定义业务流程 业务流程通过(流程图和xml文件)表达
-
流程定义部署
activiti部署业务流程定义 使用activiti提供的api把流程定义内容存储起来,在activiti执行过程中可以查询定义的内容 activiti执行把流程定义内容存储在数据库中
-
启动一个流程实例
流程实例也叫:processInstance 启动一个流程实例标识开始一次业务流程的运行 在员工请假流程定义部署完成后,如果张三要请假就可以启动一个流程实例,如果李四也要请假也启动一个流程实例,两个流程的执行互相不受影响。
-
用户查询代办任务(task)
因为现在系统的业务流程已经交给activiti管理,通过activiti就可以查询当前流程执行到哪了,当前用户需要办理什么任务了,这些activiti帮我们管理了,而不需要开发人员自己编写在sql语句查询
-
用户办理任务
用户查询代办任务后,就可以办理某个任务,如果这个任务办理完成还需要其他用户办理,比如采购单创建后由部门经理审批,这个过程也是由activiti帮我们完成了。
-
流程结束
当任务办理完成没有下一个任务节点了,这个流程实例就完成了。
2.入门
1.流程
创建工作流并启动
- 定义流程,按照bpmn的规范,使用流程定义工具,使用流程符号把整个流程描述出来。
- 部署流程,把画好的流程定义文件,加载到数据库中,生成表的数据。
- 启动流程,使用java代码来操作数据库表中的内容。
2.流程符号
基本符号
- 事件Event
- 活动Activiti
- 网关GateWay
- 流向Flow
1.事件
- 开始事件
- 中间事件
- 结束事件
2.活动
- 用户任务
- 服务任务
- 子流程
3.网关
-
排他网关
可以理解为选择
-
并行网关
可以理解为&
-
包容网关
就是 || + &
-
综合网关
-
事件网关
专门为捕获中间事件设置的,允许设置多个输出流指向多个不同的中间捕获事件。当流程执行到事件网关后,流程处于等待状态,需要等待抛出事件才能将等待状态转换为活动状态。
4.流向
流就是连接两个流程节点的连线
常见的流向:
- 顺序流 sequence flow
- 消息流 message flow
- 关联 association
- 数据关联 data association
3.流程设计(定义)
流程定义
- 绘制流程(bpmn流程图)
- 指定流程定义key(就是每个流程的name)
- 指定任务负责人(每个流程的 Assignee属性)
- 生成
.bpmn
和.png
后缀文件
4.流程定义部署
将上面再设计器中定义的流程部署到activiti数据库中,就是流程定义部署。
通过调用activiti的api将流程定义的bpmn和png两个文件一个一个添加部署到activiti中,也可以将两个文件打成zip包进行部署。
单个文件部署方式
public class ActivitiDemo {
/**
* 部署流程定义
*/
@Test
public void testDeployment(){
// 1、创建ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2、得到RepositoryService实例
RepositoryService repositoryService = processEngine.getRepositoryService();
// 3、使用RepositoryService进行部署
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("bpmn/evection.bpmn") // 添加bpmn资源
.addClasspathResource("bpmn/evection.png") // 添加png资源
.name("出差申请流程")
.deploy();
// 4、输出部署信息
System.out.println("流程部署id:" + deployment.getId());
System.out.println("流程部署名称:" + deployment.getName());
}
}
执行此操作后activiti会将上边代码中指定的bpmn文件和图片保存在activiti数据库。
压缩包部署方式
将.bpmn
和 .png
压缩成zip包
@Test
public void deployProcessByZip() {
// 定义zip输入流
InputStream inputStream = this
.getClass()
.getClassLoader()
.getResourceAsStream(
"bpmn/evection.zip");
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
// 获取repositoryService
RepositoryService repositoryService = processEngine
.getRepositoryService();
// 流程部署
Deployment deployment = repositoryService.createDeployment()
.addZipInputStream(zipInputStream)
.deploy();
System.out.println("流程部署id:" + deployment.getId());
System.out.println("流程部署名称:" + deployment.getName());
}
执行此操作后activiti会将上边代码中指定的bpmn文件和图片保存在activiti数据库。
操作数据表
流程定义部署后操作activiti的三张表:
- act_re_deployment 流程定义部署表,每部署一次增加一条记录
- act_re_procdef 流程定义表,部署每个新的流程定义都会在这张表中增加一条记录
- act_ge_bytearray 流程资源表
注意:
- act_re_deployment和act_re_procdef一对多关系,一次部署在流程部署表生成一条记录,但一次部署可以部署多个流程定义,每个流程定义在流程定义表生成一条记录。每一个流程定义在act_ge_bytearray会存在两个资源记录,bpmn和png
建议:
- 一次部署一个流程,这样部署表和流程定义表是一对一有关系,方便读取流程部署及流程定义信息。
5.启动流程实例
流程定义部署在activiti后就可以通过工作流管理业务流程了,也就是说上边部署的流程可以使用了。
启动一个流程标识发起一个新的申请单,这就相当于java类和java对象的关系,类定义好需要new创建一个对象使用。当然也可以new多个对象。
6.工作流数据库
SELECT * FROM act_re_deployment --流程定义部署表,记录流程部署信息
SELECT * FROM act_re_procdef --流程定义表,记录流程定义信息
SELECT * FROM act_ge_bytearray --资源表
SELECT * FROM ACT_RU_EXECUTION --正在执行的执行对象表
SELECT * FROM ACT_HI_PROCINST --流程实例的历史表
ORDER BY START_TIME_ DESC;
SELECT * FROM ACT_HI_TASKINST --历史的任务表(只有节点是userTask的时候,该表中存在数据)
ORDER BY START_TIME_ DESC;
SELECT * FROM ACT_RU_TASK --正在执行任务表(只有节点是userTask的时候,该表中存在数据)
SELECT * FROM ACT_HI_ACTINST--所有活动节点的历史表
ORDER BY START_TIME_ DESC;
3.进阶
3.1流程实例
流程实例:ProcessInstance 代表流程定义的执行实例
流程定义:processDefinition
一个流程实例包括了所有的运行节点。我们可以利用这个对象来了解当前流程实例的进度等信息。
例如:用户或程序按照流程定义内容发起一个流程,这就是一个流程实例。
启动流程实例,并添加Businesskey(业务标识)
流程定义部署在activiti后,就可以在系统中通过activiti去管理该流程的执行,执行流程表示流程的一次执行。
比如部署系统出差流程后,如果某用户要申请出差这时就要执行这个流程,如果另外一个用户也要申请出差则也需要执行该流程,每个执行互不影响,每个执行是单独的流程实例。
启动流程实例时,指定的businesskey,就会在act_ru_execution
#流程实例的执行表中存储businesskey。
Bussinesskey:业务标识,通常为业务表的主键,业务标识和流程实例 一 一 对应。业务标识来源于业务系统。存储业务标识就是根据业务标识来关联查询业务系统的数据。
比如:出差流程启动一个流程实例,就可以将出差单的id作为业务标识存储到activiti中,将来查询activiti的流程实例信息就可以获取出差单的id从而关联查询业务系统数据库得到出差单信息。
/**
* 启动流程实例,添加businessKey
*/
@Test
public void addBusinessKey(){
// 1、得到ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2、得到RunTimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 3、启动流程实例,同时还要指定业务标识businessKey,也就是出差申请单id,这里是1001
ProcessInstance processInstance = runtimeService.
startProcessInstanceByKey("myEvection","1001");
// 4、输出processInstance相关属性
System.out.println("业务id=="+processInstance.getBusinessKey());
}
act_ru_execution中存储业务标识:
3.2操作数据库表
act_ru_execution
SELECT * FROM act_ru_execution #流程实例执行表,记录当前流程实例的执行情况
说明:
流程实例执行,如果当前只有一个分支时,一个流程实例只有一条记录且执行表的主键id和流程实例id相同,如果当前有多个分支正在运行则该执行表中有多条记录,存在执行表的主键和流程实例id不相同的记录。不论当前有几个分支,总会有一条记录的执行表的主键和流程实例id相同
一个流程实例运行完成,此表中与流程实例相关的记录删除。
act_ru_task
SELECT * FROM act_ru_task #任务执行表,记录当前执行的任务
说明:
启动流程实例,流程当前执行到第一个任务节点,此表会插入一条记录表示当前任务的完成情况,如果任务完成则删除情况。
act_ru_identitylink
SELECT * FROM act_ru_identitylink #任务参与者,记录当前参与任务的用户或组
act_hi_procinst
SELECT * FROM act_hi_procinst #流程实例历史表
流程实例启动,会在此表插入一条记录,流程实例运行完成记录也不会删除。
act_hi_taskinst
SELECT * FROM act_hi_taskinst #任务历史表,记录所有任务
开始一个任务,不仅在act_ru_task表插入记录,也会在历史任务表插入一条记录,任务历史表的主键就是任务id,任务完成此表记录不删除。
act_hi_actinst
SELECT * FROM act_hi_actinst #活动历史表,记录所有活动
活动包括任务,所以此表中不仅记录了任务,还记录了流程执行过程的其它活动,比如开始事件、结束事件。
3.3查询流程实例
流程在运行过程中可以查询流程实例的状态,当前运行节点等信息。
@Test
public void queryProcessInstance() {
// 流程定义key
String processDefinitionKey = "evection";
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取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());
}
}
3.4挂起,激活流程实例
某些情况可能由于流程变更需要将当前运行的流程暂停而不是直接删除,流程暂停后将不会继续执行
3.4.1全部流程实例挂起
操作流程定义为挂起状态,该流程定义下边所有的流程实例全部暂停:
流程定义为挂起状态,该流程定义将不允许启动新的流程实例,同时该流程定义下所有的流程实例将全部挂起暂停执行。
/**
* 全部流程实例挂起与激活
*/
@Test
public void SuspendAllProcessInstance(){
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取repositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
// 查询流程定义的对象
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().
processDefinitionKey("myEvection").
singleResult();
// 得到当前流程定义的实例是否都为暂停状态
boolean suspended = processDefinition.isSuspended();
// 流程定义id
String processDefinitionId = processDefinition.getId();
// 判断是否为暂停
if(suspended){
// 如果是暂停,可以执行激活操作 ,参数1 :流程定义id ,参数2:是否激活,参数3:激活时间
repositoryService.activateProcessDefinitionById(processDefinitionId,
true,
null
);
System.out.println("流程定义:"+processDefinitionId+",已激活");
}else{
// 如果是激活状态,可以暂停,参数1 :流程定义id ,参数2:是否暂停,参数3:暂停时间
repositoryService.suspendProcessDefinitionById(processDefinitionId,
true,
null);
System.out.println("流程定义:"+processDefinitionId+",已挂起");
}
}
3.4.2单个流程实例挂起
操作流程实例对象,针对单个流程执行挂起操作,某个流程实例挂起,则此流程不再继续执行,完成该流程实例的当前任务将报异常。
/**
* 单个流程实例挂起与激活
*/
@Test
public void SuspendSingleProcessInstance(){
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// RuntimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 查询流程定义的对象
ProcessInstance processInstance = runtimeService.
createProcessInstanceQuery().
processInstanceId("15001").
singleResult();
// 得到当前流程定义的实例是否都为暂停状态
boolean suspended = processInstance.isSuspended();
// 流程定义id
String processDefinitionId = processInstance.getId();
// 判断是否为暂停
if(suspended){
// 如果是暂停,可以执行激活操作 ,参数:流程定义id
runtimeService.activateProcessInstanceById(processDefinitionId);
System.out.println("流程定义:"+processDefinitionId+",已激活");
}else{
// 如果是激活状态,可以暂停,参数:流程定义id
runtimeService.suspendProcessInstanceById( processDefinitionId);
System.out.println("流程定义:"+processDefinitionId+",已挂起");
}
}
/**
* 测试完成个人任务
*/
@Test
public void completTask(){
// 获取引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取操作任务的服务 TaskService
TaskService taskService = processEngine.getTaskService();
// 完成任务,参数:流程实例id,完成zhangsan的任务
Task task = taskService.createTaskQuery()
.processInstanceId("15001")
.taskAssignee("rose")
.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());
}
3.5个人任务
3.5.1分配任务责任人
3.5.1.1固定分配
在进行业务流程建模时指定固定的任务负责人
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cRSFcksu-1640859075534)(C:\Users\pengzhang1\Desktop\图库\activiti\固定分配任务责任人.jpg)]
如图:zhangsan
为任务负责人
3.5.1.2表达式分配
由于固定分配的缺陷:任务只管一步一步执行任务,执行到每一个任务将按照bpmn 的配置去分配任务负责人。所以有了表达式分配。
UEL表达式
activiti使用UEL表达式,UEL是java EE6规范的一部分,UEL(unified Expression Language)即统一表达式语言,activiti 支持两个UEL表达式:UEL-value 和 UEL-method
UEL-value定义
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OSRYP6kA-1640859075535)(C:\Users\pengzhang1\Desktop\图库\activiti\UEL-value分配任务人.jpg)]
assignnee 这个变量是 activiti 的一个流程变量
或者使用另一种方式定义:
${user.assignee}
user 也是activiti的一个流程变量,user.assginee表示通过调用user 的getter方式获取值
UEL-method
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TBxh5cI4-1640859075536)(C:\Users\pengzhang1\Desktop\图库\activiti\UEl-method分配任务人.jpg)]
userBean 是 spring 容器中的一个Bean,表示调用该bean的getUserId()方法。
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}
3.5.1.3编写代码配置负责人
1)定义任务分配流程变量
2) 设置流程变量
在启动流程实例的时候设置流程变量
/**
* 设置流程负责人
*/
@Test
public void assigneeUEL(){
// 获取流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取 RuntimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 设置assignee的取值,用户可以在界面上设置流程的执行
Map<String,Object> assigneeMap = new HashMap<>();
assigneeMap.put("assignee0","张三");
assigneeMap.put("assignee1","李经理");
assigneeMap.put("assignee2","王总经理");
assigneeMap.put("assignee3","赵财务");
// 启动流程实例,同时还要设置流程定义的assignee的值
runtimeService.startProcessInstanceByKey("myEvection1",assigneeMap);
// 输出
System.out.println(processEngine.getName());
}
执行成功后,可以在act_ru_variable
表中看到刚才map中的数据
3.5.1.4注意事项
由于使用了表达式分配,必须保证在任务执行过程表达式执行成功,比如:
某个任务使用了表达式${order.price > 100 && order.price < 250},当执行该任务时必须保证 order 在
流程变量中存在,否则 activiti 异常。
3.5.1.5监听器分配
可以是哦哟那个监听器来完成很多activiti流程的业务
可以使用监听器的方式来指定负责人,那么在流程设计时就不需要指定assignee
任务监听器是发生对应的任务相关事件时执行自定义java逻辑或表达式
任务相当事件包括:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1oQV8eE5-1640859075536)(C:\Users\pengzhang1\Desktop\图库\activiti\Listener.jog)]
Event的选项包括:
Create:任务创建后触发
Assignment:任务分配后触发
Delete:任务完成后触发
All:所有事件发生都触发
定义任务监听类,且类必须实现 org.activiti.engine.delegate.TaskListener 接口
public class MyTaskListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
if(delegateTask.getName().equals("创建出差申请")&&
delegateTask.getEventName().equals("create")){
//这里指定任务负责人
delegateTask.setAssignee("张三");
}
}
}
DelegateTask对象的内容如下:
就是个对象
String getId();
String getName();
void setName(String var1);
String getDescription();
void setDescription(String var1);
int getPriority();
void setPriority(int var1);
String getProcessInstanceId();
String getExecutionId();
String getProcessDefinitionId();
Date getCreateTime();
String getTaskDefinitionKey();
boolean isSuspended();
String getTenantId();
String getFormKey();
void setFormKey(String var1);
String getEventName();
String getEventHandlerId();
DelegationState getDelegationState();
void addCandidateUser(String var1);
void addCandidateUsers(Collection<String> var1);
void addCandidateGroup(String var1);
void addCandidateGroups(Collection<String> var1);
String getOwner();
void setOwner(String var1);
String getAssignee();
void setAssignee(String var1);
Date getDueDate();
void setDueDate(Date var1);
String getCategory();
void setCategory(String var1);
void addUserIdentityLink(String var1, String var2);
void addGroupIdentityLink(String var1, String var2);
void deleteCandidateUser(String var1);
void deleteCandidateGroup(String var1);
void deleteUserIdentityLink(String var1, String var2);
void deleteGroupIdentityLink(String var1, String var2);
Set<IdentityLink> getCandidates();
注意事项
使用监听器分配方式,按照监听器事件去执行监听器的 notify方法,方法如果不能正常执行也会收到影响
3.6查询任务
查询任务负责人的待办任务
// 查询当前个人待执行的任务
@Test
public void findPersonalTaskList() {
// 流程定义key
String processDefinitionKey = "myEvection1";
// 任务负责人
String assignee = "张三";
// 获取TaskService
TaskService taskService = processEngine.getTaskService();
List<Task> taskList = taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
.includeProcessVariables()
.taskAssignee(assignee)
.list();
for (Task task : taskList) {
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 findProcessInstance(){
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取TaskService
TaskService taskService = processEngine.getTaskService();
// 获取RuntimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 查询流程定义的对象
Task task = taskService.createTaskQuery()
.processDefinitionKey("myEvection1")
.taskAssignee("张三")
.singleResult();
// 使用task对象获取实例id
String processInstanceId = task.getProcessInstanceId();
// 使用实例id,获取流程实例对象
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
// 使用processInstance,得到 businessKey
String businessKey = processInstance.getBusinessKey();
System.out.println("businessKey=="+businessKey);
}
3.7、办理任务
注意:在实际应用中,完成任务前需要校验任务的负责人是否有该任务的办理权限
/**
* 完成任务,判断当前用户是否有权限
*/
@Test
public void completTask() {
//任务id
String taskId = "15005";
// 任务负责人
String assingee = "张三";
//获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 创建TaskService
TaskService taskService = processEngine.getTaskService();
// 完成任务前,需要校验该负责人可以完成当前任务
// 校验方法:
// 根据任务id和任务负责人查询当前任务,如果查到该用户有权限,就完成
Task task = taskService.createTaskQuery()
.taskId(taskId)
.taskAssignee(assingee)
.singleResult();
if(task != null){
taskService.complete(taskId);
System.out.println("完成任务");
}
}
3.8 流程变量
流程变量在activiti 中是一个非常重要的角色,流程运转有时需要靠流程变量,业务系统和activiti结合时少不了流程变量,流程变量就是activiti在管理工作流的时候根据管理需要而设置的变量。
比如:在出差申请流程流转的时候如果出差天数大于三天则由总经理审核,否则由人事直接审核,出差天数就可以设置为流程变量,在流程流转时使用。
注意:虽然流程变量中可以存储业务数据可以通过activiti的api查询流程变量从而实现查询业务数据,但是不建议这样使用,因为业务数据查询由业务系统负责,activiti设置流程变量是为了流程执行需要而创建。
3.8.1流程变量类型
如果将pojo存储到流程变量中,必须实现序列化接口serializable,为了防止由于新增字段无法反序列化,需要生成serialVersionUID。
3.8.2流程变量作用域
流程变量的作用域可以是一个流程实例(processInstance),或者一个任务(task),或者一个执行实例(execution)。
3.8.2.1globa变量
流程变量的默认作用域是流程实例。当一个流程变量的作用域为流程实例时,可以称之为gloal变量。
注意:
如:Global变量:userID(变量名),zhangsan(变量名)
global变量中变量名不允许重复,设置相同名称的变量,后设置的值会覆盖前设置的变量值。
3.8.2.2local变量
任务和执行实例仅仅是针对一个任务和一个执行实例范围,范围没有流程实例大,称为local变量。
local 变量由于在不同的任务或者不同的执行实例中,作用域互不影响,变量名可以相同没有影响。local变量名也可以和global变量名相同,没有影响。
3.8.3流程变量的使用方法
3.8.3.1在属性上使用UEL表达式
可以在assignee处设置UEL表达式,表达式的值为任务的负责人,比如:${assignee},assignee就是一个流程变量名称。
activiti获取UEL表达式的值,即流程变量assignee的值,将assignee的值作为任务的负责人进行任务分配。
3.8.3.2在连线上使用UEL表达式
可以在连线上设置UEL表达式,决定流程走向。
比如:${price < 1000}。price就是一个流程变量名称,UEL表达式结果类型为布尔类型。
如果UEL表达式是true,要决定流程执行走向。
3.8.4使用global变量控制流程
3.8.4.1需求
员工创建出差申请单,由部门经理审核,部门经理审核通过后出差3天及以下由人财务直接审批,3天以上先由总经理审核,总经理审核通过再由财务审批。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-toWDz042-1640859075537)(C:\Users\pengzhang1\Desktop\图库\activiti\global变量实例.jpg)]
3.8.4.2流程定义
1)、出差天数大于3连线条件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3mrVjx94-1640859075537)(C:\Users\pengzhang1\Desktop\图库\activiti\出差天数大于3天.jpg)]
2)、也可以使用对象参数命名,如evetion.num
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Gs4x2Jw-1640859075538)(C:\Users\pengzhang1\Desktop\图库\activiti\对象参数命名.jpg)]
3.8.4.3设置global流程变量
在部门经理审核前设置流程变量,变量值为出差单信息(包括出差天数),部门经理审核后可以根据流程变量的值决定流程走向。
在设置流程变量时,可以在启动流程时设置,也可以在任务办理时设置
3.8.4.3.1创建pojo对象
创建出差申请poio对象
package com.itheima.demo.pojo;
import java.io.Serializable;
import java.util.Date;
/**
* 出差申请 pojo
*/
public class Evection implements Serializable {
/**
* 主键id
*/
private Long id;
/**
* 出差申请单名称
*/
private String evectionName;
/**
* 出差天数
*/
private Double num;
/**
* 预计开始时间
*/
private Date beginDate;
/**
* 预计结束时间
*/
private Date endDate;
/**
* 目的地
*/
private String destination;
/**
* 出差事由
*/
private String reson;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getEvectionName() {
return evectionName;
}
public void setEvectionName(String evectionName) {
this.evectionName = evectionName;
}
public Date getBeginDate() {
return beginDate;
}
public void setBeginDate(Date beginDate) {
this.beginDate = beginDate;
}
public Date getEndDate() {
return endDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
public String getDestination() {
return destination;
}
public void setDestination(String destination) {
this.destination = destination;
}
public String getReson() {
return reson;
}
public void setReson(String reson) {
this.reson = reson;
}
public Double getNum() {
return num;
}
public void setNum(Double num) {
this.num = num;
}
}
3.8.4.3.2启动流程时设置变量
在启动流程时设置流程变量,变量的作用域是整个流程实例。
通过Map<key,value> 设置流程变量,map中可以设置多个变量,这个key就是流程变量的名。
/**
* 启动流程实例,设置流程变量的值
*/
@Test
public void startProcess(){
// 获取流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取RunTimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 流程定义key
String key = "myEvection2";
// 创建变量集合
Map<String, Object> map = new HashMap<>();
// 创建出差pojo对象
Evection evection = new Evection();
// 设置出差天数
evection.setNum(2d);
// 定义流程变量,把出差pojo对象放入map
map.put("evection",evection);
// 设置assignee的取值,用户可以在界面上设置流程的执行
map.put("assignee0","张三");
map.put("assignee1","李经理");
map.put("assignee2","王总经理");
map.put("assignee3","赵财务");
// 启动流程实例,并设置流程变量的值(把map传入)
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey(key, map);
// 输出
System.out.println("流程实例名称="+processInstance.getName());
System.out.println("流程定义id=="+processInstance.getProcessDefinitionId());
}
/**
* 完成任务,判断当前用户是否有权限
*/
@Test
public void completTask() {
//任务id
String key = "myEvection2";
// 任务负责人
String assingee = "张三";
//获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 创建TaskService
TaskService taskService = processEngine.getTaskService();
// 完成任务前,需要校验该负责人可以完成当前任务
// 校验方法:
// 根据任务id和任务负责人查询当前任务,如果查到该用户有权限,就完成
Task task = taskService.createTaskQuery()
.processDefinitionKey(key)
.taskAssignee(assingee)
.singleResult();
if(task != null){
taskService.complete(task.getId());
System.out.println("任务执行完成");
}
}
说明:
startProcessInstanceByKey(processDefinitionKey, variables)
流程变量作用域是一个流程实例,流程变量使用Map存储,同一个流程实例设置变量map中key相同,后者覆盖前者。
3.8.4.3.3任务办理时设置变量
在完成任务时设置流程变量,该流程变量只有在该任务完成后其他节点才可使用该变量,它的作用域是整个流程实例,如果设置的流程变量的key在流程实例中已存在相同的名字则后设置的变量替换前边设置的变量。
这里需要在创建出差任务单任务完成时设置流程变量。
/**
* 完成任务,判断当前用户是否有权限
*/
@Test
public void completTask() {
//任务id
String key = "myEvection2";
// 任务负责人
String assingee = "张三";
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 创建TaskService
TaskService taskService = processEngine.getTaskService();
// 创建变量集合
Map<String, Object> map = new HashMap<>();
// 创建出差pojo对象
Evection evection = new Evection();
// 设置出差天数
evection.setNum(2d);
// 定义流程变量
map.put("evection",evection);
// 完成任务前,需要校验该负责人可以完成当前任务
// 校验方法:
// 根据任务id和任务负责人查询当前任务,如果查到该用户有权限,就完成
Task task = taskService.createTaskQuery()
.processDefinitionKey(key)
.taskAssignee(assingee)
.singleResult();
if(task != null){
//完成任务是,设置流程变量的值
taskService.complete(task.getId(),map);
System.out.println("任务执行完成");
}
}
说明:
通过当前任务设置流程变量,需要指定当前任务id,如果当前执行的任务id不存在则抛出异常。
任务办理时也是通过map<key,value>设置流程变量,一次可以设置多个变量。
3.8.4.3.4通过流程实例设置
通过流程实例设置id设置全局变量,该流程实例必须未执行完成。
@Test
public void setGlobalVariableByExecutionId(){
// 当前流程实例执行 id,通常设置为当前执行的流程实例
String executionId="2601";
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取RuntimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 创建出差pojo对象
Evection evection = new Evection();
// 设置天数
evection.setNum(3d);
// 通过流程实例 id设置流程变量
runtimeService.setVariable(executionId, "evection", evection);
// 一次设置多个值
// runtimeService.setVariables(executionId, variables)
}
注意:
executionId必须当前未结束流程实例的执行id,通常此id设置流程实例的id。也可以通过runtimeService.getVariable()获取流程变量。
3.8.4.3.5通过当前任务设置
@Test
public void setGlobalVariableByTaskId(){
//当前待办任务id
String taskId="1404";
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
Evection evection = new Evection();
evection.setNum(3);
//通过任务设置流程变量
taskService.setVariable(taskId, "evection", evection);
//一次设置多个值
//taskService.setVariables(taskId, variables)
}
注意:
任务id必须是档期啊代办任务id,act_ru_task中存在。如果该任务已结束,会报错
也可以通过taskService.getVariable()获取流程变量。
3.8.4.4测试
正常测试:
设置流程变量的值大于等于3天
设计流程变量的值小于3天
异常测试:
流程变量不存在
流程变量的值为空NULL,price属性为空
UEL表达式都不符合条件
不设置连线的条件
3.8.4.5注意事项
1、 如果UEL表达式中流程变量名不存在则报错。
2、 如果UEL表达式中流程变量值为空NULL,流程不按UEL表达式去执行,而流程结束 。
3、 如果UEL表达式都不符合条件,流程结束
4、 如果连线不设置条件,会走flow序号小的那条线
3.8.4.6操作数据库表
设置流程变量会在当前执行流程变量表插入记录,同时也会在流程变量表也插入数据。
//当前流程变量表
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.8.5设置local流程变量
3.8.5.1任务办理时设置
任务办理时设置local流程变量。当前运行的流程实例只能在该任务结束前使用。任务结束后该变量无法在当前流程实例使用。可以通过查询历史任务查询。
/*
*处理任务时设置local流程变量
*/
@Test
public void completTask() {
//任务id
String taskId = "1404";
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
// 定义流程变量
Map<String, Object> variables = new HashMap<String, Object>();
Evection evection = new Evection ();
evection.setNum(3d);
// 定义流程变量
Map<String, Object> variables = new HashMap<String, Object>();
// 变量名是holiday,变量值是holiday对象
variables.put("evection", evection);
// 设置local变量,作用域为该任务
taskService.setVariablesLocal(taskId, variables);
// 完成任务
taskService.complete(taskId);
}
说明:
任务id必须是当前代办任务id,act_id_task中存在。
3.8.5.2local变量测试1
如果上边例子中设置global变量改为设置local变量是否可行?为什么?
Local变量在任务结束后无法在当前流程实例执行中使用,如果后续的流程执行需要用到此变量则会报错。
3.8.5.3local变量测试2
在部门经理审核、总经理审核、财务审核时设置local变量,可通过historyService查询每个历史任务时将流程变量的值也查询出来。
代码如下:
// 创建历史任务查询对象
HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery();
// 查询结果包括 local变量
historicTaskInstanceQuery.includeTaskLocalVariables();
for (HistoricTaskInstance historicTaskInstance : list) {
System.out.println("==============================");
System.out.println("任务id:" + historicTaskInstance.getId());
System.out.println("任务名称:" + historicTaskInstance.getName());
System.out.println("任务负责人:" + historicTaskInstance.getAssignee());
System.out.println("任务local变量:"+ historicTaskInstance.getTaskLocalVariables());
}
注意:
查询历史流程变量,特别是查询pojo变量需要经过反序列化,不推荐使用。
3.9组任务
3.9.1需求
在流程定义中在任务结点的 assignee 固定设置任务负责人,在流程定义时将参与者固定设置在.bpmn 文件中,如果临时任务负责人变更则需要修改流程定义,系统可扩展性差。
针对这种情况可以给任务设置多个候选人,可以从候选人中选择参与者来完成任务。
3.9.2设置任务候选人
在流程图中任务节点的配置中设置 candidate-users(候选人),多个候选人之间用逗号分开。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DEuGlxJ3-1640859075538)(C:\Users\pengzhang1\Desktop\图库\activiti\任务候选人.jpg)]
查看bpmn文件:
<userTask activiti:assignee="${initiator}" activiti:candidateGroups="managers" activiti:candidateUsers="zhangsan,lisi" activiti:exclusive="true" completionQuantity="1" id="enterOnboardingData" implementation="##unspecified" isForCompensation="false" name="Enter Data" startQuantity="1">
我们可以看到部门经理的审核人已经设置为zhangsan,lisi 这样的一组候选人,可以使用
activiti:candiateUsers=”用户 1,用户 2,用户 3”的这种方式来实现设置一组候选人
3.9.3组任务
3.9.3.1组任务办理流程
a、查询组任务
指定候选人,查询该候选人当前的待办任务。
候选人不能立即办理任务。
b、拾取(claim)任务
该组任务的所有候选人都能拾取。
将候选人的组任务,变成个人任务。原来候选人就变成了该任务的负责人。
如果拾取后不想办理该任务?
需要将已经拾取的个人任务归还到组里边,将个人任务变成了组任务。
c、查询个人任务
查询方式同个人任务部分,根据assignee查询用户负责的个人任务。
d、办理个人任务
3.9.3.2查询组任务
根据候选人查询组任务
@Test
public void findGroupTaskList() {
// 流程定义key
String processDefinitionKey = "evection3";
// 任务候选人
String candidateUser = "lisi";
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 创建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());
}
}
3.9.3.3拾取组任务
候选人员拾取组任务后该任务就变成为自己的个人任务
@Test
public void claimTask(){
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
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("任务拾取成功");
}
}
说明:
即使该用户不是候选人也能拾取,建议拾取时校验是否有资格
组任务拾取后,该任务已有负责人,通过候选人将查询不到该任务
3.9.3.4查询个人代办任务
查询方式同个人任务查询
@Test
public void findPersonalTaskList() {
// 流程定义key
String processDefinitionKey = "evection1";
// 任务负责人
String assignee = "zhangsan";
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 创建TaskService
TaskService taskService = processEngine.getTaskService();
List<Task> list = taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
.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());
}
}
3.9.3.5办理个人任务
同个人任务办理
/*完成任务*/
@Test
public void completeTask(){
// 任务ID
String taskId = "12304";
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
processEngine.getTaskService()
.complete(taskId);
System.out.println("完成任务:"+taskId);
}
说明:
建议完成任务前校验该用户是否是该任务的负责人。
3.9.3.6归还组任务
如果个人不想办理该组任务,可以归还任务,归还后该用户不再是该任务的负责人。
/*
*归还组任务,由个人任务变为组任务,还可以进行任务交接
*/
@Test
public void setAssigneeToGroupTask() {
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 查询任务使用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方法将任务委托给其他用户负责,注意被委托的用户可以不是候选人(建议不要这样使用)。
3.9.3.7任务交接
任务交接,任务负责人将任务交给其他候选人办理该任务。
@Test
public void setAssigneeToCandidateUser() {
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 查询任务使用TaskService
TaskService taskService = processEngine.getTaskService();
// 当前待办任务
String taskId = "6004";
// 任务负责人
String userId = "zhangsan2";
// 将此任务交给其它候选人办理该 任务
String candidateuser = "zhangsan";
// 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
Task task = taskService
.createTaskQuery()
.taskId(taskId)
.taskAssignee(userId)
.singleResult();
if (task != null) {
taskService.setAssignee(taskId, candidateuser);
}
}
3.9.3.8数据库操作
查询当前任务执行表
SELECT * FROM act_ru_task
任务执行表,记录当前执行的任务,由于该任务当前是组任务,所有assignee为空,当拾取任务后该字段就是拾取用户的id
查询任务参与者
SELECT * FROM act_ru_identitylink
任务参与者,记录当前参考任务用户或组,当前任务如果设置了候选人,会向该表插入候选人记录,有几个候选就插入几个
与act_ru_identitylink对应的还有一张历史表act_hi_identitylink,向act_ru_identitylink插入记录的同时也会向历史表插入记录。任务完成
ngsan2";
// 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
Task task = taskService
.createTaskQuery()
.taskId(taskId)
.taskAssignee(userId)
.singleResult();
if (task != null) {
// 如果设置为null,归还组任务,该 任务没有负责人
taskService.setAssignee(taskId, null);
}
}
**说明:**
建议归还任务前校验该用户是否是该任务的负责人
也可以通过setAssignee方法将任务委托给其他用户负责,注意被委托的用户可以不是候选人(建议不要这样使用)。
##### 3.9.3.7任务交接
任务交接,任务负责人将任务交给其他候选人办理该任务。
```java
@Test
public void setAssigneeToCandidateUser() {
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 查询任务使用TaskService
TaskService taskService = processEngine.getTaskService();
// 当前待办任务
String taskId = "6004";
// 任务负责人
String userId = "zhangsan2";
// 将此任务交给其它候选人办理该 任务
String candidateuser = "zhangsan";
// 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
Task task = taskService
.createTaskQuery()
.taskId(taskId)
.taskAssignee(userId)
.singleResult();
if (task != null) {
taskService.setAssignee(taskId, candidateuser);
}
}
3.9.3.8数据库操作
查询当前任务执行表
SELECT * FROM act_ru_task
任务执行表,记录当前执行的任务,由于该任务当前是组任务,所有assignee为空,当拾取任务后该字段就是拾取用户的id
查询任务参与者
SELECT * FROM act_ru_identitylink
任务参与者,记录当前参考任务用户或组,当前任务如果设置了候选人,会向该表插入候选人记录,有几个候选就插入几个
与act_ru_identitylink对应的还有一张历史表act_hi_identitylink,向act_ru_identitylink插入记录的同时也会向历史表插入记录。任务完成