activiti入门
在本章内容中,我们来创建一个Activiti工作流,并启动这个流程。
创建Activiti工作流主要包含以下几步:
1、定义流程,按照BPMN的规范,使用流程定义工具,用流程符号把整个流程描述出来
2、部署流程,把画好的流程定义文件,加载到数据库中,生成表的数据
3、启动流程,使用java代码来操作数据库表中的内容
1.流程符号
BPMN2.0就是使用一些符号来明确业务流程设计流程图的一整套符号规范。
基本流程符号:
事件Event
活动Activity
活动是一个工作或任务的通用术语。一个活动可以是一个任务,也可以是一个当前流程的子处理流程。
常见的活动如下:
网关Getway
网关用来处理决策,常用的网关如下:
- 排他网关
只有一条路径会被选择。流程执行到该网关时,按照输出流的顺序逐个计算,当条件的计算结果为true时,继续执行当前网关的输出流。如果多条线路计算结果都是true,则会执行第一个值为true的线路。如果所有网关计算结果没有true,则引擎会抛出异常。
排他网关需要和条件顺序流结合使用,default属性指定默认顺序流,当所有的条件不满足时会执行默认顺序流。
- 并行网关
所有路径都会被同时选择。
拆分:并行执行所有输出顺序流,为每一条顺序流创建一个并行执行线路。
合并:所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才可以继续向下执行。
- 包容网关
可以同时执行多条线路,也可以在网关上设置条件。
拆分:计算每条线路上的表达式,当表达式计算结果为true时,创建一个并行线路并执行。
合并:所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才可以挤继续向下执行。
- 事件网关
专门为中间捕获事件设置的,运行设置多个输出流指向多个不同的中间捕获事件。当流程执行到事件网关后,流程处于等待状态,需要等待抛出事件才能将等待状态转换为活动状态。
流向Flow
流是连接两个流程节点的连线。常见的流向包含:
2.流程设计器使用
在idea中已经安装好bpmn插件,安装好的插件面板(可以自行百度,我这里装的是activiti bpmn visualizer):
2.1新建流程
在resource目录下新建bpmn目录,然后点击右键菜单新建:
文件名随意,默认生成一个xml文件,右键文件:
弹出绘图层
2.2绘制流程
在面板右键,选择Activities > User Task
,创建一个用户任务,并定义属性值:该节点为zhangsan创建出差申请单
再次选择Activities > User Task
,创建下一个任务,并定义属性值:部门经理审批人为lisi
再创建一个任务,定义属性值:总经理审批人为wangwu
再创建一个任务。定义属性值:财务审批为zhaoliu
最后创建一个结束事件:
然后将所有的流程符号连接起来,如下:
3.流程操作
流程定义是线下按照bpmn2.0标准去描述 业务流程,通常使用idea中的插件对业务流程进行建模。
上面步骤,创建的流程文件是.xml文件,我们需要改成bpmn文件,然后还需要该流程的png文件
png文件,直接右键导出保存到resource/bpmn路径下
bnmn文件直接将上面创建的xml文件改后缀名即可
最后在resource/bpmn路径下有两个文件,evection.png,evection.bpmn
4.流程部署
将上面的流程部署到activiti数据库中,就是流程定义部署。
通过调用activiti的api将流程定义的bpmn和png两个文件添加部署到activiti数据库中,也可以将两个文件打成zip包进行部署。
4.1单文件部署方式
/**
* 流程部署
*/
@Test
public void testDeployment() {
//创建ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//得到RepositoryService实例,该实例是activiti的资源管理类
RepositoryService repositoryService = processEngine.getRepositoryService();
//使用repositoryService进行部署
Deployment deployment = repositoryService.createDeployment()
//添加bpmn资源
.addClasspathResource("bpmn/evection.bpmn")
//添加png
.addClasspathResource("bpmn/evection.png")
.name("出差申请流程")
.deploy();
//输出部署信息
System.out.println("流程id:" + deployment.getId());
System.out.println("流程部署名:" + deployment.getName());
}
执行上面代码后activiti会将上边代码中指定的bpm文件和图片文件保存在activiti数据库。
act_re_deployment
流程定义部署表,每部署一次增加一条记录
act_re_procdef
流程定义表,部署每个新的流程定义都会在这张表中增加一条记录
act_ge_bytearray
流程资源表
注意:act_re_deployment和act_re_procdef是一对多的关系,一次部署在流程部署表生成一条记录,但一次部署可部署多个流程定义,每个流程定义在流程定义表生成一条记录。每个流程定义在act_ge_bytearray表会存在两个资源记录,bpmn和png。
建议:一次部署一个流程,这样部署表和流程定义表是一对一的关系,方便读取流程部署及流程定义信息。
4.2压缩包部署方式
/**
* 压缩包方式部署
*/
@Test
public void testDeploymentByZip() {
//定义zip流
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("bpmn/evection.zip");
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
//创建ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//获取RepositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
//使用repositoryService进行部署
Deployment deployment = repositoryService.createDeployment()
//添加zip
.addZipInputStream(zipInputStream)
.name("出差申请流程")
.deploy();
//输出部署信息
System.out.println("流程id:" + deployment.getId());
System.out.println("流程部署名:" + deployment.getName());
}
5.启动流程
流程定义部署在activiti后就可以通过工作流管理业务流程了。
针对该流程,启动一个流程表示发起一个新的出差申请单。张三发起一个出差申请单需要启动一个流程实例,出差申请单发起一个出差单也需要启动一个流程实例。
/**
* 启动流程实例
*/
@Test
public void startProcess() {
//创建ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//获取runtimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
//根据流程key启动流程
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("evection");
//输出内容
System.out.println("流程定义id:" + processInstance.getProcessDefinitionId());
System.out.println("流程实例id:" + processInstance.getId());
System.out.println("当前活动id:" + processInstance.getActivityId());
}
流程定义id:evection:1:7504
流程实例id:15001
当前活动id:null
这里根据的key查询:就是act_re_procdef
的key字段值,也是evection.bpmn20.xml
中process
标签的id值,后续的查询key同样
操作数据表
act_hi_actinst 流程实例执行历史
act_hi_identitylink 流程的参与用户历史信息
act_hi_procinst 流程实例历史信息
act_hi_taskinst 流程任务历史信息
act_ru_execution 流程执行信息
act_ru_identitylink 流程的参与用户信息
act_ru_task 任务信息
6.任务查询
流程启动后,任务的负责人就可以查询自己当前需要处理的任务,查询出来的任务都是当前用户的待办任务。
/**
* 查询当前个人待执行任务
*/
@Test
public void queryTask() {
//任务负责人
String assignee = "zhagnsan";
//创建processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//创建taskService
TaskService taskService = processEngine.getTaskService();
//根据流程任务key和任务负责人查询任务
List<Task> taskList = taskService.createTaskQuery()
.processDefinitionKey("evection") //流程key
.taskAssignee(assignee) //只查询该任务负责人的任务
.list();
taskList.forEach(task -> {
System.out.println("流程实例id:" + task.getProcessInstanceId());
System.out.println("任务id:" + task.getId());
System.out.println("任务负责人:" + task.getAssignee());
System.out.println("任务名称:" + task.getName());
});
}
流程实例id:15001
任务id:15005
任务负责人:zhagnsan
任务名称:创建出差申请单
7.流程任务处理
任务负责人查询待办任务,选择任务进行处理完成该任务。
/**
* 完成任务
*/
@Test
public void completeTask() {
//获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//获取taskService
TaskService taskService = processEngine.getTaskService();
//根据流程key和任务负责人查询任务
List<Task> taskList = taskService.createTaskQuery()
.processDefinitionKey("evection") //流程key
.taskAssignee("zhagnsan") //要查询的负责人
.list();
//完成任务,参数任务id
taskList.forEach(task -> {
taskService.complete(task.getId());
});
}
8.流程定义信息查询
查询流程相关信息,包含流程定义、流程部署、流程定义版本等
/**
* 查询流程定义
*/
@Test
public void queryProcessDefinition() {
//获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//获取RepositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
//获取ProcessDefinitionQuery
ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
/**
* 查询到当前流程的所有定义
* 条件:processDefinitionKey = evection
* orderByProcessDefinitionVersion desc 按照版本倒序
* 返回list集合
*/
List<ProcessDefinition> definitionList = processDefinitionQuery
.processDefinitionKey("evection")
.orderByProcessDefinitionVersion().desc()
.list();
//输出流程定义信息
definitionList.forEach(processDefinition -> {
System.out.println("流程定义id:" + processDefinition.getId());
System.out.println("流程定义name:" + processDefinition.getName());
System.out.println("流程定义key:" + processDefinition.getKey());
System.out.println("流程定义version:" + processDefinition.getVersion());
System.out.println("流程部署id:" + processDefinition.getDeploymentId());
});
}
流程定义id:evection:1:7504
流程定义name:evection
流程定义key:evection
流程定义version:1
流程部署id:7501
9.流程删除
/**
* 流程删除
*/
@Test
public void deleteProcess() {
//流程部署id
String deploymentId = "20001";
//获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//获取RepositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
//删除流程定义,如果该流程定义已有流程实例启动,则删除报错
repositoryService.deleteDeployment(deploymentId);
//级联删除,即时流程已有流程实例启动也可以删除
//repositoryService.deleteDeployment(deploymentId, true); //true为级联删除
}
说明:
- 使用repositoryService删除流程定义,历史表信息不会被删除
- 如果流程定义下没有流程实例启动,使用普通删除,只传流程id即可
- 如果流程定义下有流程实例启动,使用普通删除会报错,需要使用级联删除且级联删除会将相关记录全部删除
项目开发中级联删除操作一般只开放给超级管理员使用
10.流程资源下载
上面流程部署的时候,已经将流程资源文件bpmn和png文件上传到数据库中,如果需要查看这些资源文件的话,可以从数据库将资源文件下载到本地。
解决方案有:
- 在
act_ge_bytearray
表中可以看到资源文件是以BLOB的格式存储的,可以通过jdbc操作进行读取再存储到本地 - 直接使用activiti的api来实现
使用commons-io.jar 解决IO的操作
引入commons-io依赖包
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
使用api的方式实现:
/**
* 下载流程资源文件 bpmn png
*/
@Test
public void downloadFile() throws Exception {
//得到processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//得到repositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
//得到流程定义查询器,设置查询条件查询想要的流程定义
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey("evection").singleResult();
//获取到流程部署id
String deploymentId = processDefinition.getDeploymentId();
//通过deploymentId获取到png和bpmn
InputStream pngInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getDiagramResourceName());
InputStream bpmnInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());
//构建outputStream
File pngFile = new File("d:/data/evection.png");
File bpmnFile = new File("d:/data/evection.bpmn");
FileOutputStream pngOutput = new FileOutputStream(pngFile);
FileOutputStream bpmnOutput = new FileOutputStream(bpmnFile);
//输入流输出流转换
IOUtils.copy(pngInput, pngOutput);
IOUtils.copy(bpmnInput, bpmnOutput);
//关闭流
pngOutput.close();
bpmnOutput.close();
pngInput.close();
bpmnInput.close();
}
说明:
- deploymentId是流程部署id
- resource_name是
act_ge_bytearray
表中的NAME_列的值 - 使用repositoryService的
getDeploymentResourceNames
方法可以获取指定部署下的所有文件的名称 - 使用repositoryService的
getResourceAsStream
方法传入部署id和资源文件名称可以获取指定文件的输入流 - 最后将资源文件的输入流输出到本地
11.流程历史信息查询
即时流程定义已经删除,流程执行的历史信息还是可以在act_hi_*
相关的表中查询到。级联删除的不可以。
/**
* 历史信息查询
*/
@Test
public void queryHistory() {
//获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//获取historyService
HistoryService historyService = processEngine.getHistoryService();
//获取actinst表的查询对象
HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery();
//排序,根据开始时间排序
instanceQuery.orderByHistoricActivityInstanceStartTime().asc();
//查询actinst表,条件:根据instanceId查询
//instanceQuery.processInstanceId("25001");
//查询 actinst表,条件:根据 DefinitionId 查询
instanceQuery.processDefinitionId("evection:1:22504");
List<HistoricActivityInstance> instanceList = instanceQuery.list();
instanceList.forEach(instance -> {
System.out.println(instance.getId());
System.out.println("流程活动id:" + instance.getActivityId());
System.out.println("流程活动名称:" + instance.getActivityName());
System.out.println("流程实例id:" + instance.getProcessInstanceId());
System.out.println("流程定义id:" + instance.getProcessDefinitionId());
System.out.println("===================================");
});
}
25003
流程活动id:sid-07bae378-ef8d-4dab-83a7-ea0943ba27f3
流程活动名称:开始出差申请单任务
流程实例id:25001
流程定义id:evection:1:22504
===================================
25004
流程活动id:sid-60d04e4f-b36f-4e69-ad4f-4a51be3d1c3c
流程活动名称:创建出差申请单
流程实例id:25001
流程定义id:evection:1:22504
===================================
27501
流程活动id:sid-cc56cae5-1d4f-48a1-9df1-c7987b1e3ce6
流程活动名称:部门经理审批
流程实例id:25001
流程定义id:evection:1:22504
===================================