Activiti7流程操作详解
一、Activiti流程操作步骤
-
定义流程,按照
BPMN
的规范,使用流程定义工具,用流程符号把整个流程描述出来 -
部署流程,把画好的流程定义文件,加载到数据库中,生成表的数据
-
启动流程,使用
java
代码来操作数据库表中的内容 -
操作流程当中的各个任务
通过以上三步、就可以创建一个Activiti
工作流、并且启动该流程。
二、常见流程符号
2.1 BPMN 2.0
BPMN
(Business Process Model AndNotation
)- 业务流程模型和符号 是由BPMI
(BusinessProcess Management Initiative
)开发的一套标准的业务流程建模符号,使用BPMN
提供的符号可以创建业务流程。
BPMN
是目前被各 BPM
厂商广泛接受的 BPM
标准。
Activiti
就是使用 BPMN 2.0
进行流程建模、流程执行管理等。
BPMN的五类元素:
类别 | 对象 |
---|---|
流对象 (Flow Objects ) | 事件( Events)、活动 (Activities )、网关 ( Gateways ) |
数据 (Data) | 数据对象 ( Data Objects )、数据输入 ( Data Inputs )、数据输出( Data Outputs )、数据存储 ( Data Stores ) |
连接对象 (Connecting Objects ) | 顺序流 ( Sequence Flows )、消息流 ( Message Flows )、关联 ( Associations )、数据关联( Data Associations ) |
泳道(Swimlanes) | 池子( Pools)、泳道 ( Lanes) |
工件 ( Artifacts ) | 组 (Group )、文字注释 |
2.2 常用流程符号
1)事件 Event
- 开始事件
- 中间事件
- 结束事件
2)活动 Activity
活动是工作或任务的一个通用术语。一个活动可以是一个任务,还可以是一个当前流程的子处理流程; 其次,你还可以为活动指定不同的类型。常见活动如下:
3)网关 GateWay
网关用来处理决策,有几种常用网关需要了解:
-
排他网关 (x)
只有一条路径会被选择。流程执行到该网关时,按照输出流的顺序逐个计算,当条件的计算结果为true时,继续执行当前网关的输出流;
如果多条线路计算结果都是
true
,则会执行第一个值为true
的线路。如果所有网关计算结果没有
true
,则引擎会抛出异常。排他网关需要和条件顺序流结合使用,
default
属性指定默认顺序流,当所有的条件不满足时会执行默认顺序流。 -
并行网关 (+)
所有路径会被同时选择
拆分 —— 并行执行所有输出顺序流,为每一条顺序流创建一个并行执行线路。
合并 —— 所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才继续向下执行。
-
包容网关 (+)
包含网关可以看做是排他网关和并行网关的结合体
可以同时执行多条线路,也可以在网关上设置条件拆分 —— 计算每条线路上的表达式,当表达式计算结果为true时,创建一个并行线路并继续执行
合并 —— 所有并行分支到达包含网关,会进入等待状态, 直到每个包含流程token的进入顺序流的分支都到达。 这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的路线(其实就是满足判断条件的顺序流)进入顺序流。 在汇聚之后,流程会穿过包含网关继续执行。
-
事件网关 (+)
专门为中间捕获事件设置的,允许设置多个输出流指向多个不同的中间捕获事件。
当流程执行到事件网关后,流程处于等待状态,需要等待抛出事件才能将等待状态转换为活动状态。
4)流向 Flow
流是连接两个流程节点的连线。常见的流向包含以下几种:
三、安装Activiti BPMN visualizer插件
这个在idea2020
版本开始,老的插件actiBPM
已经在插件市场中搜索不到了。
虽然依旧可以在插件官网下载actiBPM
到本地进行安装,但是没必要,新的插件更好使用一些。
四、流程设计器使用
4.1 新建BPMN 2.0文件
新建以后会得到一个XML
文件,并没有出现画BPMN
流程图的界面。
4.2 调出流程设计页面
4.3 绘制出差流程
1)绘制开始事件
右击流程设计页面,选择Start events
。
选择后注意下面三个区域即可:
2)绘制用户任务流程
- 创建申请单
绘制出用户任务之后,可以指定流程名称为创建出差申请,并且指定分配到任务的人为小王。
- 部门经理审批
- 总经理审批
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XblAWfMu-1683250543424)(assets\流程-部门经理审批.png)]
- 财务审批
3)绘制结束事件
4)绘制流向
1.选中流程节点
2.鼠标左键按住右上角箭头进行拖动
拖向需要指向的节点即可。
3.最终流程图效果
五、流程操作
5.1 流程定义
流程定义是线下按照bpmn2.0
标准去描述 业务流程,通常使用idea
中的插件对业务流程进行建模。
使用idea
下的designer
设计器绘制流程,需要成两个文件:.bpmn
和.png
1)bpmn文件
已经按第三步的操作定义完成,在就是first.bpmn.xml
文件。
2)png文件
直接在流程设计器中,右击后再菜单栏点击Save to PNG
,然后选择文件位置,放到.bpmn
文件的相同目录下即可。
5.2 流程部署
1)单文件部署方式
将上面在设计器中定义的流程部署到activiti
数据库中,就是流程定义部署。
直观的说就是把bpmn
文件定义的流程和png
文件存储到数据库当中。
这里使用一个单元测试来进行演示:
/**
* 部署流程定义测试
*/
@Test
public void DeployTest() {
// 1. 创建ProcessEngine流程引擎对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2、得到RepositoryService实例
RepositoryService repositoryService = processEngine.getRepositoryService();
// 3、使用RepositoryService进行部署,定义一个流程名字,把bpmn和png部署到数据库中
Deployment deployment = repositoryService.createDeployment() // 创建一个部署流程
.name("出差申请流程") // 给部署流程命名
.addClasspathResource("a-bpmn/first.bpmn20.xml") // 添加类路径下的资源文件
.addClasspathResource("a-bpmn/first.png")
.deploy(); // 执行流程部署
// 4、输出部署信息
System.out.println("流程部署id:" + deployment.getId());
System.out.println("流程部署名称:" + deployment.getName());
}
运行之后,activiti
主要操作了4
张表:
-
act_re_deployment
:流程部署表,每部署一个流程就会多一条数据
-
act_re_procdef
:流程定义表
这里面比较重要的字段:
KTY_
:也就是之前流程定义时的ID
属性RESOURCE_NAME_
:bpmn
文件的资源路径DGRM_RESOURCE_NAME_
:png
文件的资源路径
-
act_ge_bytearray
:通用的流程资源表
-
查看图像的方法:用
Navicat
的话选择开始事务旁边的下拉框为图像,再选中想看的png
文件,下方就能显示了
-
查看
bpmn
文件的方法
其实这就是一个
xml
文件即可。 -
-
act_ge_property
:系统相关属性
完成上述操作后Activiti
会将上边代码中指定的bpmn
文件和图片文件保存在Activiti
数据库中的相关表中。
小结:
act_re_deployment
和act_re_procdef
一对多关系.
一次部署在流程部署表生成一条记录,但一次部署可以部署多个流程定义,每个流程定义在流程定义表生成一条记录。
每一个流程定义在act_ge_bytearray
会存在两个资源记录,bpmn
和png
。
建议:
一次部署一个流程,这样部署表和流程定义表是一对一有关系,方便读取流程部署及流程定义信息。
2)压缩包部署方式
这种方式需要把bpmn
文件和png
文件打成一个.zip
的压缩包。
/**
* 部署流程--压缩包部署方式
*/
@Test
public void deployByZipTest() {
// 1. 创建ProcessEngine流程引擎对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2.定义字节输入流
InputStream inputStream = this.getClass()
.getClassLoader()
.getResourceAsStream("bpmn/evection.zip");
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
// 3.获取repositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
// 4.流程部署
Deployment deployment = repositoryService.createDeployment().addZipInputStream(zipInputStream).deploy();
// 5、输出部署信息
System.out.println("流程部署id:" + deployment.getId());
System.out.println("流程部署名称:" + deployment.getName());
}
当有很多流程的时候,很多时候都是使用这种压缩包的形式来进行流程的部署
前端直接把这个压缩包上传上来,后端拿到后,进行流程部署即可。
5.3 启动流程实例
/**
* 启动流程实例
*/
@Test
public void startProcessTest(){
// 1、创建ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2、获取RunTimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 3、根据流程定义Id启动流程,并获取流程实例
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myEvection");
// 输出内容
System.out.println("流程定义id:" + processInstance.getProcessDefinitionId());
System.out.println("流程实例id:" + processInstance.getId());
System.out.println("当前活动Id:" + processInstance.getActivityId());
}
所操作数据表如下:
-
act_hi_actinst
:流程实例执行历史
-
act_hi_identitylink
:流程的参与用户历史信息
-
act_hi_procinst
:流程实例历史信息
-
act_hi_taskinst
: 流程任务历史信息
-
act_ru_execution
:流程执行信息
-
act_ru_identitylink
:流程的参与用户信息,记录当前用户需要做的流程实例的关联关系
-
act_ru_task
:任务信息
5.4 流程任务查询
一个流程启动后,任务的负责人就可以查询自己当前需要处理的任务,查询出来的任务都是该用户的待办任务。
/**
* 查询当前个人待执行的任务
*/
@Test
public void findTaskListTest() {
// 1.创建流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2.获取TaskService
TaskService taskService = processEngine.getTaskService();
// 3.根据流程key 和 任务负责人 查询任务
String assignee = "小王";
List<Task> list = taskService.createTaskQuery()
.processDefinitionKey("myEvection") //流程Key
.taskAssignee(assignee) //只查询当前任务负责人的任务
.list();
list.forEach(task -> {
System.out.printf("-----------------------------------------");
System.out.println("流程实例id:" + task.getProcessInstanceId());
System.out.println("任务id:" + task.getId());
System.out.println("任务负责人:" + task.getAssignee());
System.out.println("任务名称:" + task.getName());
});
}
执行上述代码后,就可以查询出来小王的所有待办任务了:
5.5 流程任务处理
任务负责人查询属于自己的待办任务,选择相应的任务进行处理,并且去完成自己的代办任务。
/**
* 完成个人代办任务
*/
@Test
public void completTaskTest(){
// 1.创建流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2. 获取TaskService实例
TaskService taskService = processEngine.getTaskService();
// 3.根据流程key 和 任务的负责人 查询任务
String assignee = "小王";
Task task = taskService.createTaskQuery()
.processDefinitionKey("myEvection") //流程Key
.taskAssignee(assignee) //只查询当前任务负责人的任务
.singleResult();
// 4.完成待办任务,参数:任务id
taskService.complete(task.getId());
}
执行上述代码之前,act_ru_task
表中的内容还是第一步创建出差申请单:
执行上述代码之前,act_ru_task
表中的内容已经替换为了当前流程:
而之前的流程就放到了act_hi_taskinst
历史的任务实例当中去了:
5.6 流程定义信息查询
查询流程相关信息也是经常要用到的操作,通常包含对流程定义,流程部署,流程定义版本的查询。
/**
* 查询流程定义
*/
@Test
public void findProcessDefinitionTest(){
// 1.创建流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2.获取repositoryService资源管理实例
RepositoryService repositoryService = processEngine.getRepositoryService();
// 3.通过repositoryService获取ProcessDefinitionQuery对象
ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
// 4.查询出当前所有的流程定义
List<ProcessDefinition> definitionList = processDefinitionQuery.processDefinitionKey("myEvection") // 根据流程key(id)进行查询
.orderByProcessDefinitionVersion() // 按照版本排序
.desc() // 倒序
.list();
// 5.输出流程定义信息
definitionList.forEach(processDefinition -> {
System.out.printf("----------------------------------------");
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());
});
}
5.7 流程删除
如果某个流程不想要了,可以选择删除该流程。
/**
* 流程删除(它影响的表和流程定义时的表)
*/
@Test
public void deleteDeploymentTest() {
// 1.创建流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2.通过流程引擎获取repositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
// 3.删除流程定义,如果该流程定义已有流程实例启动,那么删除时就会报错
String deploymentId = "1"; // 流程部署id
repositoryService.deleteDeployment(deploymentId);
// 4.设置true 级联删除流程定义,即使该流程有流程实例启动也可以删除
// 设置为false非级别删除方式,如果该流程定义已有流程实例启动,那么删除时就会报错
//repositoryService.deleteDeployment(deploymentId, true);
}
注意:
-
使用
repositoryService
删除流程定义,历史表信息不会被删除 -
如果该流程定义下没有正在运行的流程,则可以用普通删除
-
项目开发中级联删除操作一般只开放给超级管理员使用.
5.8 流程资源下载
虽然jdbc
也可以把blob
类型的数据读取出来,但是这里主要还是用Activiti
提供的API
来进行操作。
1)引入commons-io的Maven依赖
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
2)代码示例
/**
* 下载资源文件
* @throws IOException
*/
@Test
public void findBpmnFileTest() throws IOException {
// 1.创建流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2.通过流程引擎获取repositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
// 3、得到流程定义查询器:ProcessDefinitionQuery,设置查询条件,得到想要的流程定义
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey("myEvection")
.singleResult();
// 4、通过流程定义信息,得到部署ID
String deploymentId = processDefinition.getDeploymentId();
// 5、通过repositoryService的方法,实现读取图片信息和bpmn信息
// 5.1 获取png图片的流
InputStream pngInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getDiagramResourceName());
// 5.2 获取bpmn文件的流
InputStream bpmnInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());
// 6、构造OutputStream流
File file_png = new File("d:/first.png");
File file_bpmn = new File("d:/first.bpmn");
FileOutputStream bpmnOut = new FileOutputStream(file_bpmn);
FileOutputStream pngOut = new FileOutputStream(file_png);
// 7、输入流,输出流的转换
IOUtils.copy(pngInput, pngOut);
IOUtils.copy(bpmnInput, bpmnOut);
// 8、关闭相关流
pngOut.close();
bpmnOut.close();
pngInput.close();
bpmnInput.close();
}
这只是在本地是这么操作的,是比较原生的写法,更有利于理解。
如果是在web项目中,就直接按文件下载的方式传输数据就可以了。
5.9 流程历史信息的查看
/**
* 查看历史信息
*/
@Test
public void findHistoryInfoTest(){
// 1.创建流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2.获取HistoryService
HistoryService historyService = processEngine.getHistoryService();
// 3.获取 actinst表的查询对象
HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery();
// 查询 actinst表,条件:根据 InstanceId 查询(等价于: instanceQuery.processInstanceId("2501"); 根据 InstanceId 查询)
instanceQuery.processDefinitionId("myEvection:1:4");
// 增加排序操作,orderByHistoricActivityInstanceStartTime 根据开始时间排序 asc 升序
instanceQuery.orderByHistoricActivityInstanceStartTime().asc();
// 查询所有内容
List<HistoricActivityInstance> activityInstanceList = instanceQuery.list();
// 输出查询结果
activityInstanceList.forEach(hi -> {
System.out.println("<==========================>");
System.out.println(hi.getActivityId());
System.out.println(hi.getActivityName());
System.out.println(hi.getProcessDefinitionId());
System.out.println(hi.getProcessInstanceId());
});
}
5.10 流程实例的挂起
在一些情况下可能由于流程变更需要将当前运行的流程暂停(挂起)而不是直接删除,流程暂停后将不会继续执行。
比如每月的最后一天不处理出差申请呀,财务审批流程改变需要暂停已经发起的审批等等。
流程实例的挂起也有下面两种方式:
1)将单个流程实例的挂起和激活
/**
* 挂起、激活单个流程实例
*/
@Test
public void suspendSingleProcessInstance(){
// 1. 创建ProcessEngine流程引擎对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2、获取RuntimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 3、通过RuntimeService获取流程实例对象
ProcessInstance instance = runtimeService.createProcessInstanceQuery()
.processInstanceId("27501")
.singleResult();
// 4、得到当前流程实例的暂停状态,true-已暂停 false -激活
boolean suspended = instance.isSuspended();
// 5、获取流程实例id
String instanceId = instance.getId();
// 6、判断是否已经暂停,如果已经暂停,就执行激活操作
if(suspended){
// 如果已经暂停,就执行激活
runtimeService.activateProcessInstanceById(instanceId);
System.out.println("流程实例id:"+instanceId+"已经激活");
}else {
// 7、如果是激活状态,就执行暂停操作
runtimeService.suspendProcessInstanceById(instanceId);
System.out.println("流程实例id:"+instanceId+"已经暂停");
}
}
2)将全部流程实例的挂起和激活
/**
* 全部流程实例的挂起和激活
* suspend 暂停
*/
@Test
public void suspendAllProcessInstance(){
// 1. 创建ProcessEngine流程引擎对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2、获取Repositoryservice
RepositoryService repositoryService = processEngine.getRepositoryService();
// 3、根据流程定义Key查询流程定义,获取流程定义的查询对象
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey("myEvection")
.singleResult();
// 4、获取当前流程定义的实例是否都是挂起状态
boolean suspended = processDefinition.isSuspended();
// 5、获取流程定义的id
String definitionId = processDefinition.getId();
// 6、如果是挂起状态,改为激活状态
if(suspended){
// 如果是挂起,可以执行激活的操作,参数1:流程定义id 参数2:是否激活,参数3:激活时间
repositoryService.activateProcessDefinitionById(definitionId, true, null);
System.out.println("流程定义id:"+definitionId+",已经被激活");
}else {
// 7、如果是激活状态,改为挂起状态,参数1:流程定义id 参数2:是否暂停 参数3 :暂停的时间
repositoryService.suspendProcessDefinitionById(definitionId, true, null);
System.out.println("流程定义id:"+definitionId+",已经被挂起");
}
}
这两者的区别也就是一个是查询实例,拎一个查询的是流程定义罢了
六、小结
上面已经演示了常见的BPMN
操作和流程操作,更偏向于新手理解相关API
。
但是在绘制流程图的时候,暂时都是把责任人等信息写死了。这显然在实际的业务中是难以适应业务的变化的。
并且流程审批的时候,下一个审批人根本不知道审批的具体内容是啥。
比如上面的出差申请,具体要出差去哪,出差几天,都是不知道的。
因为这些数据在业务系统当中,而不是在Activiti
中。
只有两者联动起来才能完成实际的业务开发需求,这个是需要注意的。
下一篇博客会讲诉Activiti
的中流程实例、用户任务、流程变量以及网关的使用,更加贴合实际开发。
更多推荐
所有评论(0)