Commit 30732428f17b0cab4087cf508fcb2ef1d13c02bf

Authored by 芋道源码
Committed by Gitee
2 parents abf2f697 e9e73c40

!54 返回高亮任务流图片

Merge pull request !54 from 云扬四海/feature/activiti
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 + * 返回高亮的流转图SVG
  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,11 @@ public interface TaskService { @@ -23,4 +24,11 @@ public interface TaskService {
23 24
24 TodoTaskRespVO getTaskFormKey(TaskQueryReqVO taskQuery); 25 TodoTaskRespVO getTaskFormKey(TaskQueryReqVO taskQuery);
25 26
  27 +
  28 + /**
  29 + * 返回高亮的流转进程
  30 + * @param processInstanceId 实例Id
  31 + * @param response 响应
  32 + */
  33 + void getHighlightImg(String processInstanceId, HttpServletResponse response);
26 } 34 }
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.*; @@ -5,27 +5,46 @@ 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;
  26 +import org.activiti.engine.history.HistoricTaskInstance;
18 import org.activiti.engine.repository.ProcessDefinition; 27 import org.activiti.engine.repository.ProcessDefinition;
  28 +import org.activiti.engine.runtime.ProcessInstance;
19 import org.activiti.engine.task.Comment; 29 import org.activiti.engine.task.Comment;
  30 +import org.activiti.image.ProcessDiagramGenerator;
  31 +import org.apache.commons.lang3.exception.ExceptionUtils;
20 import org.springframework.stereotype.Service; 32 import org.springframework.stereotype.Service;
21 import org.springframework.transaction.annotation.Transactional; 33 import org.springframework.transaction.annotation.Transactional;
  34 +import org.springframework.util.CollectionUtils;
  35 +
22 36
23 import javax.annotation.Resource; 37 import javax.annotation.Resource;
24 -import java.util.ArrayList;  
25 -import java.util.List;  
26 -import java.util.Optional; 38 +import javax.imageio.ImageIO;
  39 +import javax.servlet.http.HttpServletResponse;
  40 +import java.awt.image.BufferedImage;
  41 +import java.io.IOException;
  42 +import java.io.InputStream;
  43 +import java.net.URLEncoder;
  44 +import java.util.*;
27 import java.util.stream.Collectors; 45 import java.util.stream.Collectors;
28 46
  47 +@Slf4j
29 @Service 48 @Service
30 public class TaskServiceImpl implements TaskService { 49 public class TaskServiceImpl implements TaskService {
31 50
@@ -41,6 +60,12 @@ public class TaskServiceImpl implements TaskService { @@ -41,6 +60,12 @@ public class TaskServiceImpl implements TaskService {
41 @Resource 60 @Resource
42 private RepositoryService repositoryService; 61 private RepositoryService repositoryService;
43 62
  63 + @Resource
  64 + private RuntimeService runtimeService;
  65 +
  66 + @Resource
  67 + private ProcessDiagramGenerator processDiagramGenerator;
  68 +
44 @Override 69 @Override
45 public PageResult<TodoTaskRespVO> getTodoTaskPage(TodoTaskPageReqVO pageReqVO) { 70 public PageResult<TodoTaskRespVO> getTodoTaskPage(TodoTaskPageReqVO pageReqVO) {
46 // TODO @jason:封装一个方法,用于转换成 activiti 的分页对象 71 // TODO @jason:封装一个方法,用于转换成 activiti 的分页对象
@@ -201,4 +226,131 @@ public class TaskServiceImpl implements TaskService { @@ -201,4 +226,131 @@ public class TaskServiceImpl implements TaskService {
201 // return highLightedFlows; 226 // return highLightedFlows;
202 // } 227 // }
203 228
  229 +
  230 + @Override
  231 + public void getHighlightImg(String processInstanceId, HttpServletResponse response) {
  232 + // 查询历史
  233 + HistoricProcessInstance hpi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
  234 + // 如果有结束时间
  235 + if (hpi == null) {
  236 + return;
  237 + }
  238 + // 没有结束时间。说明流程在执行过程中
  239 + ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
  240 + BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId());
  241 + List<String> highLightedActivities = new ArrayList<>();
  242 +
  243 + List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId)
  244 + .orderByHistoricActivityInstanceId().asc().list();
  245 + // 获取所有活动节点
  246 + List<HistoricActivityInstance> finishedInstances = historyService.createHistoricActivityInstanceQuery()
  247 + .processInstanceId(processInstanceId).finished().list();
  248 + for (HistoricActivityInstance hai : finishedInstances) {
  249 + highLightedActivities.add(hai.getActivityId());
  250 + }
  251 + // 已完成的节点+当前节点
  252 + highLightedActivities.addAll(runtimeService.getActiveActivityIds(processInstanceId));
  253 +
  254 + // 经过的流
  255 + List<String> highLightedFlowIds = getHighLightedFlows(bpmnModel, historicActivityInstances);
  256 +
  257 + //设置"宋体"
  258 + try (InputStream inputStream = processDiagramGenerator.generateDiagram(bpmnModel, highLightedActivities, highLightedFlowIds,
  259 + "宋体", "宋体", "宋体")){
  260 + String picName = hpi.getProcessDefinitionName()+".svg";
  261 + // 输出到浏览器
  262 + responseImage(response, inputStream, picName);
  263 + } catch (IOException e) {
  264 + log.error(ExceptionUtils.getStackTrace(e));
  265 + }
  266 +
  267 + }
  268 +
  269 + private void responseImage(HttpServletResponse response, InputStream inputStream, String picName) throws IOException {
  270 + response.setContentType("application/octet-stream;charset=UTF-8");
  271 + response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(picName, "UTF-8"));
  272 + byte[] b = new byte[1024];
  273 + int len = -1;
  274 + while ((len = inputStream.read(b, 0, 1024)) != -1) {
  275 + response.getOutputStream().write(b, 0, len);
  276 + }
  277 + response.flushBuffer();
  278 + }
  279 + /**
  280 + * 获取已经流转的线
  281 + * @see https://blog.csdn.net/qiuxinfa123/article/details/119579863
  282 + * @param bpmnModel model
  283 + * @param historicActivityInstances 高亮线条
  284 + * @return
  285 + */
  286 + private List<String> getHighLightedFlows(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances) {
  287 + // 高亮流程已发生流转的线id集合
  288 + List<String> highLightedFlowIds = new ArrayList<>();
  289 + // 全部活动节点
  290 + List<FlowNode> historicActivityNodes = new ArrayList<>();
  291 + // 已完成的历史活动节点
  292 + List<HistoricActivityInstance> finishedActivityInstances = new ArrayList<>();
  293 +
  294 + for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
  295 + FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true);
  296 + historicActivityNodes.add(flowNode);
  297 + // 结束时间不为空,则是已完成节点
  298 + if (historicActivityInstance.getEndTime() != null) {
  299 + finishedActivityInstances.add(historicActivityInstance);
  300 + }
  301 + }
  302 +
  303 + FlowNode currentFlowNode;
  304 + FlowNode targetFlowNode;
  305 + // 遍历已完成的活动实例,从每个实例的outgoingFlows中找到已执行的
  306 + for (HistoricActivityInstance currentActivityInstance : finishedActivityInstances) {
  307 + // 获得当前活动对应的节点信息及outgoingFlows信息
  308 + currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true);
  309 + List<SequenceFlow> sequenceFlows = currentFlowNode.getOutgoingFlows();
  310 +
  311 + /**
  312 + * 遍历outgoingFlows并找到已流转的 满足如下条件认为已已流转:
  313 + * 1.当前节点是并行网关或兼容网关,则通过outgoingFlows能够在历史活动中找到的全部节点均为已流转
  314 + * 2.当前节点是以上两种类型之外的,通过outgoingFlows查找到的时间最早的流转节点视为有效流转
  315 + */
  316 + if ("parallelGateway".equals(currentActivityInstance.getActivityType()) || "inclusiveGateway".equals(currentActivityInstance.getActivityType())) {
  317 + // 遍历历史活动节点,找到匹配流程目标节点的
  318 + for (SequenceFlow sequenceFlow : sequenceFlows) {
  319 + targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(sequenceFlow.getTargetRef(), true);
  320 + if (historicActivityNodes.contains(targetFlowNode)) {
  321 + highLightedFlowIds.add(targetFlowNode.getId());
  322 + }
  323 + }
  324 + } else {
  325 + List<Map<String, Object>> tempMapList = new ArrayList<>();
  326 + for (SequenceFlow sequenceFlow : sequenceFlows) {
  327 + for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
  328 + if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
  329 + Map<String, Object> map = new HashMap<>();
  330 + map.put("highLightedFlowId", sequenceFlow.getId());
  331 + map.put("highLightedFlowStartTime", historicActivityInstance.getStartTime().getTime());
  332 + tempMapList.add(map);
  333 + }
  334 + }
  335 + }
  336 +
  337 + if (!CollectionUtils.isEmpty(tempMapList)) {
  338 + // 遍历匹配的集合,取得开始时间最早的一个
  339 + long earliestStamp = 0L;
  340 + String highLightedFlowId = null;
  341 + for (Map<String, Object> map : tempMapList) {
  342 + long highLightedFlowStartTime = Long.valueOf(map.get("highLightedFlowStartTime").toString());
  343 + if (earliestStamp == 0 || earliestStamp >= highLightedFlowStartTime) {
  344 + highLightedFlowId = map.get("highLightedFlowId").toString();
  345 + earliestStamp = highLightedFlowStartTime;
  346 + }
  347 + }
  348 + highLightedFlowIds.add(highLightedFlowId);
  349 + }
  350 +
  351 + }
  352 +
  353 + }
  354 + return highLightedFlowIds;
  355 + }
204 } 356 }
yudao-admin-ui/src/api/oa/todo.js
@@ -81,3 +81,11 @@ export function processHistorySteps(id) { @@ -81,3 +81,11 @@ export function processHistorySteps(id) {
81 method: 'get' 81 method: 'get'
82 }) 82 })
83 } 83 }
  84 +
  85 +
  86 +export function getHighlightImg(id) {
  87 + return request({
  88 + url: '/workflow/task/process/highlight-img/'+id,
  89 + method: 'get'
  90 + })
  91 +}
yudao-admin-ui/src/views/oa/leave/index.vue
@@ -137,11 +137,12 @@ @@ -137,11 +137,12 @@
137 </div> 137 </div>
138 </el-dialog> 138 </el-dialog>
139 139
140 - <el-dialog :title="title" :visible.sync="dialogStepsVisible" width="600px" append-to-body> 140 + <el-dialog :title="title" :visible.sync="dialogStepsVisible" width="750px" append-to-body>
141 <el-steps :active="stepActive" finish-status="success" > 141 <el-steps :active="stepActive" finish-status="success" >
142 <el-step :title="stepTitle(item)" :description="stepAssignee(item.assignee)" icon="el-icon-edit" v-for="(item) in handleTask.historyTask"></el-step> 142 <el-step :title="stepTitle(item)" :description="stepAssignee(item.assignee)" icon="el-icon-edit" v-for="(item) in handleTask.historyTask"></el-step>
143 </el-steps> 143 </el-steps>
144 <br/> 144 <br/>
  145 + <div v-html="svgUrl"></div>
145 <el-steps direction="vertical" :active="stepActive"> 146 <el-steps direction="vertical" :active="stepActive">
146 <el-step :title="stepTitle(item)" :description="stepDes(item)" v-for="(item) in handleTask.historyTask"></el-step> 147 <el-step :title="stepTitle(item)" :description="stepDes(item)" v-for="(item) in handleTask.historyTask"></el-step>
147 </el-steps> 148 </el-steps>
@@ -156,7 +157,7 @@ @@ -156,7 +157,7 @@
156 <script> 157 <script>
157 import { createLeave, updateLeave, deleteLeave, getLeave, getLeavePage, exportLeaveExcel } from "@/api/oa/leave" 158 import { createLeave, updateLeave, deleteLeave, getLeave, getLeavePage, exportLeaveExcel } from "@/api/oa/leave"
158 import { getDictDataLabel, getDictDatas, DICT_TYPE } from '@/utils/dict' 159 import { getDictDataLabel, getDictDatas, DICT_TYPE } from '@/utils/dict'
159 -import { processHistorySteps } from '@/api/oa/todo' 160 +import { processHistorySteps,getHighlightImg } from '@/api/oa/todo'
160 export default { 161 export default {
161 name: "Leave", 162 name: "Leave",
162 components: { 163 components: {
@@ -194,6 +195,7 @@ export default { @@ -194,6 +195,7 @@ export default {
194 }, 195 },
195 // 表单参数 196 // 表单参数
196 form: {}, 197 form: {},
  198 + svgUrl: "",
197 handleTask: { 199 handleTask: {
198 historyTask:[], 200 historyTask:[],
199 taskVariable: "", 201 taskVariable: "",
@@ -361,6 +363,9 @@ export default { @@ -361,6 +363,9 @@ export default {
361 this.handleTask.historyTask = response.data; 363 this.handleTask.historyTask = response.data;
362 this.dialogStepsVisible = true 364 this.dialogStepsVisible = true
363 this.title = "审批进度"; 365 this.title = "审批进度";
  366 + getHighlightImg(row.processInstanceId).then(response => {
  367 + that.svgUrl = response
  368 + })
364 }); 369 });
365 }, 370 },
366 /** 导出按钮操作 */ 371 /** 导出按钮操作 */
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