Application Development Platform Integrated Workflow - Design and Implementation of Process Modeling Functional Model Transformation Framework

background

For the problem of unfriendly process settings, domestic DingTalk designed and implemented a set of process modeling mode separately, which has nothing to do with the bpmn specification. Someone imitated it and made it open source ( https://github.com/StavinLi/Workflow -Vue3 ), the effect diagram is as follows:

the general principle of implementation is based on infinitely nested child nodes, output json data, and pass it to the backend. After the backend parses, it calls the api of the Camunda engine, converts it into a process model, and then persists it.

The previous article introduced the key technical point model conversion technical solution, and today we will focus on how to realize it.

Process node class design and implementation

For the modeling of front-end open source projects, the data structure is an infinitely nested json object, and each layer is a node instance, as follows:

"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":{
    
    } //子节点

Passed to the backend is a json string, which is parsed by FastJson and mapped to an object, which is convenient for later processing such as reading properties, so create a new class named FlowNode, as follows:

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

Node properties temporarily set two core properties, one is nodeName, node name, and the other is childNode, child node. The reason why we don’t set all the attributes at once is because we want to gradually improve the functions through iteration. We will not use the existing data definitions in their entirety, but will add, delete, rename them according to actual needs, and adjust the data structure when necessary.

Development and testing class design and implementation

Converting json to a model is not a simple matter and requires a lot of debugging and testing. If it is directly embedded in the service class, SpringBoot must be loaded every time it is debugged, which is inefficient and time-consuming.
Therefore, a test class is built in the module workflow, and the converted function code is directly written into the test class for quick startup and verification. After the development of the conversion function is completed or most of it is completed, it will be migrated back to the service class for integration.

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

For the convenience of development, the model used is a three-node simple process.

The json string is also directly solidified in the test class. When running, the console output is as follows:

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

In line with the expected output, when fastjson maps a string to a java object, it will automatically ignore undefined properties and will not report an error.

Model conversion function realization

The above two steps are actually preparatory work, and then the real function development will be carried out.

Node Type Design

There are different types of nodes like start, end, branch, user task, service task, etc. The processing is different for different node types, and the same is true for conversion.
Node type is an important attribute. The front-end definition of the original open source project is as follows: 0 Initiator 1 Approval 2 CC 3 Condition 4 Routing, there are several problems: 1. The type is inconsistent with the concept of
back-end workflow Camunda
2. The number is small , which can be regarded as just an example, and needs to be expanded later.
3. Using numbers 0, 1, and 2 is poor readability
Based on the above problems, the node type is redefined, and a new enumeration type class is created, as follows:

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

It also adopts an iterative method, first building a type used in a three-step simple approval process, and then gradually improving the functions.

Process Transformation Framework Design

The process nodes transmitted from the front end are actually the main body of the process, and do not include the start and end links. This is actually quite good, focusing on the main body of the process, the back-end first does the framework work, and uses the smooth API provided by Camunda to establish the process.

        //  解析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);

The backend automatically creates the head and tail, the main body of the intermediate process is to be processed, the test is executed, the model verification is passed, and the xml converted to the model is as follows:

<?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>

Process Transformation Framework Implementation

Next, deal with the core part, converting the json data from the front end into a camunda model.
At this time, it is found that the smooth API is not capable enough, and it is necessary to rely on the standard API to create nodes and draw connection edges between nodes. At the same time, the data organization is nested at the node level, and recursion is used.

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

The processing results are as follows:

<?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>

There is a problem encountered in this step, there is about 30% chance that an error will be reported, as follows:

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

The core error is here

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

The prompt is a bit strange, but after analyzing it, it is speculated that the reason is that we use uuid as the id of the node element. If the generated uuid happens to start with a number, it does not comply with the rule that identifiers must start with letters or underscores. This is also simple, just put some letters in front of the id, and here I added a node at the beginning. Currently focusing on process modeling, the use of uuid is only a temporary solution, and will be replaced by the snowflake algorithm later.

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

After the above adjustments and multiple tests, the model verification was 100% successful, and the xml was converted and output, and there was no more error reporting.

The code has been debugged. In retrospect, the first link of the process can also be taken into the recursive method. It does not need to be processed separately. The reconstruction is as follows:

       //  解析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);

Development platform information

Platform name: One Two Three Development Platform
Introduction: Enterprise-level general development platform
Design information: csdn column
Open source address: Gitee
open source protocol: MIT
open source is not easy, welcome to favorite, like, comment.

Guess you like

Origin blog.csdn.net/seawaving/article/details/131974424