[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) 本节到此结束。