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..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 @@ -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)); } + /** + * 返回高亮的流转图SVG + * @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..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 @@ -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,11 @@ public interface TaskService { TodoTaskRespVO getTaskFormKey(TaskQueryReqVO taskQuery); + + /** + * 返回高亮的流转进程 + * @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 0907ae467..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 @@ -5,27 +5,46 @@ 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.history.HistoricTaskInstance; 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.imageio.ImageIO; +import javax.servlet.http.HttpServletResponse; +import java.awt.image.BufferedImage; +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 +60,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 +226,131 @@ public class TaskServiceImpl implements TaskService { // return highLightedFlows; // } + + @Override + public void getHighlightImg(String processInstanceId, HttpServletResponse response) { + // 查询历史 + HistoricProcessInstance hpi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + // 如果有结束时间 + if (hpi == null) { + return; + } + // 没有结束时间。说明流程在执行过程中 + ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId()); + List highLightedActivities = new ArrayList<>(); + + List historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId) + .orderByHistoricActivityInstanceId().asc().list(); + // 获取所有活动节点 + List finishedInstances = historyService.createHistoricActivityInstanceQuery() + .processInstanceId(processInstanceId).finished().list(); + for (HistoricActivityInstance hai : finishedInstances) { + highLightedActivities.add(hai.getActivityId()); + } + // 已完成的节点+当前节点 + highLightedActivities.addAll(runtimeService.getActiveActivityIds(processInstanceId)); + + // 经过的流 + 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-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 @@