[TOC]
*****
> 本节我们介绍Activiti提供的 7大Service接口。先了解这7个service的用途,有利于我们更好的学习activiti。
我们不会专门介绍这几个Service接口的所有API方法,而是通过实例的方式直接使用这些API,在实践中熟悉这些API。此处贴出官方的javadocs文档地址:[https://www.activiti.org/javadocs/](https://www.activiti.org/javadocs/)
![](https://img.kancloud.cn/a1/04/a104c6c80fb7e8623a6fa64b205780f6_1464x613.png)
## 1、流程模型
我们通过使用下面这个请假流程模型来练习这7个Activiti Service。
### 1.1 流程图
流程节点的具体内容,本章节我们不介绍如何画流程图,而是先以一个实例来熟悉一下Activiti的使用过程。
![](https://img.kancloud.cn/71/eb/71ebb109f20a3648dcb31de1a3f5240f_2288x1105.png)
简要介绍一下,以上流程图的使用场景,这是一个请假流程:
* 请假当事人发起流程后,流程流转到`部门经理审批`节点,部门经理选择同意,则流转到`人事审批`节点,否则流转到`调整申请`节点,当事人可以在`调整申请`节点修改请假信息,重新申请流程或者结束流程。
* 当部门经理同意后,流程到达`人事审批`节点,这个节点的操作和`部门经理审批`节点逻辑是一样的。
* 当人事经理审批同意后,流程流转到销假节点,当事人在这个节点处理销假信息,然后结束流程。
### 1.2 流程定义
以下是上面流程图对应的定义文件。(省略部分位置信息,初学看不懂没关系,了解工作流程就行)
```
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.kafeitu.me/activiti/leave">
<process id="leave" name="请假流程-普通表单" isExecutable="true">
<documentation>请假流程演示</documentation>
<startEvent id="startevent1" name="Start" activiti:initiator="applyUserId"></startEvent>
<userTask id="deptLeaderVerify" name="部门领导审批" activiti:candidateGroups="deptLeader"></userTask>
<exclusiveGateway id="exclusivegateway5" name="Exclusive Gateway"></exclusiveGateway>
<userTask id="modifyApply" name="调整申请" activiti:assignee="${applyUserId}"></userTask>
<userTask id="hrVerify" name="人事审批" activiti:candidateGroups="hr"></userTask>
<exclusiveGateway id="exclusivegateway6" name="Exclusive Gateway"></exclusiveGateway>
<userTask id="reportBack" name="销假" activiti:assignee="${applyUserId}">
<extensionElements>
<activiti:taskListener event="complete" delegateExpression="${reportBackEndProcessor}"></activiti:taskListener>
</extensionElements>
</userTask>
<endEvent id="endevent1" name="End"></endEvent>
<exclusiveGateway id="exclusivegateway7" name="Exclusive Gateway"></exclusiveGateway>
<sequenceFlow id="flow2" sourceRef="startevent1" targetRef="deptLeaderVerify"></sequenceFlow>
<sequenceFlow id="flow3" sourceRef="deptLeaderVerify" targetRef="exclusivegateway5"></sequenceFlow>
<sequenceFlow id="flow4" name="不同意" sourceRef="exclusivegateway5" targetRef="modifyApply">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${!deptLeaderApproved}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow5" name="同意" sourceRef="exclusivegateway5" targetRef="hrVerify">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${deptLeaderApproved}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow6" sourceRef="hrVerify" targetRef="exclusivegateway6"></sequenceFlow>
<sequenceFlow id="flow7" name="同意" sourceRef="exclusivegateway6" targetRef="reportBack">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${hrApproved}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow8" sourceRef="reportBack" targetRef="endevent1"></sequenceFlow>
<sequenceFlow id="flow9" name="不同意" sourceRef="exclusivegateway6" targetRef="modifyApply">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${!hrApproved}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow10" name="重新申请" sourceRef="exclusivegateway7" targetRef="deptLeaderVerify">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${reApply}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow11" sourceRef="modifyApply" targetRef="exclusivegateway7"></sequenceFlow>
<sequenceFlow id="flow12" name="结束流程" sourceRef="exclusivegateway7" targetRef="endevent1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${!reApply}]]></conditionExpression>
</sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_leave">
<bpmndi:BPMNPlane bpmnElement="leave" id="BPMNPlane_leave">
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
//........................
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
```
### 1.3 节点审批人员约定
| 用户名 | ACT\_ID\_USER(表) |ACT\_ID\_GROUP(表)|
| --- | --- |--- |
| 发起人(startmen) | 任意 | 任意 |
| 部门领导 | deptmen | deptLeader |
| 人事领导 | hrmen | hr |
## 2、单元测试
我们以单元测试的形式演示这个请假流程的工作过程。
### 2.1 获取流程引擎及各个Service接口
只有先获取了流程引擎,才能获取7大Service接口。
```
package com.sxdx.workflow.activiti.rest;
import org.activiti.engine.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import static org.junit.Assert.assertNotNull;
@SpringBootTest
@RunWith(SpringRunner.class)
public class ActivitiServiceTest {
private ProcessEngine processEngine;
private IdentityService identityService;
private RepositoryService repositoryService;
private RuntimeService runtimeService;
private TaskService taskService;
private HistoryService historyService;
@Test
public void before(){
ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration
.createStandaloneProcessEngineConfiguration();
processEngineConfiguration.setJdbcDriver("com.mysql.cj.jdbc.Driver");
processEngineConfiguration.setJdbcUrl("jdbc:mysql://localhost:3306/activiti-demo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true");
processEngineConfiguration.setJdbcUsername("root");
processEngineConfiguration.setJdbcPassword("xxxxxxx");
//如果表不存在,则自动创建表
processEngineConfiguration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
processEngine = processEngineConfiguration.buildProcessEngine();
System.out.println(processEngine.toString());
repositoryService = processEngine.getRepositoryService();
identityService = processEngine.getIdentityService();
runtimeService = processEngine.getRuntimeService();
taskService = processEngine.getTaskService();
historyService = processEngine.getHistoryService();
assertNotNull(processEngine);
}
}
```
测试结果:查看 activiti-demo 数据库(单元测试库),可以看到生成了28张表。
![](https://img.kancloud.cn/b0/3f/b03f9e463e106b92d5dff30bede46659_398x754.png)
### 2.2 初始化审批人
此处我们通过代码方式,向数据库中插入流程所需的用户信息。
~~~
package com.sxdx.workflow.activiti.rest;
import org.activiti.engine.*;
import org.activiti.engine.identity.User;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@SpringBootTest
@RunWith(SpringRunner.class)
public class ActivitiServiceTest {
private ProcessEngine processEngine;
private IdentityService identityService;
private RepositoryService repositoryService;
private RuntimeService runtimeService;
private TaskService taskService;
private HistoryService historyService;
/**
* 获取流程引擎及各个Service
*/
@Before
public void before(){
ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration
.createStandaloneProcessEngineConfiguration();
processEngineConfiguration.setJdbcDriver("com.mysql.cj.jdbc.Driver");
processEngineConfiguration.setJdbcUrl("jdbc:mysql://localhost:3306/activiti-demo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true");
processEngineConfiguration.setJdbcUsername("root");
processEngineConfiguration.setJdbcPassword("gaoyipeng");
//如果表不存在,则自动创建表
processEngineConfiguration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
processEngine = processEngineConfiguration.buildProcessEngine();
System.out.println(processEngine.toString());
repositoryService = processEngine.getRepositoryService();
identityService = processEngine.getIdentityService();
runtimeService = processEngine.getRuntimeService();
taskService = processEngine.getTaskService();
historyService = processEngine.getHistoryService();
assertNotNull(processEngine);
}
/**
* 初始化审批人 act_id_user: deptmen, hrmen
*/
@Test
public void initUser(){
User deptmen = identityService.newUser("deptmen");
deptmen.setFirstName("部门领导");
identityService.saveUser(deptmen);
User hrmen = identityService.newUser("hrmen");
hrmen.setFirstName("人事领导");
identityService.saveUser(hrmen);
assertEquals(2,identityService.createUserQuery().count());
}
}
~~~
执行结果:
![](https://img.kancloud.cn/ba/28/ba280fe262a36cd45ea67ba51f354acf_639x176.png)
### 2.3 初始化组
此处我们通过代码方式,向数据库中插入流程所需的用户组信息。
~~~
/**
* 初始化组 act_id_group: deptLeader, hr
* 在Activiti中组分为2种:
* assignment:普通的岗位角色,是用户分配业务中的功能权限
* security-role: 安全角色,全局管理用户组织及整个流程的状态
* 如果使用Activiti提供的Explorer,需要security-role才能看到manage页签,需要assignment才能claim任务
*/
@Test
public void initGroup(){
Group deptLeader = identityService.newGroup("deptLeader");
deptLeader.setName("deptLeader");
//扩展字段
deptLeader.setType("assignment");
identityService.saveGroup(deptLeader);
Group hr = identityService.newGroup("hr");
hr.setName("hr");
hr.setType("assignment");
identityService.saveGroup(hr);
assertEquals(2,identityService.createGroupQuery().count());
}
~~~
执行结果:
![](https://img.kancloud.cn/aa/db/aadb1d284847e673b2b1f9fbaba94b1e_616x185.png)
### 2.4 初始化人员、组的关系
```
/**
* 初始化人员、组的关系
*/
@Test
public void initMemberShip(){
identityService.createMembership("deptmen","deptLeader");
identityService.createMembership("hrmen","hr");
}
~
```
执行结果:
![](https://img.kancloud.cn/72/52/7252e7bd7bd2d9f9dd6d025ff0d254a8_868x201.png)
API 补充:
```
//删除user
identityService.deleteUser(userId);
//删除group
identityService.deleteGroup(groupId);
//删除user、group关联关系
identityService.deleteMembership( userId, groupId);
```
### 2.5 部署流程定义
流程图准备好后,需要部署流程才能进行后续操作。
将下载好的leave.bpmn放到`src/main/resources`目录下,github代码中已经包含了
![](https://img.kancloud.cn/34/5d/345d16b0f2802a68bd56b4eef40fdb77_478x481.png)
~~~
/**
* 部署流程定义
*/
@Test
public void deployTest(){
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("leave.bpmn")
.deploy();
assertNotNull(deployment);
}
~~~
执行结果:
![](https://img.kancloud.cn/d9/ba/d9ba6017bb7130dd99bb3a9ca551ca19_825x163.png)
![](https://img.kancloud.cn/f0/ed/f0ed566b210f5ef0ae54f37235dc7457_1285x179.png)
数据表中已经生成了数据,说明流程已经部署成功。
### 2.6 发起审批
```
/**
* 发起流程
*/
@Test
public void submitApplyTest(){
String applyUserId = "startmen";
//设置流程启动发起人,在流程开始之前设置,会自动在表ACT_HI_PROCINST 中的START_USER_ID_中设置用户ID
identityService.setAuthenticatedUserId(applyUserId);
runtimeService.startProcessInstanceByKey("leave");
}
```
执行结果如下(说明流程已经发起成功):
![](https://img.kancloud.cn/ac/28/ac2844eb4d8e394dd102412bf88b124f_1399x146.png)
![](https://img.kancloud.cn/3e/83/3e837f042b185827ed279e796eaa95d2_764x145.png)
### 2.7 部门领导获取待办
```
/**
* 获取待办
*/
@Test
public void getTaskTodo(){
//根据当前人id查询待办任务
List<Task> taskList = taskService.createTaskQuery()
.processDefinitionKey("leave")
.taskAssignee("deptmen")
.active().list();
//根据当前人未签收的任务
List<Task> taskList1 = taskService.createTaskQuery()
.processDefinitionKey("leave")
.taskCandidateUser("deptmen")
.active().list();
List<Task> list = new ArrayList<>();
list.addAll(taskList);
list.addAll(taskList1);
System.out.println("-------"+list.get(0).toString()+"----"+list.get(0).getName());
assertEquals(1,list.size());
Task task = list.get(0);
}
```
断点截图(显示了部门领导节点需要处理的审批记录):
![](https://img.kancloud.cn/d4/55/d455dbb493ebc029e9378ad89793fcb1_1671x1202.png)
### 2.8 部门领导审批通过流程
接下来添加审批代码
~~~
/**
* 获取待办并通过
*/
@Test
public void getTaskTodo(){
//根据当前人id查询待办任务
List<Task> taskList = taskService.createTaskQuery()
.processDefinitionKey("leave")
.taskAssignee("deptmen")
.active().list();
//根据当前人未签收的任务
List<Task> taskList1 = taskService.createTaskQuery()
.processDefinitionKey("leave")
.taskCandidateUser("deptmen")
.active().list();
List<Task> list = new ArrayList<>();
list.addAll(taskList);
list.addAll(taskList1);
System.out.println("-------"+list.get(0).toString()+"----"+list.get(0).getName());
assertEquals(1,list.size());
Task task = list.get(0);
//审批流程
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processDefinitionKey("leave")
.singleResult();
// 添加批注
identityService.setAuthenticatedUserId("deptmen");
taskService.addComment(task.getId(), processInstance.getId(), "deptmen【同意】了");
Map<String, Object> variables = new HashMap<>();
variables.put("deptLeaderApproved", true);
// 只有签收任务,act_hi_taskinst 表的 assignee 字段才不为 null
taskService.claim(task.getId(), "deptmen");
taskService.complete(task.getId(), variables);
}
~~~
执行结果:可以看到审批意见了,部门领导已经审批通过了。
![](https://img.kancloud.cn/22/47/224708d09dda7898c8a7233eb3dd2f6f_1300x190.png)
### 2.9 获取已办
~~~
/**
* 获取已完成的流程
*/
@Test
public void getCompileTask(){
List<HistoricTaskInstance> taskInstanceList = historyService.createHistoricTaskInstanceQuery()
.processDefinitionKey("leave")
.taskAssignee("deptmen")
.finished().list();
for (HistoricTaskInstance instance :taskInstanceList) {
System.out.println(instance.getProcessInstanceId()+"--"+instance.getName()+"--"+instance.getAssignee());
}
}
~~~
执行结果:可以看到 5001 这条流程,部门经理已经审批过了。
![](https://img.kancloud.cn/10/9c/109cf7953a3f3b0a0105b6d0f1266da8_982x506.png)
### 2.10 获取审批意见
~~~
/**
* 获取审批意见
*/
@Test
public void getHistoryComment(){
//获取流程实例对象
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processDefinitionKey("leave").singleResult();
//获取历史活动集合
List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstance.getId())
.activityType("userTask")
.finished()
.list();
for (HistoricActivityInstance historicActivityInstance:historicActivityInstanceList ) {
List<Comment> commentList = taskService.getTaskComments(historicActivityInstance.getTaskId(), "comment");
for (int i = 0; i < commentList.size(); i++) {
System.out.println(commentList.get(i).getProcessInstanceId()+"---"+ commentList.get(i).getUserId() +"批复内容:" + commentList.get(i).getFullMessage());
}
}
}
~~~
![](https://img.kancloud.cn/9d/72/9d72d8d52498b73f59f01f6127088cde_871x433.png)
**请记住这个5001,获取流程图时会用到。**
## 3、获取流程图
这个是比较固定的代码,不做详解介绍。只做演示。添加代码如下
![](https://img.kancloud.cn/80/d7/80d712ee60e55e654186df8ddc1663b7_575x561.png)
访问API:[http://localhost:8080/process/read-resource/5001](http://localhost:8080/process/read-resource/5001),pProcessInstanceId是流程实例:5001(**act\_hi\_procinst**表ID\_)
~~~
@RequestMapping(value = "/read-resource/{pProcessInstanceId}")
public void readResource(@PathVariable("pProcessInstanceId")String pProcessInstanceId, HttpServletResponse response)
throws Exception {
// 设置页面不缓存
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
String processDefinitionId = "";
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(pProcessInstanceId).singleResult();
if(processInstance == null) {
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(pProcessInstanceId).singleResult();
processDefinitionId = historicProcessInstance.getProcessDefinitionId();
} else {
processDefinitionId = processInstance.getProcessDefinitionId();
}
ProcessDefinitionQuery pdq = repositoryService.createProcessDefinitionQuery();
ProcessDefinition pd = pdq.processDefinitionId(processDefinitionId).singleResult();
String resourceName = pd.getDiagramResourceName();
if(resourceName.endsWith(".png") && StringUtils.isEmpty(pProcessInstanceId) == false)
{
getActivitiProccessImage(pProcessInstanceId,response);
//ProcessDiagramGenerator.generateDiagram(pde, "png", getRuntimeService().getActiveActivityIds(processInstanceId));
}
else
{
// 通过接口读取
InputStream resourceAsStream = repositoryService.getResourceAsStream(pd.getDeploymentId(), resourceName);
// 输出资源内容到相应对象
byte[] b = new byte[1024];
int len = -1;
while ((len = resourceAsStream.read(b, 0, 1024)) != -1) {
response.getOutputStream().write(b, 0, len);
}
}
}
~~~
~~~
/**
* 获取流程图像,已执行节点和流程线高亮显示
*/
public void getActivitiProccessImage(String pProcessInstanceId, HttpServletResponse response) {
//logger.info("[开始]-获取流程图图像");
try {
// 获取历史流程实例
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(pProcessInstanceId).singleResult();
if (historicProcessInstance == null) {
//throw new BusinessException("获取流程实例ID[" + pProcessInstanceId + "]对应的历史流程实例失败!");
}
else {
// 获取流程定义
ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
.getDeployedProcessDefinition(historicProcessInstance.getProcessDefinitionId());
// 获取流程历史中已执行节点,并按照节点在流程中执行先后顺序排序
List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(pProcessInstanceId).orderByHistoricActivityInstanceId().asc().list();
// 已执行的节点ID集合
List<String> executedActivityIdList = new ArrayList<String>();
int index = 1;
//logger.info("获取已经执行的节点ID");
for (HistoricActivityInstance activityInstance : historicActivityInstanceList) {
executedActivityIdList.add(activityInstance.getActivityId());
//logger.info("第[" + index + "]个已执行节点=" + activityInstance.getActivityId() + " : " +activityInstance.getActivityName());
index++;
}
BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());
// 已执行的线集合
List<String> flowIds = new ArrayList<String>();
// 获取流程走过的线 (getHighLightedFlows是下面的方法)
flowIds = getHighLightedFlows(bpmnModel,processDefinition, historicActivityInstanceList);
// // 获取流程图图像字符流
// ProcessDiagramGenerator pec = processEngine.getProcessEngineConfiguration().getProcessDiagramGenerator();
// //配置字体
// InputStream imageStream = pec.generateDiagram(bpmnModel, "png", executedActivityIdList, flowIds,"宋体","微软雅黑","黑体",null,2.0);
Set<String> currIds = runtimeService.createExecutionQuery().processInstanceId(pProcessInstanceId).list()
.stream().map(e->e.getActivityId()).collect(Collectors.toSet());
ICustomProcessDiagramGenerator diagramGenerator = (ICustomProcessDiagramGenerator) processEngine.getProcessEngineConfiguration().getProcessDiagramGenerator();
InputStream imageStream = diagramGenerator.generateDiagram(bpmnModel, "png", executedActivityIdList,
flowIds, "宋体", "宋体", "宋体", null, 1.0, new Color[] { WorkflowConstants.COLOR_NORMAL, WorkflowConstants.COLOR_CURRENT }, currIds);
response.setContentType("image/png");
OutputStream os = response.getOutputStream();
int bytesRead = 0;
byte[] buffer = new byte[8192];
while ((bytesRead = imageStream.read(buffer, 0, 8192)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.close();
imageStream.close();
}
//logger.info("[完成]-获取流程图图像");
} catch (Exception e) {
System.out.println(e.getMessage());
//logger.error("【异常】-获取流程图失败!" + e.getMessage());
//throw new BusinessException("获取流程图失败!" + e.getMessage());
}
}
~~~
~~~
private List<String> getHighLightedFlows(BpmnModel bpmnModel, ProcessDefinitionEntity processDefinitionEntity, List<HistoricActivityInstance> historicActivityInstances) {
// 高亮流程已发生流转的线id集合
List<String> highLightedFlowIds = new ArrayList<>();
// 全部活动节点
List<FlowNode> historicActivityNodes = new ArrayList<>();
// 已完成的历史活动节点
List<HistoricActivityInstance> finishedActivityInstances = new ArrayList<>();
for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true);
historicActivityNodes.add(flowNode);
if (historicActivityInstance.getEndTime() != null) {
finishedActivityInstances.add(historicActivityInstance);
}
}
FlowNode currentFlowNode = null;
FlowNode targetFlowNode = null;
// 遍历已完成的活动实例,从每个实例的outgoingFlows中找到已执行的
for (HistoricActivityInstance currentActivityInstance : finishedActivityInstances) {
// 获得当前活动对应的节点信息及outgoingFlows信息
currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true);
List<SequenceFlow> sequenceFlows = currentFlowNode.getOutgoingFlows();
/**
* 遍历outgoingFlows并找到已已流转的 满足如下条件认为已已流转: 1.当前节点是并行网关或兼容网关,则通过outgoingFlows能够在历史活动中找到的全部节点均为已流转 2.当前节点是以上两种类型之外的,通过outgoingFlows查找到的时间最早的流转节点视为有效流转
*/
if ("parallelGateway".equals(currentActivityInstance.getActivityType()) || "inclusiveGateway".equals(currentActivityInstance.getActivityType())) {
// 遍历历史活动节点,找到匹配流程目标节点的
for (SequenceFlow sequenceFlow : sequenceFlows) {
targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(sequenceFlow.getTargetRef(), true);
if (historicActivityNodes.contains(targetFlowNode)) {
highLightedFlowIds.add(targetFlowNode.getId());
}
}
} else {
List<Map<String, Object>> tempMapList = new ArrayList<>();
for (SequenceFlow sequenceFlow : sequenceFlows) {
for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
Map<String, Object> map = new HashMap<>();
map.put("highLightedFlowId", sequenceFlow.getId());
map.put("highLightedFlowStartTime", historicActivityInstance.getStartTime().getTime());
tempMapList.add(map);
}
}
}
if (!CollectionUtils.isEmpty(tempMapList)) {
// 遍历匹配的集合,取得开始时间最早的一个
long earliestStamp = 0L;
String highLightedFlowId = null;
for (Map<String, Object> map : tempMapList) {
long highLightedFlowStartTime = Long.valueOf(map.get("highLightedFlowStartTime").toString());
if (earliestStamp == 0 || earliestStamp >= highLightedFlowStartTime) {
highLightedFlowId = map.get("highLightedFlowId").toString();
earliestStamp = highLightedFlowStartTime;
}
}
highLightedFlowIds.add(highLightedFlowId);
}
}
}
return highLightedFlowIds;
}
~~~
获取的流程图如下:
![](https://img.kancloud.cn/be/15/be1555ff016ca2293dea4a38965be9b3_1911x1006.png)
本节到此结束。
- 使用教程
- 1、环境说明
- 2、导入教程
- 3、系统展示
- 4、更新历史
- 搭建教程
- 第一章:Activiti模块
- 1、基本概念
- 2、资料下载
- 3、环境搭建
- 4、集成Activiti-Modeler流程设计器
- 5、七大Service接口
- 6、流程定义文件—流程定义—流程模型 的相互转化
- 7、用户和用户组
- 8、任务表单
- 8.1、表单分类
- 8.2 、动态表单实战、集成Swagger、Logback
- 8.3、外置表单实战
- 8.4、普通表单实战,集成Thymeleaf,Mybatis-Plus
- 8.5、表单模式选型
- 9、多实例(会签)
- 10、子流程和调用活动
- 10.1、子流程
- 10.2、事件子流程
- 10.3、调用活动
- 10.4、事务子流程
- 11、流程历史管理、补充获取流程定义列表接口
- 12、Activiti事件
- 12.1、 事件类别
- 12.2、 Activiti启动事件
- 12.3、Activiti结束事件
- 12.4、边界事件(一)
- 12.5、边界事件(二)
- 12.6、中间事件
- 13、网关
- 14、Activiti审批意见管理
- 15、Activiti流程驳回、流程回退
- 16、Activiti任务委托
- 17、Activiti流程的挂起、激活
- 第二章:基础架构完善
- 1、Spring-Security-OAuth2简介
- 2、搭建认证服务器
- 3、搭建资源服务器
- 4、Activiti自带的Rest接口
- 5、添加JWT支持
- 6、数据库存储授权码Code,Client信息
- 第三章、集成RBAC权限管理
- 1、RBAC-基于角色的访问控制
- 2、替换Activiti用户和用户组
- 3、Spring-Security获取当前操作人信息
- 4、OauthUserDetailService改造
- 第四章、使用Swagger生成静态接口文档