一、流程图
比较简单,直接上代码
@GetMapping("resource-view")
public void resourceView(@RequestParam String processDefinitionId, String processInstanceId, HttpServletResponse response) throws Exception {
// 获取流程图
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
ProcessEngineConfiguration engConf = processEngine.getProcessEngineConfiguration();
ProcessDiagramGenerator diagramGenerator = new CustomProcessDiagramGenerator();
InputStream in = diagramGenerator.generateDiagram(bpmnModel, "bmp", Collections.EMPTY_LIST, Collections.EMPTY_LIST, engConf.getActivityFontName(),
engConf.getLabelFontName(), engConf.getAnnotationFontName(), engConf.getClassLoader(), 1.0, true);
OutputStream out = null;
byte[] buf = new byte[1024];
int length;
try {
out = response.getOutputStream();
while ((length = in.read(buf)) != -1) {
out.write(buf, 0, length);
}
} catch (IOException e) {
log.error("操作异常", e);
} finally {
IoUtil.closeSilently(out);
IoUtil.closeSilently(in);
}
}
二、流程跟踪
流程跟踪相对复杂一些,需要高亮显示流程已经经过的节点,将当前节点标识为绿色,并且鼠标移至节点上要显示节点信息,我们先看生成高亮流程图。
1、高亮流程图
@GetMapping(value = "diagram-view")
public void diagramView(String processInstanceId, HttpServletResponse httpServletResponse) {
// 获得当前活动的节点
String processDefinitionId;
// 如果流程已经结束,则得到结束节点
if (this.isFinished(processInstanceId)) {
HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
processDefinitionId = pi.getProcessDefinitionId();
} else {
// 如果流程没有结束,则取当前活动节点
// 根据流程实例ID获得当前处于活动状态的ActivityId合集
ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
processDefinitionId = pi.getProcessDefinitionId();
}
List<String> highLightedActivities = new ArrayList<>();
// 获得活动的节点
List<HistoricActivityInstance> highLightedActivityList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();
List<String> highLightedFlows = new ArrayList<>();
for (HistoricActivityInstance tempActivity : highLightedActivityList) {
String activityId = tempActivity.getActivityId();
highLightedActivities.add(activityId);
if("sequenceFlow".equals(tempActivity.getActivityType())){
highLightedFlows.add(tempActivity.getActivityId());
}
}
// 获取流程图
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
ProcessEngineConfiguration engConf = processEngine.getProcessEngineConfiguration();
ProcessDiagramGenerator diagramGenerator = engConf.getProcessDiagramGenerator();
// ProcessDiagramGenerator diagramGenerator = new CustomProcessDiagramGenerator();
InputStream in = diagramGenerator.generateDiagram(bpmnModel, "bmp", highLightedActivities, highLightedFlows, engConf.getActivityFontName(),
engConf.getLabelFontName(), engConf.getAnnotationFontName(), engConf.getClassLoader(), 1.0, true);
OutputStream out = null;
byte[] buf = new byte[1024];
int length;
try {
out = httpServletResponse.getOutputStream();
while ((length = in.read(buf)) != -1) {
out.write(buf, 0, length);
}
} catch (IOException e) {
log.error("操作异常", e);
} finally {
IoUtil.closeSilently(out);
IoUtil.closeSilently(in);
}
}
注意看这里,如果这里用engConf.getProcessDiagramGenerator()获取Flowable默认的流程图生成器,生成的流程图是这个样子
ProcessDiagramGenerator diagramGenerator = engConf.getProcessDiagramGenerator();
这里我们需要创建两个类去继承DefaultProcessDiagramCanvas和DefaultProcessDiagramGenerator。
CustomProcessDiagramCanvas中增加一个方法drawActiveHighLight,用来生成当前节点,也可以写其他方法去实现自己想要的效果,如开始节点默认生成的是方形的,可以改成生成一个圆形效果。
CustomProcessDiagramGenerator中重写drawActivity方法,
// Draw highlighted activities
if (highLightedActivities.contains(flowNode.getId())) {
if (StringUtil.containsAny(flowNode.getId(), "startEvent", "endEvent")
|| StringUtil.equals(flowNode.getName(), "开始")
|| StringUtil.equals(flowNode.getName(), "结束")) {
// 开始结束节点设置为圆形
((CustomProcessDiagramCanvas)processDiagramCanvas).drawStartOrEndEventHighLight((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight());
} else if(highLightedActivities.get(highLightedActivities.size()-1).equals(flowNode.getId())){
// 这里判断是当前节点,设置为绿色高亮
drawActiveHighLight((CustomProcessDiagramCanvas)processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()));
}else{
drawHighLight(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()));
}
}
2、显示节点信息
实现思路:在显示出高亮流程图后,请求后台获取流程节点坐标及节点信息,前端监听流程图的mousemove事件,通过弹窗位置、节点坐标和鼠标位置计算判断鼠标在哪个节点上,然后显示出对应的信息。
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
List<Process> processes = bpmnModel.getProcesses();
List<UserTask> datas = new ArrayList<>();
processes.forEach(process -> {
List<UserTask> userTasks = process.findFlowElementsOfType(UserTask.class);
datas.addAll(userTasks);
});
List<ActivityVo> list = new ArrayList<>();
List<HistoricTaskInstance> taskInstances = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId).list();
List<Task> taskList = taskService.createTaskQuery().processInstanceId(processInstanceId).list();
datas.forEach(userTask -> {
ActivityVo vo = new ActivityVo();
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(userTask.getId());
vo.setX(graphicInfo.getX());
vo.setY(graphicInfo.getY());
vo.setWidth(graphicInfo.getWidth());
vo.setHeight(graphicInfo.getHeight());
List<String> taskCandidates = getTaskCandidate(userTask);
vo.setTaskCandidates(StringUtil.join(taskCandidates));
vo.setAssigneeName(getTaskAssigneeName(userTask.getAssignee()));
List<BladeFlow> flows = new ArrayList<>();
vo.setFlows(flows);
taskInstances.forEach(task -> {
if (StringUtil.equals(task.getTaskDefinitionKey(), userTask.getId())) {
BladeFlow flow = new BladeFlow();
flow.setStatus("已结束");
taskList.forEach(runtimeTask -> {
if (StringUtil.equals(runtimeTask.getId(), task.getId())) {
flow.setStatus(runtimeTask.isSuspended() ? "挂起" : "处理中");
}
});
flow.setCreateTime(task.getCreateTime());
flow.setEndTime(task.getEndTime());
String durationTime = DateUtil.secondToTime(Func.toLong(task.getDurationInMillis(), 0L) / 1000);
flow.setHistoryActivityDurationTime(durationTime);
User assigneeUser = UserCache.getUserByTaskUser(task.getAssignee());
flow.setAssignee(task.getAssignee());
flow.setAssigneeName(Func.isNull(assigneeUser) ? "" : assigneeUser.getRealName());
flows.add(flow);
}
});
list.add(vo);
});
$('img', layero).attr('src', 'api/blade-flow/process/diagram-view?processInstanceId=' + that.options.processInstanceId);
$('img', layero).on('mousemove', function(e){
let x = e.offsetX;
let y = e.offsetY;
showTaskInfo(x, y);
})
function showTaskInfo(x, y){
let taskInfoWidth = 250;
let dialogTop = parseInt($(dialogContainer).css('top').replace('px', ''));
let dialogLeft = parseInt($(dialogContainer).css('left').replace('px', ''));
let titleHeight = $('.layui-layer-title', dialogContainer).height();
let imageX = dialogLeft;
let imageY = dialogTop + titleHeight;
$.each(userTaskData, function(index, item){
if (x > item.x
&& y > item.y
&& (x < item.x + item.width)
&& (y < item.y + item.height)
) {
// 进到这里说明已经在这个节点上了,可以显示信息了
if ($('.task-info-container[taskId="' + item.taskId + '"]', dialogContainer).length > 0){
// 进到这里说明已经显示信息了,不用再显示了
return;
}
// 计算位置
let top = imageY + item.y + item.height + 10;
let left = imageX + item.x + (item.width / 2) - (taskInfoWidth / 2);
item.top = top + 'px';
item.left = left + 'px';
item.triangleTop = (top - 10) + 'px';
item.triangleLeft = (left + (taskInfoWidth / 2) - 10) + 'px';
laytpl($('#task-info-tpl').html()).render(item, function (html) {
$('img', dialogContainer).after(html);
})
} else {
// 进到这里说明鼠标已经移开了
$('.task-info-container[taskId="' + item.taskId + '"]', dialogContainer).remove();
}
})
}