Flujo de trabajo integrado de la plataforma de desarrollo de aplicaciones: diseño e implementación del marco de transformación del modelo funcional de modelado de procesos

fondo

Para el problema de la configuración de procesos hostil, DingTalk doméstico ha diseñado e implementado un conjunto de modos de modelado de procesos por separado, que no tiene nada que ver con la especificación bpmn. Alguien imitó la implementación y la hizo de código abierto ( https://github.com/ StavinLi/Workflow -Vue3 ), el diagrama de efectos es el siguiente:

el principio general de implementación se basa en nodos secundarios infinitamente anidados, genera datos json y los pasa al backend. Después de que el backend analiza, llama a la API de Camunda motor, lo convierte en un modelo de proceso y luego lo persiste.

El artículo anterior presentó la solución técnica de conversión de modelo de puntos técnicos clave, y hoy nos centraremos en cómo realizarla.

Diseño e implementación de clases de nodos de proceso

Para el modelado de proyectos front-end de código abierto, la estructura de datos es un objeto json infinitamente anidado y cada capa es una instancia de nodo, de la siguiente manera:

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

Se pasa al backend una cadena json, que FastJson analiza y asigna a un objeto, lo cual es conveniente para el procesamiento posterior, como la lectura de atributos, así que cree una nueva clase llamada FlowNode, de la siguiente manera:

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

Las propiedades de nodo establecen temporalmente dos propiedades principales, una es nodeName, nombre de nodo, y la otra es childNode, nodo secundario. La razón por la que no configuramos todos los atributos a la vez es porque queremos mejorar gradualmente las funciones a través de la iteración. No usaremos las definiciones de datos existentes en su totalidad, sino que las agregaremos, eliminaremos y cambiaremos de nombre de acuerdo con las necesidades reales. y ajustar la estructura de datos cuando sea necesario.

Diseño e implementación de clases de desarrollo y pruebas.

Convertir json en un modelo no es un asunto simple y requiere mucha depuración y prueba. Si está integrado directamente en la clase de servicio, SpringBoot debe cargarse cada vez que se depura, lo cual es ineficiente y requiere mucho tiempo.
Por lo tanto, se crea una clase de prueba en el flujo de trabajo del módulo y el código de función convertido se escribe directamente en la clase de prueba para un inicio y una verificación rápidos. Una vez que se completa el desarrollo de la función de conversión o se completa la mayor parte, se volverá a migrar a la clase de servicio para la integración.

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

Para facilitar el desarrollo, el modelo utilizado es un proceso simple de tres nodos.

La cadena json también se solidifica directamente en la clase de prueba. Cuando se ejecuta, la salida de la consola es la siguiente:

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

De acuerdo con el resultado esperado, cuando fastjson asigna una cadena a un objeto Java, ignorará automáticamente las propiedades indefinidas y no informará un error.

Realización de la función de conversión del modelo

Los dos pasos anteriores son en realidad un trabajo preparatorio, y luego se llevará a cabo el desarrollo de la función real.

Diseño de tipo de nodo

Hay diferentes tipos de nodos como inicio, final, rama, tarea de usuario, tarea de servicio, etc. El procesamiento es diferente para diferentes tipos de nodos y lo mismo ocurre con la conversión.
El tipo de nodo es un atributo importante. La definición de front-end del proyecto original de código abierto es la siguiente: 0 Iniciador 1 Aprobación 2 CC 3 Condición 4 Enrutamiento, hay varios problemas: 1. El tipo es inconsistente con el concepto de
back- fin del flujo de trabajo Camunda
2. El número es pequeño, lo que puede considerarse solo como un ejemplo, y debe ampliarse más adelante 3. El
uso de los números 0, 1 y 2 es de mala legibilidad
En base a los problemas anteriores, el tipo de nodo se redefine , y se crea una nueva clase de tipo de enumeración, de la siguiente manera:

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

También adopta un método iterativo, primero crea un tipo utilizado en un proceso de aprobación simple de tres pasos y luego mejora gradualmente las funciones.

Diseño del Marco de Transformación de Procesos

Los nodos de proceso transmitidos desde el front-end son en realidad el cuerpo principal del proceso y no incluyen los enlaces de inicio y fin. En realidad, esto es bastante bueno, ya que se centra en el cuerpo principal del proceso, el back-end primero hace el trabajo del marco y utiliza la API proporcionada por Camunda para establecer el proceso.

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

El backend crea automáticamente la cabeza y la cola, se procesará el cuerpo principal del proceso intermedio, se ejecutará la prueba, se pasará la verificación del modelo y el xml convertido al modelo es el siguiente:

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

Implementación del Marco de Transformación de Procesos

Luego, trate con la parte central, convirtiendo los datos json del front-end en un modelo de camunda.
En este momento, se encuentra que la API fluida no es lo suficientemente capaz y es necesario confiar en la API estándar para crear nodos y dibujar bordes de conexión entre nodos. Al mismo tiempo, la organización de datos se anida a nivel de nodo y se utiliza la recursividad.

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

Los resultados del procesamiento son los siguientes:

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

Se encontró un problema en este paso, hay aproximadamente un 30% de probabilidad de que se informe un error, de la siguiente manera:

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

El error central está aquí.

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

El indicador es un poco extraño, pero después de analizarlo, se especula que la razón es que usamos uuid como el id del elemento del nodo.Si el uuid generado comienza con un número, no cumple con la regla de que el identificador debe comenzar con una letra o un guión bajo. Esto también es simple, solo coloque algunas letras delante de la identificación, y aquí agregué un nodo al principio. Centrándose actualmente en el modelado de procesos, el uso de uuid es solo una solución temporal y será reemplazado por el algoritmo de copo de nieve más adelante.

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

Después de los ajustes anteriores y las múltiples pruebas, la verificación del modelo fue 100 % exitosa, y el xml se convirtió y se generó, y no hubo más informes de errores.

El código ha sido depurado. En retrospectiva, el primer enlace del proceso también se puede tomar en el método recursivo. No es necesario procesarlo por separado. La reconstrucción es la siguiente:

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

Información de la plataforma de desarrollo

Nombre de la plataforma: One Two Three Development Platform
Introducción: plataforma de desarrollo general de nivel empresarial
Información de diseño: columna csdn
Dirección de código abierto: protocolo de código abierto Gitee
: el código abierto del MIT
no es fácil, bienvenido a favoritos, me gusta, comentario.

Supongo que te gusta

Origin blog.csdn.net/seawaving/article/details/131974424
Recomendado
Clasificación