!54 返回高亮任务流图片

Merge pull request !54 from 云扬四海/feature/activiti
This commit is contained in:
芋道源码 2021-11-05 13:31:57 +00:00 committed by Gitee
commit 30732428f1
8 changed files with 204 additions and 6 deletions

View File

@ -9,6 +9,7 @@ import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.List; import java.util.List;
@ -57,4 +58,15 @@ public class TaskController {
return success(taskService.getHistorySteps(processInstanceId)); return success(taskService.getHistorySteps(processInstanceId));
} }
/**
* 返回高亮的流转图SVG
* @param processInstanceId
*/
@GetMapping("/process/highlight-img/{id}")
public void getHighlightImg(@PathVariable("id") String processInstanceId, HttpServletResponse response) {
taskService.getHighlightImg(processInstanceId, response);
}
} }

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.adminserver.modules.activiti.service.workflow;
import cn.iocoder.yudao.adminserver.modules.activiti.controller.workflow.vo.*; import cn.iocoder.yudao.adminserver.modules.activiti.controller.workflow.vo.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import javax.servlet.http.HttpServletResponse;
import java.util.List; import java.util.List;
// TODO @芋艿前缀注释 // TODO @芋艿前缀注释
@ -23,4 +24,11 @@ public interface TaskService {
TodoTaskRespVO getTaskFormKey(TaskQueryReqVO taskQuery); TodoTaskRespVO getTaskFormKey(TaskQueryReqVO taskQuery);
/**
* 返回高亮的流转进程
* @param processInstanceId 实例Id
* @param response 响应
*/
void getHighlightImg(String processInstanceId, HttpServletResponse response);
} }

View File

@ -5,27 +5,46 @@ import cn.iocoder.yudao.adminserver.modules.activiti.controller.workflow.vo.*;
import cn.iocoder.yudao.adminserver.modules.activiti.convert.workflow.TaskConvert; import cn.iocoder.yudao.adminserver.modules.activiti.convert.workflow.TaskConvert;
import cn.iocoder.yudao.adminserver.modules.activiti.service.workflow.TaskService; import cn.iocoder.yudao.adminserver.modules.activiti.service.workflow.TaskService;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import com.google.common.collect.ImmutableMap;
import lombok.extern.slf4j.Slf4j;
import org.activiti.api.runtime.shared.query.Page; import org.activiti.api.runtime.shared.query.Page;
import org.activiti.api.runtime.shared.query.Pageable; import org.activiti.api.runtime.shared.query.Pageable;
import org.activiti.api.task.model.Task; import org.activiti.api.task.model.Task;
import org.activiti.api.task.model.builders.ClaimTaskPayloadBuilder; import org.activiti.api.task.model.builders.ClaimTaskPayloadBuilder;
import org.activiti.api.task.model.builders.TaskPayloadBuilder; import org.activiti.api.task.model.builders.TaskPayloadBuilder;
import org.activiti.api.task.runtime.TaskRuntime; import org.activiti.api.task.runtime.TaskRuntime;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.engine.HistoryService; import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService; import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.history.HistoricActivityInstance; import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.repository.ProcessDefinition; import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Comment; import org.activiti.engine.task.Comment;
import org.activiti.image.ProcessDiagramGenerator;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.ArrayList; import javax.imageio.ImageIO;
import java.util.List; import javax.servlet.http.HttpServletResponse;
import java.util.Optional; import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j
@Service @Service
public class TaskServiceImpl implements TaskService { public class TaskServiceImpl implements TaskService {
@ -41,6 +60,12 @@ public class TaskServiceImpl implements TaskService {
@Resource @Resource
private RepositoryService repositoryService; private RepositoryService repositoryService;
@Resource
private RuntimeService runtimeService;
@Resource
private ProcessDiagramGenerator processDiagramGenerator;
@Override @Override
public PageResult<TodoTaskRespVO> getTodoTaskPage(TodoTaskPageReqVO pageReqVO) { public PageResult<TodoTaskRespVO> getTodoTaskPage(TodoTaskPageReqVO pageReqVO) {
// TODO @jason封装一个方法用于转换成 activiti 的分页对象 // TODO @jason封装一个方法用于转换成 activiti 的分页对象
@ -201,4 +226,131 @@ public class TaskServiceImpl implements TaskService {
// return highLightedFlows; // return highLightedFlows;
// } // }
@Override
public void getHighlightImg(String processInstanceId, HttpServletResponse response) {
// 查询历史
HistoricProcessInstance hpi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
// 如果有结束时间
if (hpi == null) {
return;
}
// 没有结束时间说明流程在执行过程中
ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId());
List<String> highLightedActivities = new ArrayList<>();
List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId)
.orderByHistoricActivityInstanceId().asc().list();
// 获取所有活动节点
List<HistoricActivityInstance> finishedInstances = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId).finished().list();
for (HistoricActivityInstance hai : finishedInstances) {
highLightedActivities.add(hai.getActivityId());
}
// 已完成的节点+当前节点
highLightedActivities.addAll(runtimeService.getActiveActivityIds(processInstanceId));
// 经过的流
List<String> highLightedFlowIds = getHighLightedFlows(bpmnModel, historicActivityInstances);
//设置"宋体"
try (InputStream inputStream = processDiagramGenerator.generateDiagram(bpmnModel, highLightedActivities, highLightedFlowIds,
"宋体", "宋体", "宋体")){
String picName = hpi.getProcessDefinitionName()+".svg";
// 输出到浏览器
responseImage(response, inputStream, picName);
} catch (IOException e) {
log.error(ExceptionUtils.getStackTrace(e));
}
}
private void responseImage(HttpServletResponse response, InputStream inputStream, String picName) throws IOException {
response.setContentType("application/octet-stream;charset=UTF-8");
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(picName, "UTF-8"));
byte[] b = new byte[1024];
int len = -1;
while ((len = inputStream.read(b, 0, 1024)) != -1) {
response.getOutputStream().write(b, 0, len);
}
response.flushBuffer();
}
/**
* 获取已经流转的线
* @see https://blog.csdn.net/qiuxinfa123/article/details/119579863
* @param bpmnModel model
* @param historicActivityInstances 高亮线条
* @return
*/
private List<String> getHighLightedFlows(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances) {
// 高亮流程已发生流转的线id集合
List<String> highLightedFlowIds = new ArrayList<>();
// 全部活动节点
List<FlowNode> historicActivityNodes = new ArrayList<>();
// 已完成的历史活动节点
List<HistoricActivityInstance> finishedActivityInstances = new ArrayList<>();
for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true);
historicActivityNodes.add(flowNode);
// 结束时间不为空则是已完成节点
if (historicActivityInstance.getEndTime() != null) {
finishedActivityInstances.add(historicActivityInstance);
}
}
FlowNode currentFlowNode;
FlowNode targetFlowNode;
// 遍历已完成的活动实例从每个实例的outgoingFlows中找到已执行的
for (HistoricActivityInstance currentActivityInstance : finishedActivityInstances) {
// 获得当前活动对应的节点信息及outgoingFlows信息
currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true);
List<SequenceFlow> sequenceFlows = currentFlowNode.getOutgoingFlows();
/**
* 遍历outgoingFlows并找到已流转的 满足如下条件认为已已流转
* 1.当前节点是并行网关或兼容网关则通过outgoingFlows能够在历史活动中找到的全部节点均为已流转
* 2.当前节点是以上两种类型之外的通过outgoingFlows查找到的时间最早的流转节点视为有效流转
*/
if ("parallelGateway".equals(currentActivityInstance.getActivityType()) || "inclusiveGateway".equals(currentActivityInstance.getActivityType())) {
// 遍历历史活动节点找到匹配流程目标节点的
for (SequenceFlow sequenceFlow : sequenceFlows) {
targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(sequenceFlow.getTargetRef(), true);
if (historicActivityNodes.contains(targetFlowNode)) {
highLightedFlowIds.add(targetFlowNode.getId());
}
}
} else {
List<Map<String, Object>> tempMapList = new ArrayList<>();
for (SequenceFlow sequenceFlow : sequenceFlows) {
for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
Map<String, Object> map = new HashMap<>();
map.put("highLightedFlowId", sequenceFlow.getId());
map.put("highLightedFlowStartTime", historicActivityInstance.getStartTime().getTime());
tempMapList.add(map);
}
}
}
if (!CollectionUtils.isEmpty(tempMapList)) {
// 遍历匹配的集合取得开始时间最早的一个
long earliestStamp = 0L;
String highLightedFlowId = null;
for (Map<String, Object> map : tempMapList) {
long highLightedFlowStartTime = Long.valueOf(map.get("highLightedFlowStartTime").toString());
if (earliestStamp == 0 || earliestStamp >= highLightedFlowStartTime) {
highLightedFlowId = map.get("highLightedFlowId").toString();
earliestStamp = highLightedFlowStartTime;
}
}
highLightedFlowIds.add(highLightedFlowId);
}
}
}
return highLightedFlowIds;
}
} }

View File

@ -81,3 +81,11 @@ export function processHistorySteps(id) {
method: 'get' method: 'get'
}) })
} }
export function getHighlightImg(id) {
return request({
url: '/workflow/task/process/highlight-img/'+id,
method: 'get'
})
}

View File

@ -137,11 +137,12 @@
</div> </div>
</el-dialog> </el-dialog>
<el-dialog :title="title" :visible.sync="dialogStepsVisible" width="600px" append-to-body> <el-dialog :title="title" :visible.sync="dialogStepsVisible" width="750px" append-to-body>
<el-steps :active="stepActive" finish-status="success" > <el-steps :active="stepActive" finish-status="success" >
<el-step :title="stepTitle(item)" :description="stepAssignee(item.assignee)" icon="el-icon-edit" v-for="(item) in handleTask.historyTask"></el-step> <el-step :title="stepTitle(item)" :description="stepAssignee(item.assignee)" icon="el-icon-edit" v-for="(item) in handleTask.historyTask"></el-step>
</el-steps> </el-steps>
<br/> <br/>
<div v-html="svgUrl"></div>
<el-steps direction="vertical" :active="stepActive"> <el-steps direction="vertical" :active="stepActive">
<el-step :title="stepTitle(item)" :description="stepDes(item)" v-for="(item) in handleTask.historyTask"></el-step> <el-step :title="stepTitle(item)" :description="stepDes(item)" v-for="(item) in handleTask.historyTask"></el-step>
</el-steps> </el-steps>
@ -156,7 +157,7 @@
<script> <script>
import { createLeave, updateLeave, deleteLeave, getLeave, getLeavePage, exportLeaveExcel } from "@/api/oa/leave" import { createLeave, updateLeave, deleteLeave, getLeave, getLeavePage, exportLeaveExcel } from "@/api/oa/leave"
import { getDictDataLabel, getDictDatas, DICT_TYPE } from '@/utils/dict' import { getDictDataLabel, getDictDatas, DICT_TYPE } from '@/utils/dict'
import { processHistorySteps } from '@/api/oa/todo' import { processHistorySteps,getHighlightImg } from '@/api/oa/todo'
export default { export default {
name: "Leave", name: "Leave",
components: { components: {
@ -194,6 +195,7 @@ export default {
}, },
// //
form: {}, form: {},
svgUrl: "",
handleTask: { handleTask: {
historyTask:[], historyTask:[],
taskVariable: "", taskVariable: "",
@ -361,6 +363,9 @@ export default {
this.handleTask.historyTask = response.data; this.handleTask.historyTask = response.data;
this.dialogStepsVisible = true this.dialogStepsVisible = true
this.title = "审批进度"; this.title = "审批进度";
getHighlightImg(row.processInstanceId).then(response => {
that.svgUrl = response
})
}); });
}, },
/** 导出按钮操作 */ /** 导出按钮操作 */

View File

@ -66,6 +66,11 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-image-generator</artifactId>
<version>${activiti.version}</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -1,9 +1,12 @@
package cn.iocoder.yudao.framework.activiti.config; package cn.iocoder.yudao.framework.activiti.config;
import org.activiti.api.runtime.shared.identity.UserGroupManager; import org.activiti.api.runtime.shared.identity.UserGroupManager;
import org.activiti.image.ProcessDiagramGenerator;
import org.activiti.image.impl.DefaultProcessDiagramGenerator;
import org.activiti.spring.SpringProcessEngineConfiguration; import org.activiti.spring.SpringProcessEngineConfiguration;
import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer; import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer;
import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -12,7 +15,10 @@ public class YudaoActivitiConfiguration {
@Bean
public ProcessDiagramGenerator processDiagramGenerator (){
return new DefaultProcessDiagramGenerator();
}
@Component @Component
public static class SqlSessionFactoryProcessEngineConfigurationConfigurer public static class SqlSessionFactoryProcessEngineConfigurationConfigurer
implements ProcessEngineConfigurationConfigurer { implements ProcessEngineConfigurationConfigurer {

View File

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.yudao.framework.activiti.config.YudaoActivitiConfiguration