Ver Fonte

fix:[工作流] 返回对应实例的流程图

yunlong.li há 3 anos atrás
pai
commit
3c3f46ee4e

+ 12 - 0
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);
+    }
+
+
+
 }

+ 7 - 0
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);
 }

+ 152 - 3
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<TodoTaskRespVO> 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<String> highLightedActivities = new ArrayList<>();
+
+        List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processDefinitionId)
+                .orderByHistoricActivityInstanceId().asc().list();
+        // 获取所有活动节点
+        List<HistoricActivityInstance> finishedInstances = historyService.createHistoricActivityInstanceQuery()
+                .processInstanceId(processDefinitionId).finished().list();
+        for (HistoricActivityInstance hai : finishedInstances) {
+            highLightedActivities.add(hai.getActivityId());
+        }
+        // 已完成的节点+当前节点
+        highLightedActivities.addAll(runtimeService.getActiveActivityIds(processDefinitionId));
+
+        // 经过的流
+        List<String> 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<String> getHighLightedFlows(BpmnModel bpmnModel, 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;
+        FlowNode targetFlowNode;
+        // 遍历已完成的活动实例,从每个实例的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;
+    }
 }

+ 5 - 0
yudao-framework/yudao-spring-boot-starter-activiti/pom.xml

@@ -66,6 +66,11 @@
                 </exclusion>
             </exclusions>
         </dependency>
+        <dependency>
+            <groupId>org.activiti</groupId>
+            <artifactId>activiti-image-generator</artifactId>
+            <version>${activiti.version}</version>
+        </dependency>
     </dependencies>
 
 </project>

+ 7 - 1
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 {

+ 2 - 0
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