Commit 3c3f46ee4eb93bcccab67846767429d00712d9a3

Authored by yunlong.li
1 parent abf2f697

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

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; @@ -9,6 +9,7 @@ import io.swagger.annotations.ApiOperation;
9 import org.springframework.web.bind.annotation.*; 9 import org.springframework.web.bind.annotation.*;
10 10
11 import javax.annotation.Resource; 11 import javax.annotation.Resource;
  12 +import javax.servlet.http.HttpServletResponse;
12 import javax.validation.Valid; 13 import javax.validation.Valid;
13 import java.util.List; 14 import java.util.List;
14 15
@@ -57,4 +58,15 @@ public class TaskController { @@ -57,4 +58,15 @@ public class TaskController {
57 return success(taskService.getHistorySteps(processInstanceId)); 58 return success(taskService.getHistorySteps(processInstanceId));
58 } 59 }
59 60
  61 + /**
  62 + * 返回高亮的流转进程
  63 + * @param processInstanceId
  64 + */
  65 + @GetMapping("/process/highlight-img/{id}")
  66 + public void getHighlightImg(@PathVariable("id") String processInstanceId, HttpServletResponse response) {
  67 + taskService.getHighlightImg(processInstanceId, response);
  68 + }
  69 +
  70 +
  71 +
60 } 72 }
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; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.adminserver.modules.activiti.service.workflow;
3 import cn.iocoder.yudao.adminserver.modules.activiti.controller.workflow.vo.*; 3 import cn.iocoder.yudao.adminserver.modules.activiti.controller.workflow.vo.*;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 5
  6 +import javax.servlet.http.HttpServletResponse;
6 import java.util.List; 7 import java.util.List;
7 8
8 // TODO @芋艿:前缀,注释 9 // TODO @芋艿:前缀,注释
@@ -23,4 +24,10 @@ public interface TaskService { @@ -23,4 +24,10 @@ public interface TaskService {
23 24
24 TodoTaskRespVO getTaskFormKey(TaskQueryReqVO taskQuery); 25 TodoTaskRespVO getTaskFormKey(TaskQueryReqVO taskQuery);
25 26
  27 +
  28 + /**
  29 + * 返回高亮的流转进程
  30 + * @param processInstanceId
  31 + */
  32 + void getHighlightImg(String processInstanceId, HttpServletResponse response);
26 } 33 }
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.*; @@ -5,27 +5,43 @@ import cn.iocoder.yudao.adminserver.modules.activiti.controller.workflow.vo.*;
5 import cn.iocoder.yudao.adminserver.modules.activiti.convert.workflow.TaskConvert; 5 import cn.iocoder.yudao.adminserver.modules.activiti.convert.workflow.TaskConvert;
6 import cn.iocoder.yudao.adminserver.modules.activiti.service.workflow.TaskService; 6 import cn.iocoder.yudao.adminserver.modules.activiti.service.workflow.TaskService;
7 import cn.iocoder.yudao.framework.common.pojo.PageResult; 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  8 +import cn.iocoder.yudao.framework.security.core.LoginUser;
8 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; 9 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
  10 +import com.google.common.collect.ImmutableMap;
  11 +import lombok.extern.slf4j.Slf4j;
9 import org.activiti.api.runtime.shared.query.Page; 12 import org.activiti.api.runtime.shared.query.Page;
10 import org.activiti.api.runtime.shared.query.Pageable; 13 import org.activiti.api.runtime.shared.query.Pageable;
11 import org.activiti.api.task.model.Task; 14 import org.activiti.api.task.model.Task;
12 import org.activiti.api.task.model.builders.ClaimTaskPayloadBuilder; 15 import org.activiti.api.task.model.builders.ClaimTaskPayloadBuilder;
13 import org.activiti.api.task.model.builders.TaskPayloadBuilder; 16 import org.activiti.api.task.model.builders.TaskPayloadBuilder;
14 import org.activiti.api.task.runtime.TaskRuntime; 17 import org.activiti.api.task.runtime.TaskRuntime;
  18 +import org.activiti.bpmn.model.BpmnModel;
  19 +import org.activiti.bpmn.model.FlowNode;
  20 +import org.activiti.bpmn.model.SequenceFlow;
15 import org.activiti.engine.HistoryService; 21 import org.activiti.engine.HistoryService;
16 import org.activiti.engine.RepositoryService; 22 import org.activiti.engine.RepositoryService;
  23 +import org.activiti.engine.RuntimeService;
17 import org.activiti.engine.history.HistoricActivityInstance; 24 import org.activiti.engine.history.HistoricActivityInstance;
  25 +import org.activiti.engine.history.HistoricProcessInstance;
18 import org.activiti.engine.repository.ProcessDefinition; 26 import org.activiti.engine.repository.ProcessDefinition;
  27 +import org.activiti.engine.runtime.ProcessInstance;
19 import org.activiti.engine.task.Comment; 28 import org.activiti.engine.task.Comment;
  29 +import org.activiti.image.ProcessDiagramGenerator;
  30 +import org.apache.commons.lang3.exception.ExceptionUtils;
20 import org.springframework.stereotype.Service; 31 import org.springframework.stereotype.Service;
21 import org.springframework.transaction.annotation.Transactional; 32 import org.springframework.transaction.annotation.Transactional;
  33 +import org.springframework.util.CollectionUtils;
  34 +
22 35
23 import javax.annotation.Resource; 36 import javax.annotation.Resource;
24 -import java.util.ArrayList;  
25 -import java.util.List;  
26 -import java.util.Optional; 37 +import javax.servlet.http.HttpServletResponse;
  38 +import java.io.IOException;
  39 +import java.io.InputStream;
  40 +import java.net.URLEncoder;
  41 +import java.util.*;
27 import java.util.stream.Collectors; 42 import java.util.stream.Collectors;
28 43
  44 +@Slf4j
29 @Service 45 @Service
30 public class TaskServiceImpl implements TaskService { 46 public class TaskServiceImpl implements TaskService {
31 47
@@ -41,6 +57,12 @@ public class TaskServiceImpl implements TaskService { @@ -41,6 +57,12 @@ public class TaskServiceImpl implements TaskService {
41 @Resource 57 @Resource
42 private RepositoryService repositoryService; 58 private RepositoryService repositoryService;
43 59
  60 + @Resource
  61 + private RuntimeService runtimeService;
  62 +
  63 + @Resource
  64 + private ProcessDiagramGenerator processDiagramGenerator;
  65 +
44 @Override 66 @Override
45 public PageResult<TodoTaskRespVO> getTodoTaskPage(TodoTaskPageReqVO pageReqVO) { 67 public PageResult<TodoTaskRespVO> getTodoTaskPage(TodoTaskPageReqVO pageReqVO) {
46 // TODO @jason:封装一个方法,用于转换成 activiti 的分页对象 68 // TODO @jason:封装一个方法,用于转换成 activiti 的分页对象
@@ -201,4 +223,131 @@ public class TaskServiceImpl implements TaskService { @@ -201,4 +223,131 @@ public class TaskServiceImpl implements TaskService {
201 // return highLightedFlows; 223 // return highLightedFlows;
202 // } 224 // }
203 225
  226 +
  227 + @Override
  228 + public void getHighlightImg(String processDefinitionId, HttpServletResponse response) {
  229 + // 查询历史
  230 + HistoricProcessInstance hpi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processDefinitionId).singleResult();
  231 + // 如果有结束时间
  232 + if (hpi == null) {
  233 + return;
  234 + }
  235 + // 没有结束时间。说明流程在执行过程中
  236 + ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processDefinitionId).singleResult();
  237 + BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId());
  238 + List<String> highLightedActivities = new ArrayList<>();
  239 +
  240 + List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processDefinitionId)
  241 + .orderByHistoricActivityInstanceId().asc().list();
  242 + // 获取所有活动节点
  243 + List<HistoricActivityInstance> finishedInstances = historyService.createHistoricActivityInstanceQuery()
  244 + .processInstanceId(processDefinitionId).finished().list();
  245 + for (HistoricActivityInstance hai : finishedInstances) {
  246 + highLightedActivities.add(hai.getActivityId());
  247 + }
  248 + // 已完成的节点+当前节点
  249 + highLightedActivities.addAll(runtimeService.getActiveActivityIds(processDefinitionId));
  250 +
  251 + // 经过的流
  252 + List<String> highLightedFlowIds = getHighLightedFlows(bpmnModel, historicActivityInstances);
  253 +
  254 + //设置"宋体"
  255 + try (InputStream inputStream = processDiagramGenerator.generateDiagram(bpmnModel, highLightedActivities, highLightedFlowIds,
  256 + "宋体", "宋体", "宋体")){
  257 + String picName = hpi.getProcessDefinitionName()+".svg";
  258 + // 输出到浏览器
  259 + responseImage(response, inputStream, picName);
  260 + } catch (IOException e) {
  261 + log.error(ExceptionUtils.getStackTrace(e));
  262 + }
  263 +
  264 + }
  265 +
  266 + private void responseImage(HttpServletResponse response, InputStream inputStream, String picName) throws IOException {
  267 + response.setContentType("application/octet-stream;charset=UTF-8");
  268 + response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(picName, "UTF-8"));
  269 + byte[] b = new byte[1024];
  270 + int len = -1;
  271 + while ((len = inputStream.read(b, 0, 1024)) != -1) {
  272 + response.getOutputStream().write(b, 0, len);
  273 + }
  274 + response.flushBuffer();
  275 + }
  276 + /**
  277 + * 获取已经流转的线
  278 + * @see https://blog.csdn.net/qiuxinfa123/article/details/119579863
  279 + * @param bpmnModel model
  280 + * @param historicActivityInstances 高亮线条
  281 + * @return
  282 + */
  283 + private List<String> getHighLightedFlows(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances) {
  284 + // 高亮流程已发生流转的线id集合
  285 + List<String> highLightedFlowIds = new ArrayList<>();
  286 + // 全部活动节点
  287 + List<FlowNode> historicActivityNodes = new ArrayList<>();
  288 + // 已完成的历史活动节点
  289 + List<HistoricActivityInstance> finishedActivityInstances = new ArrayList<>();
  290 +
  291 + for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
  292 + FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true);
  293 + historicActivityNodes.add(flowNode);
  294 + // 结束时间不为空,则是已完成节点
  295 + if (historicActivityInstance.getEndTime() != null) {
  296 + finishedActivityInstances.add(historicActivityInstance);
  297 + }
  298 + }
  299 +
  300 + FlowNode currentFlowNode;
  301 + FlowNode targetFlowNode;
  302 + // 遍历已完成的活动实例,从每个实例的outgoingFlows中找到已执行的
  303 + for (HistoricActivityInstance currentActivityInstance : finishedActivityInstances) {
  304 + // 获得当前活动对应的节点信息及outgoingFlows信息
  305 + currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true);
  306 + List<SequenceFlow> sequenceFlows = currentFlowNode.getOutgoingFlows();
  307 +
  308 + /**
  309 + * 遍历outgoingFlows并找到已流转的 满足如下条件认为已已流转:
  310 + * 1.当前节点是并行网关或兼容网关,则通过outgoingFlows能够在历史活动中找到的全部节点均为已流转
  311 + * 2.当前节点是以上两种类型之外的,通过outgoingFlows查找到的时间最早的流转节点视为有效流转
  312 + */
  313 + if ("parallelGateway".equals(currentActivityInstance.getActivityType()) || "inclusiveGateway".equals(currentActivityInstance.getActivityType())) {
  314 + // 遍历历史活动节点,找到匹配流程目标节点的
  315 + for (SequenceFlow sequenceFlow : sequenceFlows) {
  316 + targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(sequenceFlow.getTargetRef(), true);
  317 + if (historicActivityNodes.contains(targetFlowNode)) {
  318 + highLightedFlowIds.add(targetFlowNode.getId());
  319 + }
  320 + }
  321 + } else {
  322 + List<Map<String, Object>> tempMapList = new ArrayList<>();
  323 + for (SequenceFlow sequenceFlow : sequenceFlows) {
  324 + for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
  325 + if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
  326 + Map<String, Object> map = new HashMap<>();
  327 + map.put("highLightedFlowId", sequenceFlow.getId());
  328 + map.put("highLightedFlowStartTime", historicActivityInstance.getStartTime().getTime());
  329 + tempMapList.add(map);
  330 + }
  331 + }
  332 + }
  333 +
  334 + if (!CollectionUtils.isEmpty(tempMapList)) {
  335 + // 遍历匹配的集合,取得开始时间最早的一个
  336 + long earliestStamp = 0L;
  337 + String highLightedFlowId = null;
  338 + for (Map<String, Object> map : tempMapList) {
  339 + long highLightedFlowStartTime = Long.valueOf(map.get("highLightedFlowStartTime").toString());
  340 + if (earliestStamp == 0 || earliestStamp >= highLightedFlowStartTime) {
  341 + highLightedFlowId = map.get("highLightedFlowId").toString();
  342 + earliestStamp = highLightedFlowStartTime;
  343 + }
  344 + }
  345 + highLightedFlowIds.add(highLightedFlowId);
  346 + }
  347 +
  348 + }
  349 +
  350 + }
  351 + return highLightedFlowIds;
  352 + }
204 } 353 }
yudao-framework/yudao-spring-boot-starter-activiti/pom.xml
@@ -66,6 +66,11 @@ @@ -66,6 +66,11 @@
66 </exclusion> 66 </exclusion>
67 </exclusions> 67 </exclusions>
68 </dependency> 68 </dependency>
  69 + <dependency>
  70 + <groupId>org.activiti</groupId>
  71 + <artifactId>activiti-image-generator</artifactId>
  72 + <version>${activiti.version}</version>
  73 + </dependency>
69 </dependencies> 74 </dependencies>
70 75
71 </project> 76 </project>
yudao-framework/yudao-spring-boot-starter-activiti/src/main/java/cn/iocoder/yudao/framework/activiti/config/YudaoActivitiConfiguration.java
1 package cn.iocoder.yudao.framework.activiti.config; 1 package cn.iocoder.yudao.framework.activiti.config;
2 2
3 import org.activiti.api.runtime.shared.identity.UserGroupManager; 3 import org.activiti.api.runtime.shared.identity.UserGroupManager;
  4 +import org.activiti.image.ProcessDiagramGenerator;
  5 +import org.activiti.image.impl.DefaultProcessDiagramGenerator;
4 import org.activiti.spring.SpringProcessEngineConfiguration; 6 import org.activiti.spring.SpringProcessEngineConfiguration;
5 import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer; 7 import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer;
6 import org.apache.ibatis.session.SqlSessionFactory; 8 import org.apache.ibatis.session.SqlSessionFactory;
  9 +import org.springframework.context.annotation.Bean;
7 import org.springframework.context.annotation.Configuration; 10 import org.springframework.context.annotation.Configuration;
8 import org.springframework.stereotype.Component; 11 import org.springframework.stereotype.Component;
9 12
@@ -12,7 +15,10 @@ public class YudaoActivitiConfiguration { @@ -12,7 +15,10 @@ public class YudaoActivitiConfiguration {
12 15
13 16
14 17
15 - 18 + @Bean
  19 + public ProcessDiagramGenerator processDiagramGenerator (){
  20 + return new DefaultProcessDiagramGenerator();
  21 + }
16 @Component 22 @Component
17 public static class SqlSessionFactoryProcessEngineConfigurationConfigurer 23 public static class SqlSessionFactoryProcessEngineConfigurationConfigurer
18 implements ProcessEngineConfigurationConfigurer { 24 implements ProcessEngineConfigurationConfigurer {
yudao-framework/yudao-spring-boot-starter-activiti/src/main/resources/META-INF/spring.factories 0 → 100644
  1 +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  2 + cn.iocoder.yudao.framework.activiti.config.YudaoActivitiConfiguration