Commit 676e4f29d908de2d55d537bef6823b2d6642d3ae

Authored by YunaiV
1 parent adc6076d

重新实现后端的 bpm 流程图的高亮接口

yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/controller/task/BpmActivityController.java 0 → 100644
  1 +package cn.iocoder.yudao.adminserver.modules.bpm.controller.task;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.adminserver.modules.bpm.service.task.BpmActivityService;
  5 +import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
  6 +import io.swagger.annotations.Api;
  7 +import io.swagger.annotations.ApiImplicitParam;
  8 +import io.swagger.annotations.ApiOperation;
  9 +import org.springframework.validation.annotation.Validated;
  10 +import org.springframework.web.bind.annotation.GetMapping;
  11 +import org.springframework.web.bind.annotation.RequestMapping;
  12 +import org.springframework.web.bind.annotation.RequestParam;
  13 +import org.springframework.web.bind.annotation.RestController;
  14 +
  15 +import javax.annotation.Resource;
  16 +import javax.servlet.http.HttpServletResponse;
  17 +import java.io.IOException;
  18 +
  19 +@Api(tags = "流程活动实例")
  20 +@RestController
  21 +@RequestMapping("/bpm/activity")
  22 +@Validated
  23 +public class BpmActivityController {
  24 +
  25 + @Resource
  26 + private BpmActivityService activityService;
  27 +
  28 + // TODO 芋艿:注解、权限、validtion
  29 +
  30 + @ApiOperation(value = "生成指定流程实例的高亮流程图",
  31 + notes = "只高亮进行中的任务。不过要注意,该接口暂时没用,通过前端的 ProcessViewer.vue 界面的 highlightDiagram 方法生成")
  32 + @GetMapping("/generate-highlight-diagram")
  33 + @ApiImplicitParam(name = "id", value = "流程实例的编号", required = true, dataTypeClass = String.class)
  34 + public void generateHighlightDiagram(@RequestParam("processInstanceId") String processInstanceId,
  35 + HttpServletResponse response) throws IOException {
  36 + byte[] bytes = activityService.generateHighlightDiagram(processInstanceId);
  37 + ServletUtils.writeAttachment(response, StrUtil.format("流程图-{}.svg", processInstanceId), bytes);
  38 + }
  39 +
  40 +}
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/controller/task/BpmTaskController.java
... ... @@ -20,7 +20,7 @@ import java.util.List;
20 20 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
21 21 import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
22 22  
23   -@Api(tags = "流程任务")
  23 +@Api(tags = "流程任务实例")
24 24 @RestController
25 25 @RequestMapping("/bpm/task")
26 26 @Validated
... ... @@ -72,14 +72,4 @@ public class BpmTaskController {
72 72 return success(taskService.getTaskListByProcessInstanceId(processInstanceId));
73 73 }
74 74  
75   - /**
76   - * 返回高亮的流转图SVG
77   - * @param processInstanceId 流程Id
78   - */
79   - @GetMapping("/process/highlight-img")
80   - public void getHighlightImg(@RequestParam String processInstanceId, HttpServletResponse response) throws IOException {
81   - FileResp fileResp = taskService.getHighlightImg(processInstanceId);
82   - ServletUtils.writeAttachment(response, fileResp.getFileName(), fileResp.getFileByte());
83   - }
84   -
85 75 }
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/controller/task/vo/task/FileResp.java deleted
1   -package cn.iocoder.yudao.adminserver.modules.bpm.controller.task.vo.task;
2   -
3   -import lombok.Data;
4   -
5   -// TODO @Li:1)改成 HighlightImgRespVO 吧。2)swagger 注解要补充;3)fileByte => fileContent
6   -/**
7   - * 文件输出类
8   - *
9   - * @author yunlongn
10   - */
11   -@Data
12   -public class FileResp {
13   -
14   - /**
15   - * 文件名字
16   - */
17   - private String fileName;
18   -
19   - /**
20   - * 文件输出流
21   - */
22   - private byte[] fileByte;
23   -
24   -}
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/enums/BpmErrorCodeConstants.java
... ... @@ -33,7 +33,8 @@ public interface BpmErrorCodeConstants {
33 33 ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1009003000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图");
34 34 ErrorCode PROCESS_DEFINITION_NAME_NOT_MATCH = new ErrorCode(1009003001, "流程定义的名字期望是({}),当前是({}),请修改 BPMN 流程图");
35 35 ErrorCode PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1009003002, "流程定义不存在");
36   - ErrorCode PROCESS_DEFINITION_IS_SUSPENDED = new ErrorCode(1009003002, "流程定义处于挂起状态");
  36 + ErrorCode PROCESS_DEFINITION_IS_SUSPENDED = new ErrorCode(1009003003, "流程定义处于挂起状态");
  37 + ErrorCode PROCESS_DEFINITION_BPMN_MODEL_NOT_EXISTS = new ErrorCode(1009003004, "流程定义的模型不存在");
37 38  
38 39 // ========== 流程实例 1-009-004-000 ==========
39 40 ErrorCode PROCESS_INSTANCE_NOT_EXISTS = new ErrorCode(1009004000, "流程实例不存在");
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/service/task/BpmActivityService.java 0 → 100644
  1 +package cn.iocoder.yudao.adminserver.modules.bpm.service.task;
  2 +
  3 +/**
  4 + * BPM 活动实例 Service 接口
  5 + *
  6 + * @author 芋道源码
  7 + */
  8 +public interface BpmActivityService {
  9 +
  10 + /**
  11 + * 生成指定流程实例的高亮流程图,只高亮进行中的任务
  12 + *
  13 + * 友情提示,非该方法的注释。如果想实现更高级的高亮流程图(当前节点红色 + 完成节点为绿色),可参考如下内容:
  14 + * 博客一:https://blog.csdn.net/qq_40109075/article/details/110939639
  15 + * 博客二:https://gitee.com/tony2y/RuoYi-flowable/blob/master/ruoyi-flowable/src/main/java/com/ruoyi/flowable/flow/CustomProcessDiagramGenerator.java
  16 + * 这里不实现的原理,需要自定义实现 ProcessDiagramGenerator 和 ProcessDiagramCanvas,代码量有点大
  17 + *
  18 + * 如果你想实现高亮已完成的任务,可参考 https://blog.csdn.net/qiuxinfa123/article/details/119579863 博客。不过测试下来,貌似不太对~
  19 + *
  20 + * @param processInstanceId 实例Id
  21 + * @return 图的字节数组
  22 + */
  23 + byte[] generateHighlightDiagram(String processInstanceId);
  24 +
  25 +}
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/service/task/BpmTaskService.java
... ... @@ -10,7 +10,7 @@ import java.util.List;
10 10 import java.util.Map;
11 11  
12 12 /**
13   - * 流程任务 Service 接口
  13 + * 流程任务实例 Service 接口
14 14 *
15 15 * @author jason
16 16 * @author 芋道源码
... ... @@ -35,6 +35,14 @@ public interface BpmTaskService {
35 35 /**
36 36 * 获得流程任务列表
37 37 *
  38 + * @param processInstanceId 流程实例的编号
  39 + * @return 流程任务列表
  40 + */
  41 + List<Task> getTasksByProcessInstanceId(String processInstanceId);
  42 +
  43 + /**
  44 + * 获得流程任务列表
  45 + *
38 46 * @param processInstanceIds 流程实例的编号数组
39 47 * @return 流程任务列表
40 48 */
... ... @@ -101,13 +109,6 @@ public interface BpmTaskService {
101 109 */
102 110 void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO);
103 111  
104   - /**
105   - * 返回高亮的流转进程
106   - * @param processInstanceId 实例Id
107   - * @return {@link FileResp} 返回文件
108   - */
109   - FileResp getHighlightImg(String processInstanceId);
110   -
111 112 // ========== Task 拓展表相关 ==========
112 113  
113 114 /**
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/service/task/impl/BpmActivityServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.adminserver.modules.bpm.service.task.impl;
  2 +
  3 +import cn.hutool.core.io.IoUtil;
  4 +import cn.iocoder.yudao.adminserver.modules.bpm.service.definition.BpmProcessDefinitionService;
  5 +import cn.iocoder.yudao.adminserver.modules.bpm.service.task.BpmActivityService;
  6 +import cn.iocoder.yudao.adminserver.modules.bpm.service.task.BpmProcessInstanceService;
  7 +import cn.iocoder.yudao.adminserver.modules.bpm.service.task.BpmTaskService;
  8 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +import org.activiti.bpmn.model.BpmnModel;
  11 +import org.activiti.engine.HistoryService;
  12 +import org.activiti.engine.RepositoryService;
  13 +import org.activiti.engine.RuntimeService;
  14 +import org.activiti.engine.history.HistoricProcessInstance;
  15 +import org.activiti.engine.task.Task;
  16 +import org.activiti.image.ProcessDiagramGenerator;
  17 +import org.springframework.stereotype.Service;
  18 +import org.springframework.validation.annotation.Validated;
  19 +
  20 +import javax.annotation.Resource;
  21 +import java.io.InputStream;
  22 +import java.util.Collections;
  23 +import java.util.List;
  24 +
  25 +import static cn.iocoder.yudao.adminserver.modules.bpm.enums.BpmErrorCodeConstants.PROCESS_DEFINITION_BPMN_MODEL_NOT_EXISTS;
  26 +import static cn.iocoder.yudao.adminserver.modules.bpm.enums.BpmErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS;
  27 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  28 +
  29 +/**
  30 + * BPM 活动实例 Service 实现类
  31 + *
  32 + * @author 芋道源码
  33 + */
  34 +@Service
  35 +@Slf4j
  36 +@Validated
  37 +public class BpmActivityServiceImpl implements BpmActivityService {
  38 +
  39 + private static final String FONT_NAME = "宋体";
  40 +
  41 + @Resource
  42 + private ProcessDiagramGenerator processDiagramGenerator;
  43 +
  44 + @Resource
  45 + private BpmProcessInstanceService processInstanceService;
  46 + @Resource
  47 + private BpmProcessDefinitionService processDefinitionService;
  48 + @Resource
  49 + private BpmTaskService taskService;
  50 +
  51 + @Override
  52 + public byte[] generateHighlightDiagram(String processInstanceId) {
  53 + // 获得流程实例
  54 + HistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(processInstanceId);
  55 + if (processInstance == null) {
  56 + throw exception(PROCESS_INSTANCE_NOT_EXISTS);
  57 + }
  58 + // 获得流程定义的 BPMN 模型
  59 + BpmnModel bpmnModel = processDefinitionService.getBpmnModel(processInstance.getProcessDefinitionId());
  60 + if (bpmnModel == null) {
  61 + throw exception(PROCESS_DEFINITION_BPMN_MODEL_NOT_EXISTS);
  62 + }
  63 +
  64 + // 如果流程已经结束,则无进行中的任务,无法高亮
  65 + // 如果流程未结束,才需要高亮
  66 + List<String> highLightedActivities = Collections.emptyList();
  67 + if (processInstance.getEndTime() == null) {
  68 + List<Task> tasks = taskService.getTasksByProcessInstanceId(processInstanceId);
  69 + highLightedActivities = CollectionUtils.convertList(tasks, Task::getTaskDefinitionKey);
  70 + }
  71 +
  72 + // 生成高亮流程图
  73 + InputStream inputStream = processDiagramGenerator.generateDiagram(bpmnModel, highLightedActivities, Collections.emptyList(),
  74 + FONT_NAME, FONT_NAME, FONT_NAME);
  75 + return IoUtil.readBytes(inputStream);
  76 + }
  77 +
  78 +}
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/service/task/impl/BpmTaskServiceImpl.java
... ... @@ -54,7 +54,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
54 54 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
55 55  
56 56 /**
57   - * 流程任务 Service 实现类
  57 + * 流程任务实例 Service 实现类
58 58 *
59 59 * @author jason
60 60 * @author 芋道源码
... ... @@ -66,14 +66,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
66 66 @Resource
67 67 private TaskService taskService;
68 68 @Resource
69   - private RuntimeService runtimeService;
70   - @Resource
71 69 private HistoryService historyService;
72   - @Resource
73   - private RepositoryService repositoryService;
74   -
75   - @Resource
76   - private ProcessDiagramGenerator processDiagramGenerator;
77 70  
78 71 @Resource
79 72 private SysUserService userService;
... ... @@ -119,6 +112,14 @@ public class BpmTaskServiceImpl implements BpmTaskService {
119 112 }
120 113  
121 114 @Override
  115 + public List<Task> getTasksByProcessInstanceId(String processInstanceId) {
  116 + if (StrUtil.isEmpty(processInstanceId)) {
  117 + return Collections.emptyList();
  118 + }
  119 + return taskService.createTaskQuery().processInstanceId(processInstanceId).list();
  120 + }
  121 +
  122 + @Override
122 123 public List<Task> getTasksByProcessInstanceIds(List<String> processInstanceIds) {
123 124 if (CollUtil.isEmpty(processInstanceIds)) {
124 125 return Collections.emptyList();
... ... @@ -270,129 +271,6 @@ public class BpmTaskServiceImpl implements BpmTaskService {
270 271 // taskService.addComment(task.getId(), task.getProcessInstanceId(), reqVO.getComment());
271 272 }
272 273  
273   - @Override
274   - public FileResp getHighlightImg(String processInstanceId) {
275   - // 查询历史
276   - //TODO 云扬四海 貌似流程结束后,点击审批进度会报错
277   - // TODO @Li:一些 historyService 的查询,貌似比较通用,是不是抽一些小方法出来
278   - HistoricProcessInstance hpi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
279   - // 如果不存在实例。 说明数据异常
280   - if (hpi == null) {
281   -// throw exception(PROCESS_INSTANCE_NOT_EXISTS);
282   - throw new RuntimeException("不存在");
283   - }
284   - // 如果有结束时间 返回model的流程图
285   - if (!ObjectUtils.isEmpty(hpi.getEndTime())) {
286   - ProcessDefinition pd = repositoryService.createProcessDefinitionQuery().processDefinitionId(hpi.getProcessDefinitionId()).singleResult();
287   - String resourceName = Optional.ofNullable(pd.getDiagramResourceName()).orElse(pd.getResourceName());
288   - BpmnModel bpmnModel = repositoryService.getBpmnModel(pd.getId());
289   - InputStream inputStream = processDiagramGenerator.generateDiagram(bpmnModel, new ArrayList<>(1), new ArrayList<>(1),
290   - "宋体", "宋体", "宋体");
291   - FileResp fileResp = new FileResp();
292   - fileResp.setFileName( resourceName + ".svg");
293   - fileResp.setFileByte(IoUtil.readBytes(inputStream));
294   - return fileResp;
295   - }
296   - // 没有结束时间。说明流程在执行过程中
297   - // TODO @Li:一些 runtimeService 的查询,貌似比较通用,是不是抽一些小方法出来
298   - ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
299   -
300   - List<String> highLightedActivities = new ArrayList<>();
301   - // 获取所有活动节点
302   - List<HistoricActivityInstance> finishedInstances = historyService.createHistoricActivityInstanceQuery()
303   - .processInstanceId(processInstanceId).finished().list();
304   - finishedInstances.stream().map(HistoricActivityInstance::getActivityId)
305   - .forEach(highLightedActivities::add);
306   - // 已完成的节点+当前节点
307   - highLightedActivities.addAll(runtimeService.getActiveActivityIds(processInstanceId));
308   -
309   - BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId());
310   - // 经过的流
311   - List<String> highLightedFlowIds = getHighLightedFlows(bpmnModel, processInstanceId);
312   -
313   - //设置"宋体"
314   - try (InputStream inputStream = processDiagramGenerator.generateDiagram(bpmnModel, highLightedActivities, highLightedFlowIds,
315   - "宋体", "宋体", "宋体")){
316   - FileResp fileResp = new FileResp();
317   - fileResp.setFileName( hpi.getProcessDefinitionName() + ".svg");
318   - fileResp.setFileByte(IoUtil.readBytes(inputStream));
319   - return fileResp;
320   - } catch (IOException e) {
321   - log.error("[getHighlightImg][流程({}) 生成图表失败]", processInstanceId, e);
322   - throw exception(HIGHLIGHT_IMG_ERROR);
323   - }
324   - }
325   -
326   - // TODO @Li:这个方法的可读性还有一定的优化空间,可以思考下哈。
327   - /**
328   - * 获取指定 processInstanceId 已经高亮的Flows
329   - * 获取已经流转的线 参考: https://blog.csdn.net/qiuxinfa123/article/details/119579863
330   - * @param bpmnModel model
331   - * @param processInstanceId 流程实例Id
332   - * @return 获取已经流转的列表
333   - */
334   - private List<String> getHighLightedFlows(BpmnModel bpmnModel, String processInstanceId) {
335   - // 获取所有的线条
336   - List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId)
337   - .orderByHistoricActivityInstanceId().asc().list();
338   - // 高亮流程已发生流转的线id集合
339   - List<String> highLightedFlowIds = new ArrayList<>();
340   - // 全部活动节点
341   - List<FlowNode> historicActivityNodes = new ArrayList<>();
342   - // 已完成的历史活动节点
343   - List<HistoricActivityInstance> finishedActivityInstances = new ArrayList<>();
344   -
345   - for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
346   - FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true);
347   - historicActivityNodes.add(flowNode);
348   - // 结束时间不为空,则是已完成节点
349   - if (historicActivityInstance.getEndTime() != null) {
350   - finishedActivityInstances.add(historicActivityInstance);
351   - }
352   - }
353   - // 提取活动id 是唯一的。塞入Map
354   - Map<String, HistoricActivityInstance> historicActivityInstanceMap = CollectionUtils.convertMap(historicActivityInstances, HistoricActivityInstance::getActivityId);
355   - // 遍历已完成的活动实例,从每个实例的outgoingFlows中找到已执行的
356   - for (HistoricActivityInstance currentActivityInstance : finishedActivityInstances) {
357   - // 获得当前活动对应的节点信息及outgoingFlows信息
358   - FlowNode currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true);
359   - List<SequenceFlow> sequenceFlows = currentFlowNode.getOutgoingFlows();
360   -
361   - // 遍历outgoingFlows并找到已流转的 满足如下条件认为已已流转:
362   - // 1.当前节点是并行网关或兼容网关,则通过outgoingFlows能够在历史活动中找到的全部节点均为已流转
363   - // 2.当前节点是以上两种类型之外的,通过outgoingFlows查找到的时间最早的流转节点视为有效流转
364   - if (BpmnXMLConstants.ELEMENT_GATEWAY_PARALLEL.equals(currentActivityInstance.getActivityType())
365   - || BpmnXMLConstants.ELEMENT_GATEWAY_INCLUSIVE.equals(currentActivityInstance.getActivityType())) {
366   - // 遍历历史活动节点,找到匹配流程目标节点的
367   - for (SequenceFlow sequenceFlow : sequenceFlows) {
368   - FlowNode targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(sequenceFlow.getTargetRef(), true);
369   - if (historicActivityNodes.contains(targetFlowNode)) {
370   - highLightedFlowIds.add(targetFlowNode.getId());
371   - }
372   - }
373   - } else {
374   - long earliestStamp = 0L;
375   - String highLightedFlowId = null;
376   - // 循环流出的流
377   - for (SequenceFlow sequenceFlow : sequenceFlows) {
378   - HistoricActivityInstance historicActivityInstance = historicActivityInstanceMap.get(sequenceFlow.getTargetRef());
379   - if (historicActivityInstance == null) {
380   - continue;
381   - }
382   - final long startTime = historicActivityInstance.getStartTime().getTime();
383   - // 遍历匹配的集合,取得开始时间最早的一个
384   - if (earliestStamp == 0 || earliestStamp >= startTime) {
385   - highLightedFlowId = sequenceFlow.getId();
386   - earliestStamp = startTime;
387   - }
388   - }
389   - highLightedFlowIds.add(highLightedFlowId);
390   - }
391   -
392   - }
393   - return highLightedFlowIds;
394   - }
395   -
396 274 private Task getTask(String id) {
397 275 return taskService.createTaskQuery().taskId(id).singleResult();
398 276 }
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/service/task/package-info.java 0 → 100644
  1 +/**
  2 + * task 包下,存放的都是 xxx 实例。例如说:
  3 + * 1. ProcessInstance 是 ProcessDefinition 创建而来的实例;
  4 + * 2. TaskInstance 是 TaskDefinition 创建而来的实例;
  5 + * 3. ActivityInstance 是 BPMN 流程图的每个元素创建的实例;
  6 + *
  7 + * 考虑到 Task 和 Activity 可以比较明确表示名字,所以对应的 Service 就没有使用 Instance 后缀~
  8 + * 嘿嘿,其实也是实现到比较后面的阶段,所以就暂时没去统一和修改了~
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +package cn.iocoder.yudao.adminserver.modules.bpm.service.task;
... ...