这应该是一篇十分小众的文章,能找见这篇文章的同学,应该对审批流框架Flowable和bpmn-js都有所了解,所以对一些基础知识就不做过多介绍,直接说问题。
在利用ruoyi-vue-pro项目做审批流,通过流程定义id获取流程的xml文档,前端bpmn-js通过拿到的xml文档,显示对应的流程图,在显示流程图时,排他网关菱形中间的'X'丢失。其中flowable的版本是6.8.1,bpmn-js的版本是11.5.0。
org.flowable
flowable-spring-boot-starter-process
6.8.1
org.flowable
flowable-spring-boot-starter-actuator
6.8.1
public String getProcessDefinitionBpmnXML(String id) {
BpmnModel bpmnModel = repositoryService.getBpmnModel(id);
if (bpmnModel == null) {
return null;
}
BpmnXMLConverter converter = new BpmnXMLConverter();
return StrUtil.utf8Str(converter.convertToXML(bpmnModel));
}
public BpmModelRespVO getModel(String id) {
Model model = repositoryService.getModel(id);
if (model == null) {
return null;
}
BpmModelRespVO modelRespVO = BpmModelConvert.INSTANCE.convert(model);
// 拼接 bpmn XML
byte[] bpmnBytes = repositoryService.getModelEditorSource(id);
modelRespVO.setBpmnXml(StrUtil.utf8Str(bpmnBytes));
return modelRespVO;
}
Hello World
Flow_0454chc
Flow_0454chc
Flow_02g6tt4
Flow_02g6tt4
Flow_1wu0khg
Flow_0rdv9v5
Flow_1wu0khg
Flow_1d0b1lk
Flow_1d0b1lk
Flow_0rdv9v5
=${day>3}
3}]]>
从图中可以看到,排他网关中间的“X”丢失了!!!
查看问题,每天只能利用零碎的时间,前后两周,在这里首先介绍一个工具,bpmn流程设计器camunda-modeler,一款流程设计器,还是借用引言的话,能看到这篇文章的同学,基础知识应该都了解,我就不做过多的解释了。
前面失败的摸索过程就不多做赘述,直接说最后成功发现问题的过程。debug1.2节的代码,拿到xml,粘贴复制保存为xml文件,文件内容为1.6.2所示,利用1.4节的example,把最后的jQuery的get请求的URL替换为本地xml文件路径,浏览器直接打开1.4节的html文件,此时显示的流程图是有问题的流程图。
同样的xml文件,利用camunda-modeler显示,发现排他网关显示无任何问题
我利用camunda-modeler调整了排他网关直接到结束连线的位置,然后保存此xml文件,然后刷新1.4节的html页面,发现排他网关能正常显示了。
保存后的xml文档
=${day>3}
都是xml文档,为什么一个排他网关能正常显示,一个排他网关不能正常显示了?此时要做的就是对比两个xml文档,找出关键差异所在,对比了很久,最后定位一个非常关键的地方,xml的BPMNDiagram元素中,对于排他网关的描述,排他网关非正常显示的xml少了“isMarkerVisible="true" ”这个关键的属性,对于原来有问题的xml,我直接在相应位置加上该属性,保存xml,html网页正常显示。
至此,排他网关菱形中间的"X"丢失问题应该算是找到了病根,接下来就是如何解决问题。
第一个想法是,通过模型id查找,利用repositoryService.getModelEditorSource(id)接口获取xml流程图不就可以了吗?但是一个流程模型会有多个流程定义,每个流程定义版本可能流程图是不相同的,如果要查看每个流程版本的流程图,此方法是只能获取最新流程定义的版本图,后面使用时行不通的,所以必须通过流程定义id获取对应的BpmnModel,再利用BpmnXMLConverter进行转换。
byte[] bpmnBytes = repositoryService.getModelEditorSource(id);
跟踪convertToXML方法,找出生成BPMNDiagram的关键代码
跟进此方法,进入 org.flowable.bpmn.converter.export包下的writeBPMNDI方法。
writeBPMNDI调用了同一个类下面的 createBpmnShape方法,这个方法最关键,此方法生成的xml就包含了对于排他网关是否会添加isMarkerVisible="true"属性。
protected static void createBpmnShape(BpmnModel model, String elementId, XMLStreamWriter xtw) throws Exception {
xtw.writeStartElement(BPMNDI_PREFIX, ELEMENT_DI_SHAPE, BPMNDI_NAMESPACE);
xtw.writeAttribute(ATTRIBUTE_DI_BPMNELEMENT, elementId);
xtw.writeAttribute(ATTRIBUTE_ID, "BPMNShape_" + elementId);
GraphicInfo graphicInfo = model.getGraphicInfo(elementId);
FlowElement flowElement = model.getFlowElement(elementId);
if (flowElement instanceof SubProcess && graphicInfo.getExpanded() != null) {
xtw.writeAttribute(ATTRIBUTE_DI_IS_EXPANDED, String.valueOf(graphicInfo.getExpanded()));
}
xtw.writeStartElement(OMGDC_PREFIX, ELEMENT_DI_BOUNDS, OMGDC_NAMESPACE);
xtw.writeAttribute(ATTRIBUTE_DI_HEIGHT, String.valueOf(graphicInfo.getHeight()));
xtw.writeAttribute(ATTRIBUTE_DI_WIDTH, String.valueOf(graphicInfo.getWidth()));
xtw.writeAttribute(ATTRIBUTE_DI_X, String.valueOf(graphicInfo.getX()));
xtw.writeAttribute(ATTRIBUTE_DI_Y, String.valueOf(graphicInfo.getY()));
xtw.writeEndElement();
xtw.writeEndElement();
}
通过以上代码可以看出,对于子流程做了特殊判断,添加了新的属性ATTRIBUTE_DI_IS_EXPANDED = "isExpanded",对于排他网关,并没有任何特殊之处。
第一个想法就是继承类BPMNDIExport,重写方法createBpmnShape,但此方法是静态的,并不能重写,如果完全重写BpmnXMLConverter工作量太大了,放弃。
改造xml了?对于convertToXML的结果,进行解析,在相应位置添加上属性isMarkerVisible="true"是不是就可以了。说干就干!!!
说一说思路,首先解析xml,获取process下面的排他网关的id,此id是唯一的,接着解析xml,获取BPMNDiagram下面属性,拿到属性“bpmnElement”值为排他网关id的element,添加属性isMarkerVisible="true",返回前端显示。
思路有了,实现工具使用dom4j,Dom4j是一个易用的、开源的库,用于XML,XPath和XSLT。它应用于Java平台,采用了Java集合框架并完全支持DOM,SAX和JAXP。
Maven依赖如下
org.dom4j
dom4j
2.1.4
1.2节的通过流程定义获取xml的方法改造为
public String getProcessDefinitionBpmnXML(String id) {
try {
BpmnModel bpmnModel = repositoryService.getBpmnModel(id);
if (bpmnModel == null) {
return null;
}
BpmnXMLConverter converter = new BpmnXMLConverter();
//返回给前端的xml,排他网关的显示会丢失中间的X,经过探索是xml少了attribute isMarkerVisible="true",手动需要添加此属性
return addAttributeWithXmlDocument(StrUtil.utf8Str(converter.convertToXML(bpmnModel)));
}catch (Exception e){
throw exception(PROCESS_DEFINITION_IS_CHANGE_ERROR);
}
}
/**
* 给xml添加属性
* @param xmlStr 字符串格式的xml
* @return 添加属性后的xml
*/
private String addAttributeWithXmlDocument(String xmlStr) throws DocumentException {
// 加载文档
Document document = DocumentHelper.parseText(xmlStr);
//获取根节点
Element root = document.getRootElement();
//查找排他网关的id
Element process = root.elements("process").get(0);
Element exclusiveGateway = process.elements("exclusiveGateway").get(0);
String exclusiveGatewayId = exclusiveGateway.attribute("id").getValue();
//向下搜索
Element diagram = root.elements("BPMNDiagram").get(0);
Element BPMNPlane = diagram.elements("BPMNPlane").get(0);
for (Iterator i = BPMNPlane.elementIterator(); i.hasNext(); ) {
Element el = (Element) i.next();
if (el.attribute("bpmnElement").getValue().equals(exclusiveGatewayId)) {
//添加属性
el.addAttribute("isMarkerVisible", Boolean.toString(true));
}
}
return document.asXML();
}
至此算解决了此问题!
从遇到此问题,到找到问题的根源,花了很长的时间,毕竟也是第一次尝试,前端自己也是新手。找出问题到解决问题,其实花的时间并不多。到目前为止,问题算是解决了,但感觉解决这个问题的方式不够优雅,后续可能会在GitHub上提个issue问问作者这个问题,看看到底是代码的疏漏还是别的什么原因。
看到这篇文章的伙伴,有更好的解决方法,也欢迎一起讨论,不足之处,还请多多指教。