背景:

        由于项目的需要,当用户在查看流程图时,当点击某个流程图片上的节点时,需要提示一些信息,这就需要获取各个节点的信息,此处获取id和name的值。

         

注意:这个并不是流程图的高亮,即当点击网点申请环节时,获取该节点的id和name,即B001和网点申请,点击部门申请时,获取B002和部门经理审批

解释说明:

1.下方说的x和y:

    

2.流程定义的key: 即为下方id的值

3.节点的id和name的值:

难点分析:

      由 于activiti在部署时,如果没有流程图片,则activiti会自动生成一张图片,而我们项目中使用的是activiti modeler实现的在线画流程图,部署时没有图片,是由activiti自动生成。而activiti在生成图片时,会对图片做一个裁剪操作,所有最终 各个节点的坐标会比实际的要小。

(即:activiti自动生成的图片,会做一个裁剪操作,各个节点实际的x和y的值比xml文件中的要小)

        而我们的难点在于,各个节点实际坐标的获取。如下图所示:

      

即我们实际上获取的坐标需要在减去一个minX和minY,得到的才是我们的各个节点实际的坐标。

步骤分析:

      1.根据流程定义的key,重新生成流程图片,而不是获取流程图片。

2.还是根据流程定义的key,获取各个节点的信息。(此处需要注意的是各个节点实际的x和y的值的获取的方法

3.在jsp页面上使用绝对定位,给点击的节点加上高亮。

步骤实现:  1.根据流程定义的key,重新生成流程图片,而不是获取流程图片。

此处重新生成图片的原因:

因为有些时候我们在部署流程时,将图片也部署进去了,此时使用的就是自己的图片,activiti不会进行图片的裁剪。因为我在下一步获取流程节点的信息时,对x和y的进行了特殊处理,因此此处需要重新生成 流程图片。

/**
* 根据流程的key生成图片
*
* @param request
* @param response
* @param wfKey 流程定义的key
*/
@RequestMapping("/genericImageByWfKey")
public void genericImageByWfKey(HttpServletRequest request, HttpServletResponse response, String wfKey) {
Context.setProcessEngineConfiguration(processEngineConfiguration);
RepositoryService repositoryService = this.processEngine.getRepositoryService();
ProcessDefinition pd = repositoryService.createProcessDefinitionQuery().processDefinitionKey(wfKey).latestVersion().singleResult();
BpmnModel bm = repositoryService.getBpmnModel(pd.getId());
InputStream is = ProcessDiagramGenerator.generatePngDiagram(bm); // 生成图片,获取图片的输入流
try {
int size = is.available();
byte data[] = new byte[size];
is.read(data);
response.setContentType("image/png"); // 设置返回的文件类型
OutputStream os = response.getOutputStream();
os.write(data);
os.flush();
os.close();
} catch (IOException e) {
log.error("读写流程图时出现异常!");
}
log.info("end....");
}
InputStream is = ProcessDiagramGenerator.generatePngDiagram(bm); // 生成图片,获取图片的输入流
try {
int size = is.available();
byte data[] = new byte[size];
is.read(data);
response.setContentType("image/png"); // 设置返回的文件类型
OutputStream os = response.getOutputStream();
os.write(data);
os.flush();
os.close();
} catch (IOException e) {
log.error("读写流程图时出现异常!");
}
log.info("end....");
}

2.还是根据流程定义的key,获取各个节点的信息。

获取各个节点的坐标之前,我们先看一下activiti中是如果获取到最小的x和y的,然后是如何裁剪图片的

2.1获取节点包括线的最小x和最小y :

跟踪acticiti的源码可以发现,最小x和y的获取(org.activiti.engine.impl.bpmn.diagram.ProcessDiagramGenerator.initProcessDiagramCanvas(BpmnModel))

protected static ProcessDiagramCanvas initProcessDiagramCanvas(BpmnModel bpmnModel) {
// We need to calculate maximum values to know how big the image will be in its entirety
double minX = Double.MAX_VALUE;
double maxX = 0;
double minY = Double.MAX_VALUE;
double maxY = 0; for (Pool pool : bpmnModel.getPools()) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId());
minX = graphicInfo.getX();
maxX = graphicInfo.getX() + graphicInfo.getWidth();
minY = graphicInfo.getY();
maxY = graphicInfo.getY() + graphicInfo.getHeight();
} List<FlowNode> flowNodes = gatherAllFlowNodes(bpmnModel);
for (FlowNode flowNode : flowNodes) { GraphicInfo flowNodeGraphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); // width
if (flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth() > maxX) {
maxX = flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth();
}
if (flowNodeGraphicInfo.getX() < minX) {
minX = flowNodeGraphicInfo.getX();
}
// height
if (flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight() > maxY) {
maxY = flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight();
}
if (flowNodeGraphicInfo.getY() < minY) {
minY = flowNodeGraphicInfo.getY();
} for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) {
List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId());
for (GraphicInfo graphicInfo : graphicInfoList) {
// width
if (graphicInfo.getX() > maxX) {
maxX = graphicInfo.getX();
}
if (graphicInfo.getX() < minX) {
minX = graphicInfo.getX();
}
// height
if (graphicInfo.getY() > maxY) {
maxY = graphicInfo.getY();
}
if (graphicInfo.getY()< minY) {
minY = graphicInfo.getY();
}
}
}
} int nrOfLanes = 0;
for (Process process : bpmnModel.getProcesses()) {
for (Lane l : process.getLanes()) { nrOfLanes++; GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(l.getId());
// // width
if (graphicInfo.getX() + graphicInfo.getWidth() > maxX) {
maxX = graphicInfo.getX() + graphicInfo.getWidth();
}
if (graphicInfo.getX() < minX) {
minX = graphicInfo.getX();
}
// height
if (graphicInfo.getY() + graphicInfo.getHeight() > maxY) {
maxY = graphicInfo.getY() + graphicInfo.getHeight();
}
if (graphicInfo.getY() < minY) {
minY = graphicInfo.getY();
}
}
} // Special case, see http://jira.codehaus.org/browse/ACT-1431
if (flowNodes.size() == 0 && bpmnModel.getPools().size() == 0 && nrOfLanes == 0) {
// Nothing to show
minX = 0;
minY = 0;
} return new ProcessDiagramCanvas((int) maxX + 10,(int) maxY + 10, (int) minX, (int) minY);
}
new ProcessDiagramCanvas((int) maxX + 10,(int) maxY + 10, (int) minX, (int) minY);
}

2.2 图片的裁剪:

还是activiti的源码:(org.activiti.engine.impl.bpmn.diagram.ProcessDiagramCanvas.generateImage(String))

/**
* Generates an image of what currently is drawn on the canvas.
*
* Throws an {@link ActivitiException} when {@link #close()} is already
* called.
*/
public InputStream generateImage(String imageType) {
if (closed) {
throw new ActivitiException("ProcessDiagramGenerator already closed");
} ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
// Try to remove white space
minX = (minX <= 5) ? 5 : minX;
minY = (minY <= 5) ? 5 : minY;
BufferedImage imageToSerialize = processDiagram;
if (minX >= 0 && minY >= 0) {
// 此处的最小x和最小y减去了5
imageToSerialize = processDiagram.getSubimage(minX - 5, minY - 5, canvasWidth - minX + 5, canvasHeight - minY + 5); // 此处可以看到,activiti对图像做了裁剪的操作。
}
ImageIO.write(imageToSerialize, imageType, out);
} catch (IOException e) {
throw new ActivitiException("Error while generating process image", e);
} finally {
IoUtil.closeSilently(out);
}
return new ByteArrayInputStream(out.toByteArray());
}
// 此处的最小x和最小y减去了5
imageToSerialize = processDiagram.getSubimage(minX - 5, minY - 5, canvasWidth - minX + 5, canvasHeight - minY + 5); // 此处可以看到,activiti对图像做了裁剪的操作。
}
ImageIO.write(imageToSerialize, imageType, out);
} catch (IOException e) {
throw new ActivitiException("Error while generating process image", e);
} finally {
IoUtil.closeSilently(out);
}
return new ByteArrayInputStream(out.toByteArray());
}

2.3 我们自己的各个节点的信息获取

@RequestMapping("/getProcessTrace")
@ResponseBody
/**
* 获取各个节点的具体的信息
* @param wfKey
* 流程定义的key
* @return
*/
public List<Map<String, Object>> getProcessTrace(String wfKey) throws Exception {
List<Map<String, Object>> activityInfos = new ArrayList<Map<String, Object>>();
RepositoryService repositoryService = processEngine.getRepositoryService();
ProcessDefinition pd = repositoryService.createProcessDefinitionQuery().processDefinitionKey(wfKey).latestVersion().singleResult();
ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService).getDeployedProcessDefinition(pd.getId());
List<ActivityImpl> activitiList = processDefinition.getActivities();
InputStream xmlIs = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), processDefinition.getResourceName());
BpmnModel bm = new BpmnXMLConverter().convertToBpmnModel(new InputStreamSource(xmlIs), false, true); // 下方使用反射获取最小的x和y,仔细看就会发现调用的是上方2.1节的方法
Class<?> clazz = Class.forName("org.activiti.engine.impl.bpmn.diagram.ProcessDiagramGenerator");
Method method = clazz.getDeclaredMethod("initProcessDiagramCanvas", BpmnModel.class);
method.setAccessible(true);
ProcessDiagramCanvas pdc = (ProcessDiagramCanvas) method.invoke(clazz.newInstance(), bm); // 调用方法 clazz = Class.forName("org.activiti.engine.impl.bpmn.diagram.ProcessDiagramCanvas");
Field minXField = clazz.getDeclaredField("minX"); // 得到minX字段
Field minYField = clazz.getDeclaredField("minY");
minXField.setAccessible(true);
minYField.setAccessible(true);
int minX = minXField.getInt(pdc);// 最小的x值
int minY = minYField.getInt(pdc); // 最小的y的值

minX = minX > 0 ? minX - 5 : 0; // 此处为什么需要减5,上方2.2中activiti源码中有
minY = minY > 0 ? minY - 5 : 0;
for (ActivityImpl activity : activitiList) {
Map<String, Object> activityInfo = new HashMap<String, Object>();
activityInfo.put("width", activity.getWidth());
activityInfo.put("height", activity.getHeight());
activityInfo.put("x", activity.getX() - minX);
activityInfo.put("y", activity.getY() - minY);
activityInfo.put("actId", activity.getId());
activityInfo.put("name", activity.getProperty("name")); // ActivityImpl 中没有getName方法,所以此处需要这样获取。
activityInfos.add(activityInfo);
}
return activityInfos;
}
// 下方使用反射获取最小的x和y,仔细看就会发现调用的是上方2.1节的方法
Class<?> clazz = Class.forName("org.activiti.engine.impl.bpmn.diagram.ProcessDiagramGenerator");
Method method = clazz.getDeclaredMethod("initProcessDiagramCanvas", BpmnModel.class);
method.setAccessible(true);
ProcessDiagramCanvas pdc = (ProcessDiagramCanvas) method.invoke(clazz.newInstance(), bm); // 调用方法 clazz = Class.forName("org.activiti.engine.impl.bpmn.diagram.ProcessDiagramCanvas");
Field minXField = clazz.getDeclaredField("minX"); // 得到minX字段
Field minYField = clazz.getDeclaredField("minY");
minXField.setAccessible(true);
minYField.setAccessible(true);
int minX = minXField.getInt(pdc);// 最小的x值
int minY = minYField.getInt(pdc); // 最小的y的值

minX = minX > 0 ? minX - 5 : 0; // 此处为什么需要减5,上方2.2中activiti源码中有
minY = minY > 0 ? minY - 5 : 0;
for (ActivityImpl activity : activitiList) {
Map<String, Object> activityInfo = new HashMap<String, Object>();
activityInfo.put("width", activity.getWidth());
activityInfo.put("height", activity.getHeight());
activityInfo.put("x", activity.getX() - minX);
activityInfo.put("y", activity.getY() - minY);
activityInfo.put("actId", activity.getId());
activityInfo.put("name", activity.getProperty("name")); // ActivityImpl 中没有getName方法,所以此处需要这样获取。
activityInfos.add(activityInfo);
}
return activityInfos;
}

3.在jsp页面上使用绝对定位,给点击的节点加上高亮。

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<style type="text/css">
.activity-attr{border-radius: 10px; border: 3px solid red; transition:ease-out 0.5s; box-shadow:0px 0px 9px red;}
#processKey{color: red;}
#flowImageAndRect{position: relative;overflow: scroll;height:300px; heibackground-image: url('${ctx}/resources/images/workflow/grid_10.png')}
body,html{margin: 0px;padding:0px;}
</style>
<script type="text/javascript">
$(function(){
var wfKey = '${param.wfKey}'; // 流程定义的key
var $flowImageAndRect = $('#flowImageAndRect');
$('#processKey').html('流程定义的key --> ' + wfKey);
// 加载流程图片
loadProcessImage(wfKey,$flowImageAndRect);
// 加载各节点信息,最终实现,在点击图片上的各节点时,出现高亮
setTimeout(function(){
loadProcessTrace(wfKey,$flowImageAndRect);
},200); var $revClickRect = null; // 上次点击的图形
// 绑定click事件,点击实现,只有点击的不是同一个时,才显示红色的边框(如果多次点击同一个,红色的边框只出现一次)
$('#flowImageAndRect').off('click').on('click','.activity-attr',function(e){
var $this = $(this);
var prevFlag = false; // 是上一个图形,避免多次点击同一个
if($revClickRect){ // 说明不是第一次点击
prevFlag = ($revClickRect.attr('actId')!=$this.attr('actId')) ? false : true;// 说明2次点击的不是同一个
if(!prevFlag)
$revClickRect.css('opacity','0');
}
if(!prevFlag){ // 此处可以请求后台,加载相关的数据(多次点击同一个,下方可确保只执行一次)
$this.css('opacity','1'); // 显示当前的
$revClickRect = $this; // 将当前设置为上次点击的
$('#info').html('节点ID --> ' + $this.attr('actId') + " | " + "节点名称 --> " + $this.attr('name'));
}
});
}); /**
* 加载图片
*/
function loadProcessImage(wfKey,$flowImageAndRect){
var imageUrl = '${ctx}/workflow/monitor/genericImageByWfKey.do?wfKey='+wfKey;
// 加载图片
$('<img />',{
"src" : imageUrl,
"alt" : ''
}).appendTo($flowImageAndRect);
} /**
* 加载流程中各节点的信息
* @param wfKey : 流程定义的key
* @param $flowImageAndRect
*/
function loadProcessTrace(wfKey,$flowImageAndRect){
var traceUrl = '${ctx}/workflow/monitor/getProcessTrace.do?wfKey='+wfKey;
$.getJSON(traceUrl,function(infos){
var html = "";
$.each(infos,function(i,v){
// 矩形的div
var $div = $('<div/>', {
'class': 'activity-attr'
}).css({
position: 'absolute',
left: v.x,
top: v.y,
width: v.width - 3,
height:v.height - 3,
opacity: 0,
zIndex: 100,
cursor : 'pointer'
}).attr({'actId':v.actId,'name':v.name});
html += $div.prop("outerHTML");
});
$('<div />',{'id':'processRect'}).html(html).appendTo($flowImageAndRect);
});
}
</script>
</head>
<body>
<div id="main">
<div id="flowImageAndRect"> </div> <div id="processKey" style="font-size: 52px;text-align: center;margin-bottom: 50px;"> </div> <div id="info" style="font-size: 52px;text-align: center;"> </div>
</div>
</body>
</html>

到此,已经完成了。

activiti流程图上获取各节点的信息获取的更多相关文章

  1. 用python 获取照片的Exif 信息(获取拍摄设备,时间,地点等信息)

    第一步:先安装 pip install exifread 第二部:上代码 import exifread import requests class PhotoExifInfo(): def __in ...

  2. js获取dom节点之 id 获取

    在JavaScript中,标准的id选择器调用语法是: document.getElementById('myid').style.width = pc + "%"; 但是,今天发 ...

  3. JS获取子节点、父节点和兄弟节点的方法实例总结

    转自:https://www.jb51.net/article/143286.htm 本文实例讲述了JS获取子节点.父节点和兄弟节点的方法.分享给大家供大家参考,具体如下: 一.js获取子节点的方式 ...

  4. DA - 信息获取途径汇总

    目的驱动 大多数情况下,都是为了解决某个问题或完成某项任务,才需要进行针对性的.大范围的.细致化的信息获取. 那么,信息获取的方式和来源,就应该紧紧围绕这个"问题和任务"本身来确定 ...

  5. Info - 信息获取途径汇总

    目的驱动 大多数情况下,都是为了解决某个问题或完成某项任务,才需要进行针对性的.大范围的.细致化的信息获取. 那么,信息获取的方式和来源,就应该紧紧围绕这个"问题和任务"本身来确定 ...

  6. E1.获取Elixir/Erlang版本信息

    E1.获取Elixir/Erlang版本信息 获取Elixir版本 直接在shel中打开iex (interactive shell),就可以查到具体的版本信息: iex Erlang/OTP 22 ...

  7. activiti 配置节点 连线信息获取

    1.1.1. 前言 当使用eclipse插件进行流程设计的时候,部署流程之后,我们如何获取我们定义的所有的节点.连线.关口等配置信息呢?有的人看到这个需求,不免窃喜,这不很简单嘛,重新打来bmpn中定 ...

  8. 通过DOM节点操作来获取表单信息

    这是之前突发奇想地用dom节点的关系来操作表单的故事.. 事情的经过是这样的,大概就是一个平台注册后有留言功能,管理员登录之后可以对这些留言进行回复.这个页面呢,就是通过foreach获取到数据库里的 ...

  9. activiti官网实例项目activiti-explorer之获取流程节点

    如上图在保存步骤中添加获取节点信息方法nodes(); 方法如下: //获取所有节点    JsonNode modelNode = new ObjectMapper().readTree(repos ...

随机推荐

  1. python3 用multiprocessing模块传递多个参数

    from datetime import datetime from time import sleep import numpy as np import multiprocessing # fro ...

  2. k8s核心资源之Pod概念&入门使用讲解(三)

    目录 1. k8s核心资源之Pod 1.1 什么是Pod? 1.2 Pod如何管理多个容器? 1.3 Pod网络 1.4 Pod存储 1.5 Pod工作方式 1.5.1 自主式Pod 1.5.2 控制 ...

  3. golang指针接收者和值接收者方法调用笔记

    初学go时很多同学会把 值接收者 和 指针接收者 的方法相互调用搞混淆,好多同学都只记得指针类型可以调用值接收者方法和指针接收者方法,而值类型只能调用值接收者方法,其实不然,在某些情况下,值类型也是可 ...

  4. js根据日期获取所在周

    一.获取时间所在周的周一.周五 function getFirstLastDay (time) { let date = new Date(time) let Time = date.getTime( ...

  5. python 小鸡飞行小游戏

    python 小鸡飞行小游戏 用空格键控制小鸡飞行 代码 import pygame.freetype import sys import random pygame.init() screen = ...

  6. Jmeter系列(13)- 数据库操作之JDBC Connection Configuration配置元件、JDBC Request取样器

    Jmeter常见操作数据库场景 准备.制造测试数据 获取.查询测试数据 数据库数据作为参数引用 清理测试环境.删除过程数据 数据库压测 Jmeter操作数据库环境准备 已经安装好的数据库,比如MySq ...

  7. php环境 安装

    php 相关 wget https://www.php.net/distributions/php-7.2.16.tar.gz //你可以官网获取最新的包        tar解压    //一般目录 ...

  8. javascript 分时函数 分批次添加DOM节点 timeChunk

    创建1000个webqq的qq好友列表, 一个好友用一个节点来表示 * timeChunk var timeChunk = function(a, fn, sz, done) { var obj, t ...

  9. CI框架 模糊查询,链表查询

    $data = $this->db->from('flash_news') ->select('xx,xx,xx,xx') ->limit(2) ->like('tags ...

  10. redis 与java的连接 和集群环境下Session管理

    redis 的安装与设置开机自启(https://www.cnblogs.com/zhulina-917/p/11746993.html)  第一步: a) 搭建环境 引入 jedis jar包 co ...