Commit 3c3f46ee4eb93bcccab67846767429d00712d9a3
1 parent
abf2f697
fix:[工作流] 返回对应实例的流程图
Showing
6 changed files
with
185 additions
and
4 deletions
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