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 9 import org.springframework.web.bind.annotation.*;
10 10  
11 11 import javax.annotation.Resource;
  12 +import javax.servlet.http.HttpServletResponse;
12 13 import javax.validation.Valid;
13 14 import java.util.List;
14 15  
... ... @@ -57,4 +58,15 @@ public class TaskController {
57 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 3 import cn.iocoder.yudao.adminserver.modules.activiti.controller.workflow.vo.*;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 5  
  6 +import javax.servlet.http.HttpServletResponse;
6 7 import java.util.List;
7 8  
8 9 // TODO @芋艿:前缀,注释
... ... @@ -23,4 +24,10 @@ public interface TaskService {
23 24  
24 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 5 import cn.iocoder.yudao.adminserver.modules.activiti.convert.workflow.TaskConvert;
6 6 import cn.iocoder.yudao.adminserver.modules.activiti.service.workflow.TaskService;
7 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  8 +import cn.iocoder.yudao.framework.security.core.LoginUser;
8 9 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
  10 +import com.google.common.collect.ImmutableMap;
  11 +import lombok.extern.slf4j.Slf4j;
9 12 import org.activiti.api.runtime.shared.query.Page;
10 13 import org.activiti.api.runtime.shared.query.Pageable;
11 14 import org.activiti.api.task.model.Task;
12 15 import org.activiti.api.task.model.builders.ClaimTaskPayloadBuilder;
13 16 import org.activiti.api.task.model.builders.TaskPayloadBuilder;
14 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 21 import org.activiti.engine.HistoryService;
16 22 import org.activiti.engine.RepositoryService;
  23 +import org.activiti.engine.RuntimeService;
17 24 import org.activiti.engine.history.HistoricActivityInstance;
  25 +import org.activiti.engine.history.HistoricProcessInstance;
18 26 import org.activiti.engine.repository.ProcessDefinition;
  27 +import org.activiti.engine.runtime.ProcessInstance;
19 28 import org.activiti.engine.task.Comment;
  29 +import org.activiti.image.ProcessDiagramGenerator;
  30 +import org.apache.commons.lang3.exception.ExceptionUtils;
20 31 import org.springframework.stereotype.Service;
21 32 import org.springframework.transaction.annotation.Transactional;
  33 +import org.springframework.util.CollectionUtils;
  34 +
22 35  
23 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 42 import java.util.stream.Collectors;
28 43  
  44 +@Slf4j
29 45 @Service
30 46 public class TaskServiceImpl implements TaskService {
31 47  
... ... @@ -41,6 +57,12 @@ public class TaskServiceImpl implements TaskService {
41 57 @Resource
42 58 private RepositoryService repositoryService;
43 59  
  60 + @Resource
  61 + private RuntimeService runtimeService;
  62 +
  63 + @Resource
  64 + private ProcessDiagramGenerator processDiagramGenerator;
  65 +
44 66 @Override
45 67 public PageResult<TodoTaskRespVO> getTodoTaskPage(TodoTaskPageReqVO pageReqVO) {
46 68 // TODO @jason:封装一个方法,用于转换成 activiti 的分页对象
... ... @@ -201,4 +223,131 @@ public class TaskServiceImpl implements TaskService {
201 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 66 </exclusion>
67 67 </exclusions>
68 68 </dependency>
  69 + <dependency>
  70 + <groupId>org.activiti</groupId>
  71 + <artifactId>activiti-image-generator</artifactId>
  72 + <version>${activiti.version}</version>
  73 + </dependency>
69 74 </dependencies>
70 75  
71 76 </project>
... ...
yudao-framework/yudao-spring-boot-starter-activiti/src/main/java/cn/iocoder/yudao/framework/activiti/config/YudaoActivitiConfiguration.java
1 1 package cn.iocoder.yudao.framework.activiti.config;
2 2  
3 3 import org.activiti.api.runtime.shared.identity.UserGroupManager;
  4 +import org.activiti.image.ProcessDiagramGenerator;
  5 +import org.activiti.image.impl.DefaultProcessDiagramGenerator;
4 6 import org.activiti.spring.SpringProcessEngineConfiguration;
5 7 import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer;
6 8 import org.apache.ibatis.session.SqlSessionFactory;
  9 +import org.springframework.context.annotation.Bean;
7 10 import org.springframework.context.annotation.Configuration;
8 11 import org.springframework.stereotype.Component;
9 12  
... ... @@ -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 22 @Component
17 23 public static class SqlSessionFactoryProcessEngineConfigurationConfigurer
18 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
... ...