Activiti 流程操作
1、流程定义
流程定义是线下按照bpmn2.0标准去描述业务流程,通常使用 idea 中的插件对业务流程进行建模。使用 idea下的designer设计器绘制流程,并会生成两个文件:.bpmn和.png
个人理解:流程定义就是一个大的层面,比如请假流程,是整个公司都通用的,它定义了整个公司的请假要走的流程,因此叫做流程定义。
如何生成 .png 图片文件?
- 先把 apply.bpmn 在 IDEA 重命名为:apply.xml
- 对着 apply.xml 鼠标右键,找到【Diagrams】->【Show BPMN 2.0 Designer】
3、找到顶部菜单的导出文件按钮,然后选择导出的目录,默认是 png 格式。导出即可。
结果如图:
生成 png 文件后,把 apply.xml 重命名回 apply.bpmn。然后把 apply.png 文件也放到 resources 目录下。
2、流程定义的部署
将上面在设计器中定义的流程部署到activiti数据库中,就是流程定义部署。
通过调用 activiti 的api将流程定义的 bpmn和png两个文件一个一个添加部署到activiti中,也可以将两个文件打成zip包进行部署。
2-1、单个文件部署方式
/**
* 部署流程定义
*/
@Test
public void testDeployment(){
//创建 ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//得到 RepositoryService 实例
RepositoryService service = processEngine.getRepositoryService();
//使用 RepositoryService 实例进行部署
Deployment deployment = service.createDeployment()
.addClasspathResource("bpmn/apply.bpmn") //添加 bpmn 资源
.addClasspathResource("bpmn/apply.png") //添加 png 资源
.name("员工请假流程")
.deploy();
System.out.println("流程部署Id="+deployment.getId());
System.out.println("流程部署name="+deployment.getName());
}
点击运行,控制台输出:
流程部署Id=1
流程部署name=员工请假流程
2-2、压缩包部署方式
注意:如果使用了【单个文件的部署】方式,就不要使用压缩包的部署方式了,不然会导致重复的数据,后面的测试容易出错。如果数据出错,建议把数据库删掉,重新建表,弄测试数据来测。
需要把 apply.bpmn 和 apply.png 压缩成 zip 格式,压缩包名字叫做:apply.zip 并放到 resources/bpmn 目录下。
/**
* 压缩包方式流程部署
*/
@Test
public void deployProcessByZip() {
// 定义zip输入流
InputStream inputStream = this
.getClass()
.getClassLoader()
.getResourceAsStream("bpmn/apply.zip");
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
//创建 ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//得到 RepositoryService 实例
RepositoryService service = processEngine.getRepositoryService();
// 流程部署
Deployment deployment = service.createDeployment()
.addZipInputStream(zipInputStream)
.name("员工请假流程-2")
.deploy();
System.out.println("流程部署id:" + deployment.getId());
System.out.println("流程部署名称:" + deployment.getName());
}
流程部署id:2501
流程部署名称:员工请假流程-2
2-3、流程定义部署后操作activiti的4张表如下:
act_re_deployment 流程定义部署表,每部署一次增加一条记录。
act_re_procdef 流程定义表,部署每个新的流程定义都会在这张表中增加一条记录
act_ge_bytearray 流程资源表
act_ge_property 系统相关属性表
可以查看这几个表都有什么数据。
我们会发现每次部署流程,流程id 并不是递增1,而是递增了 2500。这是在系统属性表(act_ge_property)里控制的。
这里说一下流程定义表:act_re_procdef,这个 KEY 是用来唯一识别不同流程的关键字。实际业务是不允许重复的,也就是部署流程的时候,相同的流程只需要部署一次。我们是测试演示的。
注意:
act_re_deployment和act_re_procdef一对多关系,一次部署在流程部署表生成一条记录,但一次部署可以部署多个流程定义,每个流程定义在流程定义表生成一条记录。每一个流程定义在act_ge_bytearray会存在两个资源记录,bpmn和png。
建议:一次部署一个流程,这样部署表和流程定义表是一对一有关系,方便读取流程部署及流程定义信息。
3、启动流程实例
流程定义部署在 activiti 后就可以通过工作流管理业务流程了,也就是说上边部署的请假申请流程可以使用了。
针对该流程,启动一个流程表示发起一个新的请假申请单,这就相当于java类与java对象的关系,类定义好后需要 new 创建一个对象使用,当然可以 new 多个对象。对于请假申请流程,张三发起一个请假申请单需要启动一个流程实例,请假申请单发起一个请假单也需要启动一个流程实例。
个人理解的流程实例:是具体到某一个流程定义的一个实际例子,叫流程实例。比如公司的请假流程叫做【流程定义】,它规定请假要经过多少人审批。而张三发起的请假申请,是流程实例,是具体的某一个案例,因此叫流程实例。
启动流程测试代码:
/**
* 启动流程实例
*/
@Test
public void testStartProcess(){
//1、创建 ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2、获取 RunTimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
//3、根据流程定义ID启动流程
ProcessInstance instance = runtimeService.startProcessInstanceByKey("myApply");
System.out.println("流程定义id=" + instance.getProcessDefinitionId());
System.out.println("流程实例id=" + instance.getId());
System.out.println("当前活动Id=" + instance.getActivityId());
}
运行结果:
流程定义id=myApply:3:5004
流程实例id=7501
当前活动Id=null
说明:因为我们部署了3次流程实例,因此下一次的实例id=7501
启动流程实例操作的数据表有:
act_hi_actinst 流程实例执行历史
act_hi_identitylink 流程的参与用户历史信息
act_hi_procinst 流程实例历史信息
act_hi_taskinst 流程任务历史信息
act_ru_execution 流程执行信息
act_ru_identitylink 流程的参与用户信息
act_ru_task 任务信息
4、任务查询
流程启动后,任务的负责人就可以查询自己当前需要处理的任务,查询出来的任务都是该用户的待办任务。
测试代码:
/**
* 查询个人待执行的任务列表
*/
@Test
public void testFindPersonalTaskList(){
//1、创建 ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//创建 TaskService 实例
TaskService taskService = processEngine.getTaskService();
//根据流程key 和任务负责人id 查询任务
//注意 Task 引入的是:org.activiti.engine.task.Task
List<Task> list = taskService.createTaskQuery()
.processDefinitionKey("myApply")
.taskAssignee("zhangsan")
.list();
for(Task task : list){
System.out.println("流程实例id=" + task.getProcessInstanceId());
System.out.println("任务id=" + task.getId());
System.out.println("任务负责人=" + task.getAssignee());
System.out.println("任务名称=" + task.getName());
}
}
运行结果:
流程实例id=7501
任务id=7505
任务负责人=zhangsan
任务名称=创建请假申请
5、流程任务处理
任务负责人查询待办任务,选择任务进行处理,完成任务。
代码:
/**
* 完成任务
*/
@Test
public void testCompleteTask(){
//1、创建 ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2/创建 TaskService 实例
TaskService taskService = processEngine.getTaskService();
//根据流程key 和负责人id 查询任务
Task task = taskService.createTaskQuery()
.processDefinitionKey("myApply")//流程定义的key
.taskAssignee("zhangsan")//要查询的负责人
.singleResult();
//完成任务,需要 taskId
taskService.complete(task.getId());
}
运行结果:
6、流程定义信息查询
查询流程相关信息,包含流程定义,流程部署,流程定义版本
代码:
/**
* 查询所有流程定义
*/
@Test
public void testQueryAllProcessDefinition(){
//1、创建 ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//获取 RepositoryService 实例
RepositoryService service = processEngine.getRepositoryService();
//得到 ProcessDefinitionQuery 实例
ProcessDefinitionQuery query = service.createProcessDefinitionQuery();
//查询
List<ProcessDefinition> list = query
.processDefinitionKey("myApply")//增加流程定义的key为条件
.orderByProcessDefinitionVersion()//版本倒序排
.desc()
.list();
//输出信息
for (ProcessDefinition processDefinition : list) {
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=myApply:3:5004
流程定义 name=请假流程
流程定义 key=myApply
流程定义 Version=3
流程部署ID =5001
7、流程删除
代码:
/**
* 流程删除
*/
@Test
public void deleteDeployment(){
//1、创建 ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 通过流程引擎获取repositoryService
RepositoryService service = processEngine.getRepositoryService();
//删除流程定义,如果该流程定义已经有流程实例启动,则删除出错
service.deleteDeployment("1",false);
//设置true 级联删除流程定义,即使该流程有流程实例启动也可以删除,设置为false非级别删除方式
// service.deleteDeployment("1", true);
}
说明:
1、使用 repositoryService 删除流程定义,历史表信息不会被删除。
2、如果该流程定义下没有正在运行的流程,则可以用普通删除。
如果该流程定义下存在已经运行的流程,使用普通删除报错,可用级联删除方法将流程及相关记录全部删除:先删除没有完成流程节点,最后就可以完全删除流程定义信息。项目开发中级联删除操作一般只开放给超级管理员使用。
8、流程资源下载
现在我们的流程资源文件已经上传到数据库了,如果其他用户想要查看这些资源文件,可以从数据库中把资源文件下载到本地。
解决方案有:
1、jdbc对blob类型,clob类型数据读取出来,保存到文件目录
2、使用activiti的api来实现
使用commons-io.jar 解决IO的操作
引入commons-io依赖包
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
测试代码(需要在D盘创建 test 目录。或者指定别的目录):
/**
* 下载 Bpmn 文件
* @throws Exception
*/
@Test
public void downloadBpmnFile() throws Exception{
//1、创建 ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 通过流程引擎获取repositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
//3、得到查询器:ProcessDefinitionQuery,设置查询条件,得到想要的流程定义
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey("myApply")//根据流程定义的ke查询
.latestVersion()//只获取最新的一条数据
.singleResult();
//4、通过流程定义信息,得到部署ID
String deploymentId = processDefinition.getDeploymentId();
//5、通过repositoryService的方法,实现读取图片信息和bpmn信息
//png图片流,读到内存
InputStream pngInputStream = repositoryService.getResourceAsStream(deploymentId, processDefinition.getDiagramResourceName());
//bpmn 文件流,读到内存
InputStream bpmnInputStream = repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());
//6、构造OutputStream流
File file_png = new File("D:/test/myApply.png");
File file_bpmn = new File("D:/test/myApply.bpmn");
FileOutputStream bpmnOut = new FileOutputStream(file_bpmn);
FileOutputStream pngOut = new FileOutputStream(file_png);
//7、输入流,输出流的转换
IOUtils.copy(pngInputStream,pngOut);
IOUtils.copy(bpmnInputStream,bpmnOut);
//8、关闭流
pngOut.close();
bpmnOut.close();
pngInputStream.close();
bpmnInputStream.close();
}
说明:
1) deploymentId为流程部署ID
2) resource_name为act_ge_bytearray表中NAME_列的值
3) 使用repositoryService的getDeploymentResourceNames方法可以获取指定部署下的所有文件的名称
4) 使用repositoryService的getResourceAsStream方法传入部署ID和资源图片名称可以获取部署下指定名称文件的输入流
最后的将输入流中的图片资源进行输出。
9、流程历史信息的查看
即使流程定义已经删除了,流程执行的历史信息通过前面的分析,依然保存在activiti的act_hi_*相关的表中。所以我们还是可以查询流程执行的历史信息,可以通过HistoryService来查看相关的历史记录。
代码:
/**
* 查询历史信息
*/
@Test
public void findHistoryInfo(){
//1、创建 ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//获取 HistoryService
HistoryService historyService = processEngine.getHistoryService();
//获取 actinst 表的查询对象
HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery();
//查询 actinst表,条件:根据 InstanceId 查询
// instanceQuery.processInstanceId("2501");
//查询 actinst表,条件:根据 DefinitionId 查询
instanceQuery.processDefinitionId("myApply:3:5004");
//增加排序操作,orderByHistoricActivityInstanceStartTime 根据开始时间排序 asc 升序
instanceQuery.orderByHistoricActivityInstanceStartTime().asc();
//查询所有内容
List<HistoricActivityInstance> activityInstanceList = instanceQuery.list();
//输出数据
for (HistoricActivityInstance hi : activityInstanceList) {
System.out.println(hi.getActivityId());
System.out.println(hi.getActivityName());
System.out.println(hi.getProcessDefinitionId());
System.out.println(hi.getProcessInstanceId());
System.out.println("--------------------------------------");
}
}
结果:
_2
请假申请流程
myApply:3:5004
7501
--------------------------------------
_3
创建请假申请
myApply:3:5004
7501
--------------------------------------
_4
主管审批
myApply:3:5004
7501
--------------------------------------
注意,这个 processDefinitionId 就是如图所示的:
act_hi_actinst 表的 PROC_DEF_ID_ 字段。