From 3c3f46ee4eb93bcccab67846767429d00712d9a3 Mon Sep 17 00:00:00 2001 From: "yunlong.li" Date: Fri, 5 Nov 2021 17:13:55 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix:[=E5=B7=A5=E4=BD=9C=E6=B5=81]=20?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E5=AF=B9=E5=BA=94=E5=AE=9E=E4=BE=8B=E7=9A=84?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/workflow/TaskController.java | 12 ++ .../service/workflow/TaskService.java | 7 + .../workflow/impl/TaskServiceImpl.java | 155 +++++++++++++++++- .../pom.xml | 5 + .../config/YudaoActivitiConfiguration.java | 8 +- .../main/resources/META-INF/spring.factories | 2 + 6 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 yudao-framework/yudao-spring-boot-starter-activiti/src/main/resources/META-INF/spring.factories diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/controller/workflow/TaskController.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/controller/workflow/TaskController.java index 62ef50414..3c0837309 100644 --- a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/controller/workflow/TaskController.java +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/controller/workflow/TaskController.java @@ -9,6 +9,7 @@ import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import java.util.List; @@ -57,4 +58,15 @@ public class TaskController { return success(taskService.getHistorySteps(processInstanceId)); } + /** + * 返回高亮的流转进程 + * @param processInstanceId + */ + @GetMapping("/process/highlight-img/{id}") + public void getHighlightImg(@PathVariable("id") String processInstanceId, HttpServletResponse response) { + taskService.getHighlightImg(processInstanceId, response); + } + + + } diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/service/workflow/TaskService.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/service/workflow/TaskService.java index 7fefd0ee2..321e48d4b 100644 --- a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/service/workflow/TaskService.java +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/service/workflow/TaskService.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.adminserver.modules.activiti.service.workflow; import cn.iocoder.yudao.adminserver.modules.activiti.controller.workflow.vo.*; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import javax.servlet.http.HttpServletResponse; import java.util.List; // TODO @芋艿:前缀,注释 @@ -23,4 +24,10 @@ public interface TaskService { TodoTaskRespVO getTaskFormKey(TaskQueryReqVO taskQuery); + + /** + * 返回高亮的流转进程 + * @param processInstanceId + */ + void getHighlightImg(String processInstanceId, HttpServletResponse response); } diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/service/workflow/impl/TaskServiceImpl.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/service/workflow/impl/TaskServiceImpl.java index 0907ae467..f5ecf0f0b 100644 --- a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/service/workflow/impl/TaskServiceImpl.java +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/service/workflow/impl/TaskServiceImpl.java @@ -5,27 +5,43 @@ import cn.iocoder.yudao.adminserver.modules.activiti.controller.workflow.vo.*; import cn.iocoder.yudao.adminserver.modules.activiti.convert.workflow.TaskConvert; import cn.iocoder.yudao.adminserver.modules.activiti.service.workflow.TaskService; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; +import com.google.common.collect.ImmutableMap; +import lombok.extern.slf4j.Slf4j; import org.activiti.api.runtime.shared.query.Page; import org.activiti.api.runtime.shared.query.Pageable; import org.activiti.api.task.model.Task; import org.activiti.api.task.model.builders.ClaimTaskPayloadBuilder; import org.activiti.api.task.model.builders.TaskPayloadBuilder; import org.activiti.api.task.runtime.TaskRuntime; +import org.activiti.bpmn.model.BpmnModel; +import org.activiti.bpmn.model.FlowNode; +import org.activiti.bpmn.model.SequenceFlow; import org.activiti.engine.HistoryService; import org.activiti.engine.RepositoryService; +import org.activiti.engine.RuntimeService; import org.activiti.engine.history.HistoricActivityInstance; +import org.activiti.engine.history.HistoricProcessInstance; import org.activiti.engine.repository.ProcessDefinition; +import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.task.Comment; +import org.activiti.image.ProcessDiagramGenerator; +import org.apache.commons.lang3.exception.ExceptionUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + import javax.annotation.Resource; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.net.URLEncoder; +import java.util.*; import java.util.stream.Collectors; +@Slf4j @Service public class TaskServiceImpl implements TaskService { @@ -41,6 +57,12 @@ public class TaskServiceImpl implements TaskService { @Resource private RepositoryService repositoryService; + @Resource + private RuntimeService runtimeService; + + @Resource + private ProcessDiagramGenerator processDiagramGenerator; + @Override public PageResult getTodoTaskPage(TodoTaskPageReqVO pageReqVO) { // TODO @jason:封装一个方法,用于转换成 activiti 的分页对象 @@ -201,4 +223,131 @@ public class TaskServiceImpl implements TaskService { // return highLightedFlows; // } + + @Override + public void getHighlightImg(String processDefinitionId, HttpServletResponse response) { + // 查询历史 + HistoricProcessInstance hpi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processDefinitionId).singleResult(); + // 如果有结束时间 + if (hpi == null) { + return; + } + // 没有结束时间。说明流程在执行过程中 + ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processDefinitionId).singleResult(); + BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId()); + List highLightedActivities = new ArrayList<>(); + + List historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processDefinitionId) + .orderByHistoricActivityInstanceId().asc().list(); + // 获取所有活动节点 + List finishedInstances = historyService.createHistoricActivityInstanceQuery() + .processInstanceId(processDefinitionId).finished().list(); + for (HistoricActivityInstance hai : finishedInstances) { + highLightedActivities.add(hai.getActivityId()); + } + // 已完成的节点+当前节点 + highLightedActivities.addAll(runtimeService.getActiveActivityIds(processDefinitionId)); + + // 经过的流 + List highLightedFlowIds = getHighLightedFlows(bpmnModel, historicActivityInstances); + + //设置"宋体" + try (InputStream inputStream = processDiagramGenerator.generateDiagram(bpmnModel, highLightedActivities, highLightedFlowIds, + "宋体", "宋体", "宋体")){ + String picName = hpi.getProcessDefinitionName()+".svg"; + // 输出到浏览器 + responseImage(response, inputStream, picName); + } catch (IOException e) { + log.error(ExceptionUtils.getStackTrace(e)); + } + + } + + private void responseImage(HttpServletResponse response, InputStream inputStream, String picName) throws IOException { + response.setContentType("application/octet-stream;charset=UTF-8"); + response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(picName, "UTF-8")); + byte[] b = new byte[1024]; + int len = -1; + while ((len = inputStream.read(b, 0, 1024)) != -1) { + response.getOutputStream().write(b, 0, len); + } + response.flushBuffer(); + } + /** + * 获取已经流转的线 + * @see https://blog.csdn.net/qiuxinfa123/article/details/119579863 + * @param bpmnModel model + * @param historicActivityInstances 高亮线条 + * @return + */ + private List getHighLightedFlows(BpmnModel bpmnModel, List historicActivityInstances) { + // 高亮流程已发生流转的线id集合 + List highLightedFlowIds = new ArrayList<>(); + // 全部活动节点 + List historicActivityNodes = new ArrayList<>(); + // 已完成的历史活动节点 + List 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; + FlowNode targetFlowNode; + // 遍历已完成的活动实例,从每个实例的outgoingFlows中找到已执行的 + for (HistoricActivityInstance currentActivityInstance : finishedActivityInstances) { + // 获得当前活动对应的节点信息及outgoingFlows信息 + currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true); + List 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> tempMapList = new ArrayList<>(); + for (SequenceFlow sequenceFlow : sequenceFlows) { + for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) { + if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) { + Map 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 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; + } } diff --git a/yudao-framework/yudao-spring-boot-starter-activiti/pom.xml b/yudao-framework/yudao-spring-boot-starter-activiti/pom.xml index b1c08492a..08ac8c4bc 100644 --- a/yudao-framework/yudao-spring-boot-starter-activiti/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-activiti/pom.xml @@ -66,6 +66,11 @@ + + org.activiti + activiti-image-generator + ${activiti.version} + diff --git a/yudao-framework/yudao-spring-boot-starter-activiti/src/main/java/cn/iocoder/yudao/framework/activiti/config/YudaoActivitiConfiguration.java b/yudao-framework/yudao-spring-boot-starter-activiti/src/main/java/cn/iocoder/yudao/framework/activiti/config/YudaoActivitiConfiguration.java index 6e7355db9..6ce7991ac 100644 --- a/yudao-framework/yudao-spring-boot-starter-activiti/src/main/java/cn/iocoder/yudao/framework/activiti/config/YudaoActivitiConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-activiti/src/main/java/cn/iocoder/yudao/framework/activiti/config/YudaoActivitiConfiguration.java @@ -1,9 +1,12 @@ package cn.iocoder.yudao.framework.activiti.config; import org.activiti.api.runtime.shared.identity.UserGroupManager; +import org.activiti.image.ProcessDiagramGenerator; +import org.activiti.image.impl.DefaultProcessDiagramGenerator; import org.activiti.spring.SpringProcessEngineConfiguration; import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer; import org.apache.ibatis.session.SqlSessionFactory; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; @@ -12,7 +15,10 @@ public class YudaoActivitiConfiguration { - + @Bean + public ProcessDiagramGenerator processDiagramGenerator (){ + return new DefaultProcessDiagramGenerator(); + } @Component public static class SqlSessionFactoryProcessEngineConfigurationConfigurer implements ProcessEngineConfigurationConfigurer { diff --git a/yudao-framework/yudao-spring-boot-starter-activiti/src/main/resources/META-INF/spring.factories b/yudao-framework/yudao-spring-boot-starter-activiti/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..2f091cd70 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-activiti/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cn.iocoder.yudao.framework.activiti.config.YudaoActivitiConfiguration From 9bbeb93b2e8f6a3baf4fd883199e9f3db8d6b99f Mon Sep 17 00:00:00 2001 From: "yunlong.li" Date: Fri, 5 Nov 2021 18:14:56 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix:[=E5=B7=A5=E4=BD=9C=E6=B5=81]=20?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E5=AF=B9=E5=BA=94=E5=AE=9E=E4=BE=8B=E7=9A=84?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/workflow/TaskController.java | 2 +- .../activiti/service/workflow/TaskService.java | 3 ++- .../service/workflow/impl/TaskServiceImpl.java | 16 ++++++++++------ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/controller/workflow/TaskController.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/controller/workflow/TaskController.java index 3c0837309..e28a9c886 100644 --- a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/controller/workflow/TaskController.java +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/controller/workflow/TaskController.java @@ -59,7 +59,7 @@ public class TaskController { } /** - * 返回高亮的流转进程 + * 返回高亮的流转图SVG * @param processInstanceId */ @GetMapping("/process/highlight-img/{id}") diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/service/workflow/TaskService.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/service/workflow/TaskService.java index 321e48d4b..c5b020e87 100644 --- a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/service/workflow/TaskService.java +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/service/workflow/TaskService.java @@ -27,7 +27,8 @@ public interface TaskService { /** * 返回高亮的流转进程 - * @param processInstanceId + * @param processInstanceId 实例Id + * @param response 响应 */ void getHighlightImg(String processInstanceId, HttpServletResponse response); } diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/service/workflow/impl/TaskServiceImpl.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/service/workflow/impl/TaskServiceImpl.java index f5ecf0f0b..f1dda75ec 100644 --- a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/service/workflow/impl/TaskServiceImpl.java +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/service/workflow/impl/TaskServiceImpl.java @@ -23,6 +23,7 @@ import org.activiti.engine.RepositoryService; import org.activiti.engine.RuntimeService; import org.activiti.engine.history.HistoricActivityInstance; import org.activiti.engine.history.HistoricProcessInstance; +import org.activiti.engine.history.HistoricTaskInstance; import org.activiti.engine.repository.ProcessDefinition; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.task.Comment; @@ -34,7 +35,9 @@ import org.springframework.util.CollectionUtils; import javax.annotation.Resource; +import javax.imageio.ImageIO; import javax.servlet.http.HttpServletResponse; +import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.net.URLEncoder; @@ -225,28 +228,28 @@ public class TaskServiceImpl implements TaskService { @Override - public void getHighlightImg(String processDefinitionId, HttpServletResponse response) { + public void getHighlightImg(String processInstanceId, HttpServletResponse response) { // 查询历史 - HistoricProcessInstance hpi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processDefinitionId).singleResult(); + HistoricProcessInstance hpi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); // 如果有结束时间 if (hpi == null) { return; } // 没有结束时间。说明流程在执行过程中 - ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processDefinitionId).singleResult(); + ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId()); List highLightedActivities = new ArrayList<>(); - List historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processDefinitionId) + List historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId) .orderByHistoricActivityInstanceId().asc().list(); // 获取所有活动节点 List finishedInstances = historyService.createHistoricActivityInstanceQuery() - .processInstanceId(processDefinitionId).finished().list(); + .processInstanceId(processInstanceId).finished().list(); for (HistoricActivityInstance hai : finishedInstances) { highLightedActivities.add(hai.getActivityId()); } // 已完成的节点+当前节点 - highLightedActivities.addAll(runtimeService.getActiveActivityIds(processDefinitionId)); + highLightedActivities.addAll(runtimeService.getActiveActivityIds(processInstanceId)); // 经过的流 List highLightedFlowIds = getHighLightedFlows(bpmnModel, historicActivityInstances); @@ -264,6 +267,7 @@ public class TaskServiceImpl implements TaskService { } private void responseImage(HttpServletResponse response, InputStream inputStream, String picName) throws IOException { + BufferedImage src = ImageIO.read(inputStream); response.setContentType("application/octet-stream;charset=UTF-8"); response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(picName, "UTF-8")); byte[] b = new byte[1024]; From e9e73c402beeb8ac566ab8907e70e46ed883066c Mon Sep 17 00:00:00 2001 From: Roland <763506525@qq.com> Date: Fri, 5 Nov 2021 20:54:26 +0800 Subject: [PATCH 3/3] =?UTF-8?q?#=E8=BF=94=E6=98=BE=E9=AB=98=E4=BA=AE?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../activiti/service/workflow/impl/TaskServiceImpl.java | 1 - yudao-admin-ui/src/api/oa/todo.js | 8 ++++++++ yudao-admin-ui/src/views/oa/leave/index.vue | 9 +++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/service/workflow/impl/TaskServiceImpl.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/service/workflow/impl/TaskServiceImpl.java index f1dda75ec..c4e8823e8 100644 --- a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/service/workflow/impl/TaskServiceImpl.java +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/activiti/service/workflow/impl/TaskServiceImpl.java @@ -267,7 +267,6 @@ public class TaskServiceImpl implements TaskService { } private void responseImage(HttpServletResponse response, InputStream inputStream, String picName) throws IOException { - BufferedImage src = ImageIO.read(inputStream); response.setContentType("application/octet-stream;charset=UTF-8"); response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(picName, "UTF-8")); byte[] b = new byte[1024]; diff --git a/yudao-admin-ui/src/api/oa/todo.js b/yudao-admin-ui/src/api/oa/todo.js index 1aed4b058..cb98c466d 100644 --- a/yudao-admin-ui/src/api/oa/todo.js +++ b/yudao-admin-ui/src/api/oa/todo.js @@ -81,3 +81,11 @@ export function processHistorySteps(id) { method: 'get' }) } + + +export function getHighlightImg(id) { + return request({ + url: '/workflow/task/process/highlight-img/'+id, + method: 'get' + }) +} diff --git a/yudao-admin-ui/src/views/oa/leave/index.vue b/yudao-admin-ui/src/views/oa/leave/index.vue index 04a0eb6d0..ef9db1bc6 100644 --- a/yudao-admin-ui/src/views/oa/leave/index.vue +++ b/yudao-admin-ui/src/views/oa/leave/index.vue @@ -137,11 +137,12 @@ - +
+
@@ -156,7 +157,7 @@