(1). 概述
最近有这样一个需求,要实现一个编排系统,画布产生的数据是json,自己懒得去写一套工作流,就想着把json向工作流(Camunda/Activiti/Flowable)靠拢,实际就是把json转换成xml.
(2). 通过Camunda Modeler绘制流程图
(3). 通过Camunda Modeler绘制的原始xml
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
xmlns:modeler="http://camunda.org/schema/modeler/1.0"
id="Definitions_016seq7"
targetNamespace="http://bpmn.io/schema/bpmn"
exporter="Camunda Modeler"
exporterVersion="5.5.0-nightly.20221024"
modeler:executionPlatform="Camunda Platform"
modeler:executionPlatformVersion="7.18.0">
<bpmn:process id="Process_19ixb1h" isExecutable="true">
<bpmn:startEvent id="star" name="开始">
<bpmn:outgoing>Flow_13jlccj</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:task id="userTask" name="Task1">
<bpmn:incoming>Flow_13jlccj</bpmn:incoming>
<bpmn:outgoing>Flow_0k8cen8</bpmn:outgoing>
</bpmn:task>
<bpmn:sequenceFlow id="Flow_13jlccj" sourceRef="star" targetRef="userTask" />
<bpmn:endEvent id="end" name="结束">
<bpmn:incoming>Flow_0k8cen8</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_0k8cen8" sourceRef="userTask" targetRef="end" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_19ixb1h">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="star">
<dc:Bounds x="179" y="99" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="186" y="142" width="22" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0111s91_di" bpmnElement="userTask">
<dc:Bounds x="270" y="77" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1njqo00_di" bpmnElement="end">
<dc:Bounds x="432" y="99" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="439" y="142" width="22" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_13jlccj_di" bpmnElement="Flow_13jlccj">
<di:waypoint x="215" y="117" />
<di:waypoint x="270" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0k8cen8_di" bpmnElement="Flow_0k8cen8">
<di:waypoint x="370" y="117" />
<di:waypoint x="432" y="117" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
(4). 原理分析
通过XML,可以剖析出如下对象:
# 与流程节点相关
Process
StartEvent
UserTask
EndEvent
SequenceFlow
# 与画布描点相关
BpmnDiagram
BpmnPlane
BPMNShape
BPMNLabel
Bounds
Waypoint
(5). 通过BPMN API生成
@Test
public void testBpmn() {
// 1、创建模型
BpmnModelInstance modelInstance = Bpmn.createEmptyModel();
//2、创建BPMN定义元素,设置目标名称空间,并将其添加到新创建的模型实例中
Definitions definitions = modelInstance.newInstance(Definitions.class);
definitions.setTargetNamespace(BpmnModelConstants.CAMUNDA_NS);
definitions.getDomElement().registerNamespace("bpmn", BpmnModelConstants.BPMN20_NS);
definitions.getDomElement().registerNamespace("bpmndi", BpmnModelConstants.BPMNDI_NS);
definitions.getDomElement().registerNamespace("dc", BpmnModelConstants.DC_NS);
definitions.getDomElement().registerNamespace("di", BpmnModelConstants.DI_NS);
modelInstance.setDefinitions(definitions);
// 3、向模型新增流程.
Process process = modelInstance.newInstance(Process.class);
process.setId("pipeline-demo");
process.setName("pipelie-demo-example");
process.setExecutable(true);
definitions.addChildElement(process);
// 绘图相关对象
BpmnDiagram bpmnDiagram = modelInstance.newInstance(BpmnDiagram.class);
BpmnPlane bpmnPlane = modelInstance.newInstance(BpmnPlane.class);
bpmnPlane.setBpmnElement(process);
bpmnDiagram.addChildElement(bpmnPlane);
definitions.addChildElement(bpmnDiagram);
// 流程定义里添加开始节点
StartEvent startEvent = modelInstance.newInstance(StartEvent.class);
startEvent.setId("start");
startEvent.setName("开始");
process.addChildElement(startEvent);
// 流程定义里添加用户节点
UserTask userTask = modelInstance.newInstance(UserTask.class);
userTask.setId("userTask");
userTask.setName("Task1");
userTask.setCamundaAssignee("admin");
process.addChildElement(userTask);
// 流程定义里添加结束节点
EndEvent endEvent = modelInstance.newInstance(EndEvent.class);
endEvent.setId("end");
endEvent.setName("结束");
process.addChildElement(endEvent);
SequenceFlow sequenceFlow1 = modelInstance.newInstance(SequenceFlow.class);
sequenceFlow1.setId("Flow_13jlccj");
sequenceFlow1.setSource(startEvent);
sequenceFlow1.setTarget(userTask);
process.addChildElement(sequenceFlow1);
SequenceFlow sequenceFlow2 = modelInstance.newInstance(SequenceFlow.class);
sequenceFlow2.setId("Flow_0k8cen8");
sequenceFlow2.setSource(userTask);
sequenceFlow2.setTarget(endEvent);
process.addChildElement(sequenceFlow2);
startEvent.getOutgoing().add(sequenceFlow1);
userTask.getIncoming().add(sequenceFlow1);
userTask.getOutgoing().add(sequenceFlow2);
//
endEvent.getIncoming().add(sequenceFlow2);
// 画图(start节点配置)
Bounds _BPMNShape_StartEvent_2Bounds = modelInstance.newInstance(Bounds.class);
_BPMNShape_StartEvent_2Bounds.setX(179);
_BPMNShape_StartEvent_2Bounds.setY(99);
_BPMNShape_StartEvent_2Bounds.setWidth(36);
_BPMNShape_StartEvent_2Bounds.setHeight(36);
BpmnLabel _BPMNShape_StartEvent_2BpmnLabel = modelInstance.newInstance(BpmnLabel.class);
Bounds _BPMNShape_StartEvent_2BpmnLabelBounds = modelInstance.newInstance(Bounds.class);
_BPMNShape_StartEvent_2BpmnLabelBounds.setX(186);
_BPMNShape_StartEvent_2BpmnLabelBounds.setY(142);
_BPMNShape_StartEvent_2BpmnLabelBounds.setWidth(22);
_BPMNShape_StartEvent_2BpmnLabelBounds.setHeight(14);
_BPMNShape_StartEvent_2BpmnLabel.addChildElement(_BPMNShape_StartEvent_2BpmnLabelBounds);
BpmnShape _BPMNShape_StartEvent_2 = modelInstance.newInstance(BpmnShape.class);
_BPMNShape_StartEvent_2.setId("_BPMNShape_StartEvent_2");
_BPMNShape_StartEvent_2.setBpmnElement(startEvent);
_BPMNShape_StartEvent_2.addChildElement(_BPMNShape_StartEvent_2Bounds);
_BPMNShape_StartEvent_2.addChildElement(_BPMNShape_StartEvent_2BpmnLabel);
// 添加绘图
bpmnPlane.addChildElement(_BPMNShape_StartEvent_2);
// 画图(userTask)
Bounds activity_0111s91_diBounds = modelInstance.newInstance(Bounds.class);
activity_0111s91_diBounds.setX(270);
activity_0111s91_diBounds.setY(77);
activity_0111s91_diBounds.setWidth(100);
activity_0111s91_diBounds.setHeight(80);
BpmnShape activity_0111s91_di = modelInstance.newInstance(BpmnShape.class);
activity_0111s91_di.setId("Activity_0111s91_di");
activity_0111s91_di.setBpmnElement(userTask);
activity_0111s91_di.addChildElement(activity_0111s91_diBounds);
// 添加绘图
bpmnPlane.addChildElement(activity_0111s91_di);
// 画图(end节点配置)
Bounds Event_1njqo00_di2Bounds = modelInstance.newInstance(Bounds.class);
Event_1njqo00_di2Bounds.setX(432);
Event_1njqo00_di2Bounds.setY(99);
Event_1njqo00_di2Bounds.setWidth(36);
Event_1njqo00_di2Bounds.setHeight(36);
BpmnLabel Event_1njqo00_diBpmnLabel = modelInstance.newInstance(BpmnLabel.class);
Bounds Event_1njqo00_diBounds = modelInstance.newInstance(Bounds.class);
Event_1njqo00_diBounds.setX(439);
Event_1njqo00_diBounds.setY(142);
Event_1njqo00_diBounds.setWidth(22);
Event_1njqo00_diBounds.setHeight(14);
Event_1njqo00_diBpmnLabel.addChildElement(Event_1njqo00_diBounds);
BpmnShape event_1njqo00_di = modelInstance.newInstance(BpmnShape.class);
event_1njqo00_di.setId("Event_1njqo00_di");
event_1njqo00_di.setBpmnElement(endEvent);
event_1njqo00_di.addChildElement(Event_1njqo00_di2Bounds);
event_1njqo00_di.addChildElement(Event_1njqo00_diBpmnLabel);
// 添加绘图
bpmnPlane.addChildElement(event_1njqo00_di);
// Flow_13jlccj
Waypoint flow_13jlccj_di_1_Waypoint = modelInstance.newInstance(Waypoint.class);
flow_13jlccj_di_1_Waypoint.setX(215);
flow_13jlccj_di_1_Waypoint.setY(117);
Waypoint flow_13jlccj_di_2_Waypoint = modelInstance.newInstance(Waypoint.class);
flow_13jlccj_di_2_Waypoint.setX(270);
flow_13jlccj_di_2_Waypoint.setY(117);
BpmnEdge flow_13jlccj_di = modelInstance.newInstance(BpmnEdge.class);
flow_13jlccj_di.setId("Flow_13jlccj_di");
flow_13jlccj_di.setBpmnElement(sequenceFlow1);
flow_13jlccj_di.addChildElement(flow_13jlccj_di_1_Waypoint);
flow_13jlccj_di.addChildElement(flow_13jlccj_di_2_Waypoint);
bpmnPlane.addChildElement(flow_13jlccj_di);
// Flow_0k8cen8_di
Waypoint flow_0k8cen8_di_1_Waypoint = modelInstance.newInstance(Waypoint.class);
flow_0k8cen8_di_1_Waypoint.setX(370);
flow_0k8cen8_di_1_Waypoint.setY(117);
Waypoint flow_0k8cen8_di_2_Waypoint = modelInstance.newInstance(Waypoint.class);
flow_0k8cen8_di_2_Waypoint.setX(432);
flow_0k8cen8_di_2_Waypoint.setY(117);
BpmnEdge flow_0k8cen8_di = modelInstance.newInstance(BpmnEdge.class);
flow_0k8cen8_di.setId("Flow_0k8cen8_di");
flow_0k8cen8_di.setBpmnElement(sequenceFlow2);
flow_0k8cen8_di.addChildElement(flow_0k8cen8_di_1_Waypoint);
flow_0k8cen8_di.addChildElement(flow_0k8cen8_di_2_Waypoint);
bpmnPlane.addChildElement(flow_0k8cen8_di);
System.out.println(Bpmn.convertToString(modelInstance));
}
(6). 最后生成的xml文件如下
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" 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_b292ff24-fe48-4348-8635-54cf5d82ced6" targetNamespace="http://camunda.org/schema/1.0/bpmn" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">
<process id="pipeline-demo" isExecutable="true" name="pipelie-demo-example">
<startEvent id="start" name="开始">
<outgoing>Flow_13jlccj</outgoing>
</startEvent>
<userTask camunda:assignee="admin" id="userTask" name="Task1">
<incoming>Flow_13jlccj</incoming>
<outgoing>Flow_0k8cen8</outgoing>
</userTask>
<endEvent id="end" name="结束">
<incoming>Flow_0k8cen8</incoming>
</endEvent>
<sequenceFlow id="Flow_13jlccj" sourceRef="start" targetRef="userTask"/>
<sequenceFlow id="Flow_0k8cen8" sourceRef="userTask" targetRef="end"/>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_2520f04f-4a39-4c22-ace0-2314cfb1170e">
<bpmndi:BPMNPlane bpmnElement="pipeline-demo" id="BPMNPlane_94dce98b-afac-48fb-9e57-b3692decb051">
<bpmndi:BPMNShape bpmnElement="start" id="_BPMNShape_StartEvent_2">
<dc:Bounds height="36.0" width="36.0" x="179.0" y="99.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_17f8f5c1-b23a-4302-b63a-740f7ebcd965">
<dc:Bounds height="14.0" width="22.0" x="186.0" y="142.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="userTask" id="Activity_0111s91_di">
<dc:Bounds height="80.0" width="100.0" x="270.0" y="77.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="end" id="Event_1njqo00_di">
<dc:Bounds height="36.0" width="36.0" x="432.0" y="99.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_3e5a09a0-5a48-4589-b218-d7231cb9aa2c">
<dc:Bounds height="14.0" width="22.0" x="439.0" y="142.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="Flow_13jlccj" id="Flow_13jlccj_di">
<di:waypoint x="215.0" y="117.0"/>
<di:waypoint x="270.0" y="117.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="Flow_0k8cen8" id="Flow_0k8cen8_di">
<di:waypoint x="370.0" y="117.0"/>
<di:waypoint x="432.0" y="117.0"/>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
(7). Camunda流式API生成案例
@Test
public void testBpmnToXml() {
// @formatter:off
BpmnModelInstance modelInstance = Bpmn.createProcess()
.startEvent()
.userTask()
.id("task1")
.name("task1")
.parallelGateway("fork")
.serviceTask()
.parallelGateway("join")
.moveToNode("fork")
.userTask()
.connectTo("join")
.moveToNode("fork")
.scriptTask()
.connectTo("join")
.endEvent()
.done();
// @formatter:on
// 找到某个节点,配置naemspace和属性
ModelElementInstance task = modelInstance.getModelElementById("task1");
task.setAttributeValueNs("http://lixin.help", "hello", "world");
String hello = task.getAttributeValueNs("http://lixin.help", "hello");
if (task instanceof FlowNode) {
FlowNode flowNode = (FlowNode) task;
ExtensionElements extensionElements = flowNode.getExtensionElements();
if (null == extensionElements) {
extensionElements = modelInstance.newInstance(ExtensionElements.class);
ModelElementInstance myExtensionElement = extensionElements.addExtensionElement("http://lixin.help", "myExtensionElement");
myExtensionElement.setAttributeValue("testName", "testValue");
flowNode.setExtensionElements(extensionElements);
}
List<ModelElementInstance> list = extensionElements.getElementsQuery().filterByType(ModelElementInstance.class).list();
for (ModelElementInstance modelElementInstance : list) {
// @formatter:off
if (null != modelElementInstance.getElementType() &&
null != modelElementInstance.getElementType().getTypeName() &&
modelElementInstance.getElementType().getTypeName().equals("myExtensionElement")) {
// @formatter:on
String testName = modelElementInstance.getAttributeValueNs("http://lixin.help", "testName");
System.out.println(testName);
}
}
// Collection<SequenceFlow> incoming = flowNode.getIncoming();
// Collection<SequenceFlow> outgoing = flowNode.getOutgoing();
}
System.out.println(Bpmn.convertToString(modelInstance));
}
(8). 总结
Camunda的设计还是挺好的,基本上xml节点名称,和我们的类名称是一一对应的,不费吹灰之力,就可以实现手写xml生成过程.