应用开发平台集成工作流——流程建模功能模型转换框架设计与实现

背景

对于流程设置不友好的问题,国内钉钉另行设计与实现了一套流程建模模式,跟bpmn规范无关,有人仿照实现了下,并做了开源(https://github.com/StavinLi/Workflow-Vue3),效果图如下:

实现大致原理是基于无限嵌套的子节点,输出json数据,传给后端,后端进行解析后,调用Camunda引擎的api,转换成流程模型后持久化。

上篇介绍了关键技术点模型转换技术方案,今天重点来说如何实现。

流程节点类设计与实现

前端开源项目的建模,数据结构是一个无限嵌套的json对象,每一层都是一个节点实例,如下:

"nodeName": "发起人",//节点名称
"type": 0,// 0 发起人 1审批 2抄送 3条件 4路由
"priorityLevel": "",// 条件优先级
"settype": "",// 审批人设置 1指定成员 2主管 4发起人自选 5发起人自己 7连续多级主管
"selectMode": "", //审批人数 1选一个人 2选多个人
"selectRange": "", //选择范围 1.全公司 2指定成员 2指定角色
"directorLevel": "", //审批终点  最高层主管数
"examineMode": "", //多人审批时采用的审批方式 1依次审批 2会签
"noHanderAction": "",//审批人为空时 1自动审批通过/不允许发起 2转交给审核管理员
"examineEndDirectorLevel": "", //审批终点 第n层主管
"ccSelfSelectFlag": "", //允许发起人自选抄送人
"conditionList": [], //当审批单同时满足以下条件时进入此流程
"nodeUserList": [], //操作人
"childNode":{
    
    } //子节点

传给后端是一个json字符串,使用FastJson解析,映射到对象,方便后面读取属性等处理,因此新建一个名为FlowNode的类,如下:

package tech.abc.platform.workflow.entity;

import lombok.Data;

/**
 * 流程节点
 *
 * @author wqliu
 * @date 2023-07-12
 */
@Data
public class FlowNode {
    
    

    /**
     * 名称
     */
    private String nodeName;


    /**
     * 子节点
     */
    private FlowNode childNode;
}

节点属性暂时先设置两个核心属性,一个是nodeName,节点名称,另外一个是childNode,子节点。这里之所以没有一次性把所有属性都设置上去,是因为想通过迭代方式,逐步完善功能,不会全盘使用现有的数据定义,会根据实际需求增删及重命名,以及必要时调整数据结构。

开发测试类设计与实现

将json转换为模型,不是一件简单的事情,需要大量的调试与测试。如直接内嵌于服务类中,则每次调试都要加载SpringBoot,效率低,耗时长。
因此在模块workflow中建了一个测试类,将转换的功能代码先直接写到测试类中,快速启动与验证。待转换功能开发完成或大部分完成后,再迁移回服务类中进行集成。

package tech.abc.platform.workflow.service.impl;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import tech.abc.platform.workflow.entity.FlowNode;

/**
 * @author wqliu
 * @date 2023-07-12
 */
@Slf4j
class FlowTemplateServiceImplTest {
    
    

    @Test
    void convertJsonToModel() {
    
    
        String json="{\n" +
                "    \"nodeName\": \"发起人\",\n" +
                "    \"type\": 0,\n" +
                "    \"priorityLevel\": \"\",\n" +
                "    \"settype\": \"\",\n" +
                "    \"selectMode\": \"\",\n" +
                "    \"selectRange\": \"\",\n" +
                "    \"directorLevel\": \"\",\n" +
                "    \"examineMode\": \"\",\n" +
                "    \"noHanderAction\": \"\",\n" +
                "    \"examineEndDirectorLevel\": \"\",\n" +
                "    \"ccSelfSelectFlag\": \"\",\n" +
                "    \"conditionList\": [\n" +
                "        \n" +
                "    ],\n" +
                "    \"nodeUserList\": [\n" +
                "        \n" +
                "    ],\n" +
                "    \"childNode\": {\n" +
                "        \"nodeName\": \"审核人\",\n" +
                "        \"error\": false,\n" +
                "        \"type\": 1,\n" +
                "        \"settype\": 2,\n" +
                "        \"selectMode\": 0,\n" +
                "        \"selectRange\": 0,\n" +
                "        \"directorLevel\": 1,\n" +
                "        \"examineMode\": 1,\n" +
                "        \"noHanderAction\": 2,\n" +
                "        \"examineEndDirectorLevel\": 0,\n" +
                "        \"childNode\": {\n" +
                "            \"nodeName\": \"抄送人\",\n" +
                "            \"type\": 2,\n" +
                "            \"ccSelfSelectFlag\": 1,\n" +
                "            \"childNode\": null,\n" +
                "            \"nodeUserList\": [\n" +
                "                \n" +
                "            ],\n" +
                "            \"error\": false\n" +
                "        },\n" +
                "        \"nodeUserList\": [\n" +
                "            \n" +
                "        ]\n" +
                "    },\n" +
                "    \"conditionNodes\": [\n" +
                "        \n" +
                "    ]\n" +
                "}";

        //  解析json,反序列化为节点对象
        FlowNode flowNode = JSON.parseObject(json, FlowNode.class);
        log.info("json数据:{}",JSON.toJSONString(flowNode));
    }
}

为方便开发,使用的模型为一个三节点的简单流程

json字符串也直接固化在测试类中了,运行,控制台输出如下:

{
    
    
	"childNode": {
    
    
		"childNode": {
    
    
			"nodeName": "抄送人"
		},
		"nodeName": "审核人"
	},
	"nodeName": "发起人"
}

符合预期输出,fastjson将字符串映射到java对象时,会自动忽略未定义的属性,并不会报错。

模型转换功能实现

上面两步实际都是准备工作,接下来进行真正的功能开发。

节点类型的设计

节点有不同的类型,如开始、结束、分支、用户任务、服务任务等。不同的节点类型,处理是不一样的,转换时也同理。
节点类型是一个重要的属性,原开源项目前端定义如下: 0 发起人 1审批 2抄送 3条件 4路由,存在几个问题:
1.类型与后端工作流Camunda的概念不一致
2.数量较少,可视为仅是示例,后面还需要扩展
3.使用数字0、1、2,可读性差
基于上述问题,对节点类型进行了重定义,新建枚举类型类,如下:

package tech.abc.platform.workflow.enums;

import lombok.Getter;


/**
 * 流程节点类型
 *
 * @author wqliu
 * @date 2023-07-12
 */
@Getter
public enum FlowCodeTypeEnum {
    
    

    /**
     * 开始环节
     */
    START,
    
    /**
     * 用户任务
     */
    USER_TASK,
    
    /**
     * 服务任务
     */
    SERVICE_TASK,
    
    /**
     * 结束环节
     */
    END,
    ;
}

同样采用迭代方式,先建一个三环节简单审批流程使用到的类型,后面逐步完善功能。

流程转换框架设计

前端传来的流程节点,实际是流程的主体,并不包括开始环节和结束环节。这样做其实也挺好的,聚焦流程主体,后端首先做下框架工作,使用Camunda提供的流畅API,把流程建立起来。

        //  解析json,反序列化为节点对象
        FlowNode flowNode = JSON.parseObject(json, FlowNode.class);
        log.info("json数据:{}",JSON.toJSONString(flowNode));

        // 新建流程
        ProcessBuilder processBuilder = Bpmn.createProcess()
                .name("请假流程")
                .executable();
   		// 开始环节
        StartEventBuilder startEventBuilder = processBuilder.startEvent().name("流程开始");

        // TODO 添加流程主体
       
        
        // 添加结束环节
        EndEventBuilder endEventBuilder = startEventBuilder.endEvent().name("流程结束");
        // 构建模型
        BpmnModelInstance modelInstance =endEventBuilder.done();  

        // 验证模型
        Bpmn.validateModel(modelInstance);

        // 转换为xml
        String xmlString = Bpmn.convertToString(modelInstance);
        log.info(xmlString);

后端自动创建头尾,中间流程主体待处理,执行测试,模型验证通过,转换为模型的xml如下:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<definitions xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="definitions_53f2ba72-be03-45dd-9175-928a8a447a2c" targetNamespace="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">
  <process id="process_e6ea25a6-2b09-4b5e-90dd-f0a11032a3bf" isExecutable="true" name="请假流程">
    <startEvent id="startEvent_70613787-3877-46ba-a372-ac51189cd8e3" name="流程开始">
      <outgoing>sequenceFlow_8fe2b87b-3c93-4482-b24a-677a06535253</outgoing>
    </startEvent>
    <endEvent id="endEvent_38e23a33-beee-46a3-9140-c01bc0589a7b" name="流程结束">
      <incoming>sequenceFlow_8fe2b87b-3c93-4482-b24a-677a06535253</incoming>
    </endEvent>
    <sequenceFlow id="sequenceFlow_8fe2b87b-3c93-4482-b24a-677a06535253" sourceRef="startEvent_70613787-3877-46ba-a372-ac51189cd8e3" targetRef="endEvent_38e23a33-beee-46a3-9140-c01bc0589a7b"/>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_b0291ab8-3af6-41ea-bb62-6ac25f8a9a6c">
    <bpmndi:BPMNPlane bpmnElement="process_e6ea25a6-2b09-4b5e-90dd-f0a11032a3bf" id="BPMNPlane_ac95d669-a321-4a73-ae59-2d7c060a5440">
      <bpmndi:BPMNShape bpmnElement="startEvent_70613787-3877-46ba-a372-ac51189cd8e3" id="BPMNShape_a148e0aa-8760-4d98-8ebb-6787a7d81754">
        <dc:Bounds height="36.0" width="36.0" x="100.0" y="100.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="endEvent_38e23a33-beee-46a3-9140-c01bc0589a7b" id="BPMNShape_d79d35ef-7fd7-421f-920b-5cfecaa3ee32">
        <dc:Bounds height="36.0" width="36.0" x="186.0" y="100.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow_8fe2b87b-3c93-4482-b24a-677a06535253" id="BPMNEdge_ee90e10c-89ee-44cc-bafc-b209569e1376">
        <di:waypoint x="136.0" y="118.0"/>
        <di:waypoint x="186.0" y="118.0"/>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

流程转换框架实现

接下来,就处理最核心的部分,把前端传来的json数据,转换为camunda的模型。
这时候,就发现流畅API能力不足了,需要依托标准API,来创建节点,以及绘制节点之间的连接边。同时,数据机构是节点层级嵌套的,使用到了递归。

package tech.abc.platform.workflow.service.impl;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.EnumUtils;
import org.apache.poi.ss.formula.functions.T;
import org.camunda.bpm.model.bpmn.Bpmn;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.builder.EndEventBuilder;
import org.camunda.bpm.model.bpmn.builder.ProcessBuilder;
import org.camunda.bpm.model.bpmn.builder.StartEventBuilder;
import org.camunda.bpm.model.bpmn.builder.UserTaskBuilder;
import org.camunda.bpm.model.bpmn.instance.*;
import org.camunda.bpm.model.bpmn.instance.Process;
import org.junit.jupiter.api.Test;
import tech.abc.platform.workflow.entity.MyFlowNode;
import tech.abc.platform.workflow.enums.FlowCodeTypeEnum;

import java.util.UUID;

/**
 * @author wqliu
 * @date 2023-07-12
 */
@Slf4j
class FlowTemplateServiceImplTest {
    
    

    @Test
    void convertJsonToModel() {
    
      

        String json="{\n" +
                "    \"nodeName\": \"发起人\",\n" +
                "    \"type\": \"USER_TASK\",\n" +
                "    \"priorityLevel\": \"\",\n" +
                "    \"settype\": \"\",\n" +
                "    \"selectMode\": \"\",\n" +
                "    \"selectRange\": \"\",\n" +
                "    \"directorLevel\": \"\",\n" +
                "    \"examineMode\": \"\",\n" +
                "    \"noHanderAction\": \"\",\n" +
                "    \"examineEndDirectorLevel\": \"\",\n" +
                "    \"ccSelfSelectFlag\": \"\",\n" +
                "    \"conditionList\": [\n" +
                "        \n" +
                "    ],\n" +
                "    \"nodeUserList\": [\n" +
                "        \n" +
                "    ],\n" +
                "    \"childNode\": {\n" +
                "        \"nodeName\": \"审核人\",\n" +
                "        \"error\": false,\n" +
                "        \"type\":  \"USER_TASK\",\n" +
                "        \"settype\": 2,\n" +
                "        \"selectMode\": 0,\n" +
                "        \"selectRange\": 0,\n" +
                "        \"directorLevel\": 1,\n" +
                "        \"examineMode\": 1,\n" +
                "        \"noHanderAction\": 2,\n" +
                "        \"examineEndDirectorLevel\": 0,\n" +
                "        \"nodeUserList\": [\n" +
                "            \n" +
                "        ]\n" +
                "    },\n" +
                "    \"conditionNodes\": [\n" +
                "        \n" +
                "    ]\n" +
                "}";

    

        //  解析json,反序列化为节点对象
        MyFlowNode flowNode = JSON.parseObject(json, MyFlowNode.class);
        log.info("json数据:{}",JSON.toJSONString(flowNode));

        // 新建流程
        ProcessBuilder processBuilder = Bpmn.createProcess()
                .name("请假流程")
                .executable();
        Process process = processBuilder.getElement();
        // 开始环节
        StartEventBuilder startEventBuilder = processBuilder.startEvent().name("流程开始");


        // 流程节点转换
        // 流程发起,固化为用户任务
        MyFlowNode firstNode=flowNode;
        UserTaskBuilder userTaskBuilder = startEventBuilder.userTask().name(flowNode.getNodeName());
        // 其他节点
        if(firstNode.getChildNode()!=null){
    
    
            // 存在子节点,处理子节点
            createElement(process,userTaskBuilder.getElement(),firstNode.getChildNode());
        }else{
    
    
            //无子节点,附加结束节点
            appendEndNode(process, userTaskBuilder.getElement());
        }

        // 构建模型
        BpmnModelInstance modelInstance =userTaskBuilder.done();

        // 验证模型
        Bpmn.validateModel(modelInstance);

        // 转换为xml
        String xmlString = Bpmn.convertToString(modelInstance);
        log.info(xmlString);


    }


    /**
     * 创建元素
     *
     * @param process       流程
     * @param parentElement 父元素
     * @param flowNode      流程节点
     */
    private  void createElement(Process process,FlowNode parentElement,
                                                       MyFlowNode flowNode) {
    
    
        // 构建节点
        FlowNode element=null;
        FlowCodeTypeEnum type = EnumUtils.getEnum(FlowCodeTypeEnum.class, flowNode.getType());
        switch (type){
    
    
            case USER_TASK:
                element = process.getModelInstance().newInstance(UserTask.class);
                element.setAttributeValue("name", flowNode.getNodeName());
                break;
            case SERVICE_TASK:
                element = process.getModelInstance().newInstance(ServiceTask.class);
                break;
            default:
                log.error("未找到合适的类型");

        }
        element.setAttributeValue("id", UUID.randomUUID().toString(), true);
        process.addChildElement(element);

        // 构建边
        createSequenceFlow(process, parentElement, element);

        //递归处理子节点
        if(flowNode.getChildNode()!=null){
    
    
            createElement(process,element,flowNode.getChildNode());
        }else{
    
    
            //附加结束节点
            appendEndNode(process, element);
        }

    }

    /**
     * 创建元素
     *
     * @param parentElement 父元素
     * @param id            标识
     * @param elementClass  元素类别
     * @return {@link T}
     */
    private <T extends BpmnModelElementInstance> T createElement(BpmnModelElementInstance parentElement,
                                                                   String id, Class<T> elementClass) {
    
    
        T element = parentElement.getModelInstance().newInstance(elementClass);
        element.setAttributeValue("id", id, true);
        parentElement.addChildElement(element);
        return element;
    }

    /**
     * 创建序列流
     *
     * @param process 流程
     * @param from    起始节点
     * @param to      终止节点
     * @return {@link SequenceFlow}
     */
    private SequenceFlow createSequenceFlow(Process process, FlowNode from, FlowNode to) {
    
    
        String identifier = from.getId() + "-" + to.getId();
        SequenceFlow sequenceFlow = createElement(process,identifier, SequenceFlow.class);
        process.addChildElement(sequenceFlow);
        sequenceFlow.setSource(from);
        from.getOutgoing().add(sequenceFlow);
        sequenceFlow.setTarget(to);
        to.getIncoming().add(sequenceFlow);
        return sequenceFlow;
    }

    /**
     * 附加结束节点
     *
     * @param process  流程
     * @param flowNode 流程节点
     */
    private void appendEndNode(Process process, FlowNode flowNode) {
    
    
        EndEvent endEvent = process.getModelInstance().newInstance(EndEvent.class);
        endEvent.setAttributeValue("name", "流程结束");
        process.addChildElement(endEvent);
        createSequenceFlow(process,flowNode, endEvent);
    }
}

处理结果如下:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<definitions xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="definitions_3e6f9f0d-132b-4e4e-9274-0ef7faf59fa6" targetNamespace="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">
  <process id="process_a9d3ae2d-b794-4793-b1f8-db8b52e5ddaf" isExecutable="true" name="请假流程">
    <startEvent id="startEvent_f665528a-f446-41ef-ad03-ea138e83b64a" name="流程开始">
      <outgoing>sequenceFlow_a8e1d8e3-e204-4cf5-9bb2-1a80bf1c3594</outgoing>
    </startEvent>
    <userTask id="userTask_7f6639a1-bdf0-4451-b52e-8dad049c9d64" name="发起人">
      <incoming>sequenceFlow_a8e1d8e3-e204-4cf5-9bb2-1a80bf1c3594</incoming>
      <outgoing>userTask_7f6639a1-bdf0-4451-b52e-8dad049c9d64-a58d3872-cb1e-4f54-8de7-396ecf169b90</outgoing>
    </userTask>
    <sequenceFlow id="sequenceFlow_a8e1d8e3-e204-4cf5-9bb2-1a80bf1c3594" sourceRef="startEvent_f665528a-f446-41ef-ad03-ea138e83b64a" targetRef="userTask_7f6639a1-bdf0-4451-b52e-8dad049c9d64"/>
    <userTask id="a58d3872-cb1e-4f54-8de7-396ecf169b90">
      <incoming>userTask_7f6639a1-bdf0-4451-b52e-8dad049c9d64-a58d3872-cb1e-4f54-8de7-396ecf169b90</incoming>
      <outgoing>a58d3872-cb1e-4f54-8de7-396ecf169b90-endEvent_882384ce-03da-4f79-ab1a-770bb3b28bc0</outgoing>
    </userTask>
    <sequenceFlow id="userTask_7f6639a1-bdf0-4451-b52e-8dad049c9d64-a58d3872-cb1e-4f54-8de7-396ecf169b90" sourceRef="userTask_7f6639a1-bdf0-4451-b52e-8dad049c9d64" targetRef="a58d3872-cb1e-4f54-8de7-396ecf169b90"/>
    <endEvent id="endEvent_882384ce-03da-4f79-ab1a-770bb3b28bc0">
      <incoming>a58d3872-cb1e-4f54-8de7-396ecf169b90-endEvent_882384ce-03da-4f79-ab1a-770bb3b28bc0</incoming>
    </endEvent>
    <sequenceFlow id="a58d3872-cb1e-4f54-8de7-396ecf169b90-endEvent_882384ce-03da-4f79-ab1a-770bb3b28bc0" sourceRef="a58d3872-cb1e-4f54-8de7-396ecf169b90" targetRef="endEvent_882384ce-03da-4f79-ab1a-770bb3b28bc0"/>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_6248d292-31ca-4119-a3b8-0c8286fb4c39">
    <bpmndi:BPMNPlane bpmnElement="process_a9d3ae2d-b794-4793-b1f8-db8b52e5ddaf" id="BPMNPlane_89c9d599-07cb-4838-addf-d18408cf1052">
      <bpmndi:BPMNShape bpmnElement="startEvent_f665528a-f446-41ef-ad03-ea138e83b64a" id="BPMNShape_7070673c-6eeb-4315-8b5e-e58911ddbe0e">
        <dc:Bounds height="36.0" width="36.0" x="100.0" y="100.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="userTask_7f6639a1-bdf0-4451-b52e-8dad049c9d64" id="BPMNShape_5c5a851b-220f-4228-a26f-34168d9b3059">
        <dc:Bounds height="80.0" width="100.0" x="186.0" y="78.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow_a8e1d8e3-e204-4cf5-9bb2-1a80bf1c3594" id="BPMNEdge_d24ed1bc-d7b0-4dcc-940c-09db8ca2523f">
        <di:waypoint x="136.0" y="118.0"/>
        <di:waypoint x="186.0" y="118.0"/>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

这一步遇到了一个问题,有大约30%几率会报错,如下:

org.camunda.bpm.model.xml.ModelValidationException: DOM document is not valid

	at org.camunda.bpm.model.xml.impl.parser.AbstractModelParser.validateModel(AbstractModelParser.java:170)
	at org.camunda.bpm.model.bpmn.Bpmn.doValidateModel(Bpmn.java:281)
	at org.camunda.bpm.model.bpmn.Bpmn.validateModel(Bpmn.java:179)
	at tech.abc.platform.workflow.service.impl.FlowTemplateServiceImplTest.convertJsonToModel(FlowTemplateServiceImplTest.java:169)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:686)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:212)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:208)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:137)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:71)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
	at java.util.ArrayList.forEach(ArrayList.java:1259)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
	at java.util.ArrayList.forEach(ArrayList.java:1259)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:248)
	at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$5(DefaultLauncher.java:211)
	at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:226)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:199)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:132)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
Caused by: org.xml.sax.SAXParseException; cvc-datatype-valid.1.2.1: '860fc4aa-45bc-484c-80dc-607470ae930f' 不是 'NCName' 的有效值。
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:204)
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:135)
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:395)
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:326)
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:283)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator$XSIErrorReporter.reportError(XMLSchemaValidator.java:453)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.reportSchemaError(XMLSchemaValidator.java:3231)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.processOneAttribute(XMLSchemaValidator.java:2826)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.processAttributes(XMLSchemaValidator.java:2763)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleStartElement(XMLSchemaValidator.java:2051)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.startElement(XMLSchemaValidator.java:741)
	at com.sun.org.apache.xerces.internal.jaxp.validation.DOMValidatorHelper.beginNode(DOMValidatorHelper.java:277)
	at com.sun.org.apache.xerces.internal.jaxp.validation.DOMValidatorHelper.validate(DOMValidatorHelper.java:244)
	at com.sun.org.apache.xerces.internal.jaxp.validation.DOMValidatorHelper.validate(DOMValidatorHelper.java:190)
	at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorImpl.validate(ValidatorImpl.java:108)
	at javax.xml.validation.Validator.validate(Validator.java:124)
	at org.camunda.bpm.model.xml.impl.parser.AbstractModelParser.validateModel(AbstractModelParser.java:165)
	... 68 more


Process finished with exit code -1

核心错误是这里

Caused by: org.xml.sax.SAXParseException; cvc-datatype-valid.1.2.1: '860fc4aa-45bc-484c-80dc-607470ae930f' 不是 'NCName' 的有效值。

提示有点怪,但是分析一下,推测原因是我们使用uuid作为节点元素的id,如果生成的uuid刚好以数字开头,那么就不符合标识符必须以字母或下划线起始的规则了。这也简单,id前面拼点字母上去就好了,这里我加了一个node开头。当前聚焦在流程建模上,本来使用uuid只是临时方案,后面会用雪花算法替代掉。

 element.setAttributeValue("id", "node"+UUID.randomUUID().toString(), true);

经过上面调整,多次测试,100%成功通过模型验证,转换输出了xml,不再出现报错情况。

代码调试通了,回顾下,可以把流程首环节,也拿到递归方法里去,不需要单独处理,重构如下:

       //  解析json,反序列化为节点对象
        MyFlowNode flowNode = JSON.parseObject(json, MyFlowNode.class);
        log.info("json数据:{}",JSON.toJSONString(flowNode));

        // 新建流程
        ProcessBuilder processBuilder = Bpmn.createProcess()
                .name("请假流程")
                .executable();
        Process process = processBuilder.getElement();
        // 开始环节
        StartEventBuilder startEventBuilder = processBuilder.startEvent().name("流程开始");

        // 流程节点转换
        convertJsonToModel(process,startEventBuilder.getElement(),flowNode);

        // 构建模型
        BpmnModelInstance modelInstance =startEventBuilder.done();

        // 验证模型
        Bpmn.validateModel(modelInstance);

        // 转换为xml
        String xmlString = Bpmn.convertToString(modelInstance);
        log.info(xmlString);

开发平台资料

平台名称:一二三开发平台
简介: 企业级通用开发平台
设计资料:csdn专栏
开源地址:Gitee
开源协议:MIT
开源不易,欢迎收藏、点赞、评论。

猜你喜欢

转载自blog.csdn.net/seawaving/article/details/131974424