bpmn-js显示Flowable的BpmnModel转换为xml文档的流程图排他网关丢失中间“X”

1 问题引入

这应该是一篇十分小众的文章,能找见这篇文章的同学,应该对审批流框架Flowable和bpmn-js都有所了解,所以对一些基础知识就不做过多介绍,直接说问题。

在利用ruoyi-vue-pro项目做审批流,通过流程定义id获取流程的xml文档,前端bpmn-js通过拿到的xml文档,显示对应的流程图,在显示流程图时,排他网关菱形中间的'X'丢失。其中flowable的版本是6.8.1,bpmn-js的版本是11.5.0。


1.1 引用flowable的Maven


     org.flowable
     flowable-spring-boot-starter-process
     6.8.1


     org.flowable
     flowable-spring-boot-starter-actuator
     6.8.1

1.2 后端生成xml核心代码

 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));
    }

1.3 后端获取model的xml代码

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;
    }

1.4 bpmn-js显示流程图的example



  
    
    Hello World
    

    
    

    
    

    
    
  
  
    

1.5 正常的流程图

1.5.1 通过1.3接口获取的是流程最新的模型图

1.5.2 对应的xml文档



  
    
      Flow_0454chc
    
    
      Flow_0454chc
      Flow_02g6tt4
    
    
    
      Flow_02g6tt4
      Flow_1wu0khg
      Flow_0rdv9v5
    
    
    
      Flow_1wu0khg
      Flow_1d0b1lk
    
    
    
      Flow_1d0b1lk
      Flow_0rdv9v5
    
    
    
      =${day>3}
    
  
  
    
      
        
      
      
        
        
      
      
        
      
      
        
        
      
      
        
      
      
        
        
      
      
        
        
      
      
        
        
      
      
        
        
      
      
        
        
        
        
      
    
  

1.5.3 正常的流程图

bpmn-js显示Flowable的BpmnModel转换为xml文档的流程图排他网关丢失中间“X”_第1张图片

1.6 丢失“X”的流程图

1.6.1 后端接口

1.6.2 对应xml文档



  
    
    
    
    
    
    
    
    
    
    
      3}]]>
    
  
  
    
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
        
      
      
        
        
      
      
        
        
      
      
        
        
      
      
        
        
        
        
      
    
  

1.6.3 显示的问题流程图

bpmn-js显示Flowable的BpmnModel转换为xml文档的流程图排他网关丢失中间“X”_第2张图片

从图中可以看到,排他网关中间的“X”丢失了!!! 

2 查找问题

查看问题,每天只能利用零碎的时间,前后两周,在这里首先介绍一个工具,bpmn流程设计器camunda-modeler,一款流程设计器,还是借用引言的话,能看到这篇文章的同学,基础知识应该都了解,我就不做过多的解释了。

前面失败的摸索过程就不多做赘述,直接说最后成功发现问题的过程。debug1.2节的代码,拿到xml,粘贴复制保存为xml文件,文件内容为1.6.2所示,利用1.4节的example,把最后的jQuery的get请求的URL替换为本地xml文件路径,浏览器直接打开1.4节的html文件,此时显示的流程图是有问题的流程图。

同样的xml文件,利用camunda-modeler显示,发现排他网关显示无任何问题

bpmn-js显示Flowable的BpmnModel转换为xml文档的流程图排他网关丢失中间“X”_第3张图片

我利用camunda-modeler调整了排他网关直接到结束连线的位置,然后保存此xml文件,然后刷新1.4节的html页面,发现排他网关能正常显示了。

保存后的xml文档



  
    
    
    
    
    
    
    
    
    
    
      =${day>3}
    
  
  
    
      
        
        
        
        
      
      
        
        
      
      
        
        
      
      
        
        
      
      
        
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
    
  

 都是xml文档,为什么一个排他网关能正常显示,一个排他网关不能正常显示了?此时要做的就是对比两个xml文档,找出关键差异所在,对比了很久,最后定位一个非常关键的地方,xml的BPMNDiagram元素中,对于排他网关的描述,排他网关非正常显示的xml少了“isMarkerVisible="true" ”这个关键的属性,对于原来有问题的xml,我直接在相应位置加上该属性,保存xml,html网页正常显示。

 

至此,排他网关菱形中间的"X"丢失问题应该算是找到了病根,接下来就是如何解决问题。

3 解决问题

第一个想法是,通过模型id查找,利用repositoryService.getModelEditorSource(id)接口获取xml流程图不就可以了吗?但是一个流程模型会有多个流程定义,每个流程定义版本可能流程图是不相同的,如果要查看每个流程版本的流程图,此方法是只能获取最新流程定义的版本图,后面使用时行不通的,所以必须通过流程定义id获取对应的BpmnModel,再利用BpmnXMLConverter进行转换。

 byte[] bpmnBytes = repositoryService.getModelEditorSource(id);

3.1 跟踪BpmnXMLConverter的convertToXML方法源码

跟踪convertToXML方法,找出生成BPMNDiagram的关键代码

bpmn-js显示Flowable的BpmnModel转换为xml文档的流程图排他网关丢失中间“X”_第4张图片

跟进此方法,进入 org.flowable.bpmn.converter.export包下的writeBPMNDI方法。

bpmn-js显示Flowable的BpmnModel转换为xml文档的流程图排他网关丢失中间“X”_第5张图片

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",对于排他网关,并没有任何特殊之处。

3.2 最后的解决之道

第一个想法就是继承类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();
    }

至此算解决了此问题!

4 总结

从遇到此问题,到找到问题的根源,花了很长的时间,毕竟也是第一次尝试,前端自己也是新手。找出问题到解决问题,其实花的时间并不多。到目前为止,问题算是解决了,但感觉解决这个问题的方式不够优雅,后续可能会在GitHub上提个issue问问作者这个问题,看看到底是代码的疏漏还是别的什么原因。

看到这篇文章的伙伴,有更好的解决方法,也欢迎一起讨论,不足之处,还请多多指教。

你可能感兴趣的:(spring,boot,flowable,bpmn-js,审批流)