Давно не обновлял статью.В то время,когда не обновлял статью,был в отступлении и практиковался,старался улучшить свои навыки.Ведь мой флаг в 2020-м стать большой коровой.
Эта статья, представленная вам сегодня, предназначена для того, чтобы систематизировать мой опыт использования bpmn-js для реализации конструктора процессов activiti.Не так много китайских документов по bpmn-js, и многие люди не знают, как начать разработку, а бэкэнд bpmn -js Использование Camunda, как использовать activiti, также сильно затрудняет разработку.
вопрос
Мне нужно разделить переднюю и заднюю часть, я думаю, что дизайнер действий не прост в использовании, и использовать bpmn-js для реализации дизайнера, но задняя часть использует активити, что мне делать, если xml несовместим?
решить
Это не просто мое решение, я только предоставляю свое решение здесь.
1 Использование Bpmn-js для разработки дизайнера
О том, как использовать bpmn-js для создания предложения, перейдите на github для поиска и вставьте сюда адрес официального сайта:GitHub.com/BPMN-IO/BPM…
Адрес официального сайта кейса:GitHub.com/BPMN-IO/BPM…
Когда автор разрабатывал конструктор, я ссылался на цикл статей о разработке bpmn-js с 0 автором Lin Duan, адрес:nuggets.capable/post/684490…
Я считаю, что после прочтения статей, которые я разместил выше, вы уже знакомы с bpmn-js, тогда я объясню свой проект:
-
окружающая обстановка: окна10
-
Инструменты разработки: vscode, ИДЕЯ
-
**Технология:** Интерфейс: vue+webpack, сервер Springboot+Activiti.
1.1 Настройка правой панели свойств
Как показано на рисунке, это моя полностью настроенная панель свойств.
Часть кода выглядит следующим образом:
<template>
<div>
<el-container style="height: 700px">
<el-aside width="80%" style="border: 1px solid #DCDFE6" >
<div ref="canvas" style="width: 100%;height: 100%"></div>
</el-aside>
<el-main style="border: 1px solid #DCDFE6;background-color:#FAFAFA ">
<el-form label-width="auto" size="mini" label-position="top">
<!-- 动态显示属性面板 -->
<component :is= "propsComponent" :element= "element" :key= "key"></component>
</el-form>
</el-main>
</el-container>
</div>
</template>
Я пропускаюИзменения свойств propsComponentдля отображения свойств различных событий, таких как свойства пользовательских задач, свойства шлюзов
Свойство propsComponent изменяет свое значение, прислушиваясь к моделлеру и элементу Код выглядит следующим образом:
addModelerListener() {
// 监听 modeler
const bpmnjs = this.bpmnModeler
const that = this
// 'shape.removed', 'connect.end', 'connect.move'
const events = ['shape.added', 'shape.move.end', 'shape.removed']
events.forEach(function(event) {
that.bpmnModeler.on(event, e => {
var elementRegistry = bpmnjs.get('elementRegistry')
var shape = e.element ? elementRegistry.get(e.element.id) : e.shape
// console.log(shape)
if (event === 'shape.added') {
console.log('新增了shape');
// 展示新增图形的属性
that.key = e.element.id.replace('_label', '');
that.propsComponent = bpmnHelper.getComponentByEleType(shape.type);
that.element = e.element;
} else if (event === 'shape.move.end') {
console.log('移动了shape')
// 展示新增图形的属性
that.key = shape.id;
that.propsComponent = bpmnHelper.getComponentByEleType(shape.type);
that.element = e.shape;
} else if (event === 'shape.removed') {
console.log('删除了shape')
// 展示默认的属性
that.propsComponent = 'CommonProps'
}
})
})
},
addEventBusListener() {
// 监听 element
let that = this
const eventBus = this.bpmnModeler.get('eventBus')
const eventTypes = ['element.click', 'element.changed', 'selection.changed']
eventTypes.forEach(function(eventType) {
eventBus.on(eventType, function(e) {
if (eventType === 'element.changed') {
that.elementChanged(e)
} else if (eventType === 'element.click') {
console.log('点击了element');
if (!e || e.element.type == 'bpmn:Process') {
that.key = '1';
that.propsComponent = 'CommonProps'
that.element = e.element;
} else {
// 展示新增图形的属性
that.key = e.element.id;
that.propsComponent = bpmnHelper.getComponentByEleType(e.element.type);
that.element = e.element;
}
}
})
})
},
Из-за специфики vue компоненты необходимо представить перед использованием компонентов свойств.
components: {
CommonProps,
ProcessProps,
StartEventProps,
EndEventProps,
IntermediateThrowEventProps,
ExclusiveGatewayProps,
ParallelGatewayProps,
InclusiveGatewayProps,
UserTaskProps,
SequenceFlowProps,
CallActivityProps
},
Следующим шагом является реализация страницы каждого атрибута события.
1.2 Адаптация к деятельности
Так как bpmn-js официально совместим с camunda, есть несовместимость с activiti.Чтобы включить bpmn-js для использования activiti, нам нужно расширить activiti в BpmnModeler.Код выглядит следующим образом:
import activitiModdleDescriptor from '../js/activiti.json';
this.bpmnModeler = new BpmnModeler({
container: canvas,
//添加属性面板,添加翻译模块
additionalModules: [
customTranslateModule,
customControlsModule
],
//模块拓展,拓展activiti的描述
moddleExtensions: {
activiti: activitiModdleDescriptor
}
});
Что касается файла activiti.json, я предлагаю вам посмотретьПример пользовательской метамодели
1.2.1 Как настроить файл activiti.json 🌟
{
"name": "Activiti", // 标识是activiti
"uri": "http://activiti.org/bpmn", // 添加activiti的命名空间
"prefix": "activiti", // 属性前缀
"xml": {
"tagAlias": "lowerCase"
},
"associations": [],
"types": [
{
"name": "Process", // <bpmn2:process> 标签
"isAbstract": true,
"extends": [
"bpmn:Process" // 继承自<bpmn2:process>
],
"properties": [ // 这个标签的属性
{
"name": "candidateStarterGroups", // 属性名
"isAttr": true, // 是否是属性
"type": "String" // 属性类型
},
{
"name": "candidateStarterUsers",
"isAttr": true,
"type": "String"
},
{
"name": "versionTag",
"isAttr": true,
"type": "String"
},
{
"name": "historyTimeToLive",
"isAttr": true,
"type": "String"
},
{
"name": "isStartableInTasklist",
"isAttr": true,
"type": "Boolean",
"default": true // 给属性添加默认值,但这个默认值没有写入xml中
},
{
"name":"executionListener", // 监听器属性
"isAbstract": true, // 抽象
"type":"Expression" // 类型是表达式
}
]
},
// 在这里接着加其他节点
],
"emumerations": [ ]}
пример: мне нужно добавить пользовательский атрибут nodeType (тип узла) в пользовательскую задачу в моем проекте
{ "name": "UserTask", "isAbstract": true, "extends": [ "bpmn:UserTask" ], "properties": [ { "name": "nodeType", "isAttr": true, "type": "String" }, ] }
1.3 О частичных расширениях и полной настройке 🌟
Возьмем в качестве примера левую панель инструментов, интерфейсный проект: файл src/edit-modeler/js/customController/CustomPalette.js.
просить:
Вы можете видеть, что я настроил пользовательские задачи и вызов активных узлов, а для других узлов я использую bpmn-js;
Что делать, если я не хочу использовать тот, который поставляется с bpmn-js?
отвечать:
файл src/edit-modeler/js/customController/index.js
import CustomContextPad from './CustomContextPad';import CustomPalette from './CustomPalette';export default {__init__: [ 'customContextPad', 'customPalette' ],customContextPad: [ 'type', CustomContextPad ],customPalette: [ 'type', CustomPalette ]};
Вот customPalette, если вы хотите полностью настроить его, замените его на panelProvider;
по аналогии: полностью настроить contextPad с помощью contextPadProvider, полностью настроить панель свойств с помощью propertiesProvider.
import CustomContextPad from './CustomContextPad';import CustomPalette from './CustomPalette';export default {__init__: [ 'contextPadProvider', 'paletteProvider' ],contextPadProvider: [ 'type', CustomContextPad ],paletteProvider: [ 'type', CustomPalette ]};
1.4 О префиксах атрибутов 🌟
просить:
Все мы знаем, что все префиксы атрибутов файлов xml, сгенерированных bpmn-js, являются camunda, так как же заменить их на нужные нам префиксы?
отвечать:
Есть два способа
Один из них — расширить файл json, например, нам нужен префикс activiti для расширения activiti.json.
Второй - напрямую модифицировать xml-файл инициализации.Когда мы открываем конструктор, мы импортируем XML пустой ноды, и нам нужно добавить его в этот xml.
Например: мне нужно добавить обычный префикс после сгенерированного атрибута: normal:nodeType; мы добавляем это предложение в xml: xmlns:normal="Flowable.org/BPMN/normal…
<?xml version="1.0" encoding="UTF-8"?><bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:bpmn2="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:normal="http://flowable.org/bpmn/normal"xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd"id="sample-diagram" targetNamespace="http://activiti.org/bpmn"><bpmn2:process id="Process_1" isExecutable="true"></bpmn2:process><bpmndi:BPMNDiagram id="BPMNDiagram_1"><bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1"></bpmndi:BPMNPlane></bpmndi:BPMNDiagram></bpmn2:definitions>
После добавления его в xml, как добавить атрибуты, сгенерированные дизайнером?
На самом деле это очень просто, мы можем добавить префикс при обновлении свойств, например:
modeling.updateProperties(element, {'normal:nodeType': 'nodeType'})
1.5 Поскольку панель свойств настроена, как синхронизировать значение свойства панели свойств с XML и как синхронизировать панель свойств при изменении свойства на графике 🌟
Проект представляет собой архитектуру vue, поэтому в полной мере используйте преимущества vue: мониторинг
Часть кода выглядит следующим образом:
watch: {id (newVal, oldVal) {const bpmnModeler = this.bpmnModeler();const modeling = bpmnModeler.get('modeling');modeling.updateProperties(this.element,{'id':newVal});},name(newVal, oldVal){const bpmnModeler = this.bpmnModeler();const modeling = bpmnModeler.get('modeling');modeling.updateProperties(this.element,{'name':newVal});},// 监控element值,当发生改变时获取响应的属性element: {deep: true,immediate: true,handler(newVal, oldVal) {if(newVal) {const bpmnModeler = this.bpmnModeler(); // 我这里由于项目原因用的是方法获取bpmnModelerthis.id = newVal.businessObject.get('id');this.name = newVal.businessObject.get('name');// 初始化赋值const modeling = bpmnModeler.get('modeling');modeling.updateProperties(this.element,{'name':this.name});modeling.updateProperties(this.element,{'process_namespace':this.process_namespace});modeling.updateProperties(this.element,{'process_id':this.id});}}}}
Поскольку элемент является сложным типом, необходимо включить глубокий мониторинг.
Синхронный xml: используется метод моделирования.updateProperties, или вы можете использовать newVal.businessObject.$attrs['name'] = this.name для изменения
Измените панель свойств синхронизации графических свойств: поскольку элемент тщательно отслеживается, изменение графического свойства эквивалентно изменению элемента, поэтому оно будет отслеживаться здесь.
1.6 Как добавить слушателя 🌟
Вы можете просмотреть файл src\edit-modeler\components\CommonProps.vue
1.7 Как добавить пользовательские теги 🌟
Я предлагаю вам посмотреть: 
1.8 Как добавить несколько экземпляров 🌟
Вот метод добавления кода, вы можете напрямую щелкнуть ключ на графике, чтобы установить несколько экземпляров.
const moddle = bpmnModeler.get('moddle');loopCharacteristics = moddle.create('bpmn:MultiInstanceLoopCharacteristics');loopCharacteristics['collection'] = 'flow_assignee';loopCharacteristics['elementVariable'] = 'flow_assignee';let completionCondition = elementsHelper.createElement('bpmn:FormalExpression', { body: '${mulitiInstance.completeTask(execution,passResult,mulitiActivityId)}' }, loopCharacteristics, bpmnFactory);loopCharacteristics['completionCondition'] = completionCondition;modeling.updateProperties(element, { loopCharacteristics: loopCharacteristics });
1.9 Получить все узлы и корневые узлы 🌟
// 获取全部节点,也可以用来获取根节点
bpmnModeler._definitions.rootElements[0]
// 根节点
bpmnModeler.get('canvas').getRootElement()
1.10 Как добавить узлы к пирам узлов🌟
Например:
Добавлен BoundaryEvent к родственному SequenceFlow, просто получите все узлы под корневым узлом и вставьте в добавленный узел.
bpmnModeler._definitions.rootElements[0].flowElements.push(boundaryEvent);
1.11 Пустой xml, импортированный по умолчанию, присвойте динамическое значение идентификатору тега: нет процесса/сотрудничества для отображения 🌟
Мой пустой xml по умолчанию выглядит следующим образом:
Окончательный сгенерированный xml выглядит следующим образом:
Вы можете видеть, что идентификатор на картинке выше начинается с числа, что и вызвало это 😂😂😂
Это нормально, если оно начинается с буквы, например:id = `T-${uuidv4()}`;
Здесь должны быть аплодисменты 👏👏👏
1.12 Отслеживание процесса BpmnViewer показывает блок-схему, но блок-схема заблокирована🌟
Добавьте следующий код для решения
const currentViewbox = this.bpmnViewer.get('canvas').viewbox() const widthWindow = window.outerWidth; const heightWindow = window.outerHeight; const elementMid = { x: widthWindow / 2, y: heightWindow / 2 } this.bpmnViewer.get('canvas').viewbox({ x: elementMid.x - currentViewbox.width / 2, y: elementMid.y - currentViewbox.height / 2, width: currentViewbox.width, height: currentViewbox.height }) const width = document.getElementById('canvas').offsetWidth this.bpmnViewer.get('canvas').zoom(width / this.width)
1.13 В xml есть два одинаковых атрибута 🌟
Если вы находитесь в расширенном файле xxx.json, таком как файл activiti.json, вы настраиваете атрибут flowable:assignee пользовательской задачи в файле json, этот атрибут будет добавлен в бизнес-объект, тогда, если мы хотим передать бизнес-объект .Если атрибут attrs\['flowable:assignee'\] изменен, атрибут будет добавлен в businessObject.Attrs ниже этого, поэтому при создании xml будут сгенерированы два
1.14 Очистите холст
bpmnModeler.clear()
1.15 Установить поток по умолчанию
const newDefaultFlow = elementRegistry.get(element.id).businessObject;
modeling.updateProperties(targetElement, { default: newDefaultFlow });
1.16 Основной вызов подпроцесса
Атрибут flowable: CalledElementType="id" в узле активности может быть либо идентификатором, либо ключом. Идентификатор представляет собой идентификатор в таблице определения процесса, а ключ — это ключевое поле в таблице определения.
1.17 Отключить некоторые операции с холстом
const bpmnModeler = new BpmnModeler({
container: '#canvas',
additionalModules:[
BpmnModeler, {
paletteProvider:['value',''], // 禁用左面板
labelEditingProvider:['value', ''], // 禁用编辑
contextPadProvider: ['value', ''], // 禁用点击出现的contextPad
bendpoints: [ 'value', {} ], // 禁止流程线变换waypoints
zoomScroll:['value',''],// 禁止画布滚动
moveCanvas:['value',''],// 禁止拖拽
}
],
height: '400px'
});
Если серверная часть передает интерфейсу файл json, а не xml, смело возвращайтесь 🤔️🤔️🤔️
1.18 Скрыть значок bpmnjs
.bjs-powered-by {
display:none !important;
}
или:
// 删除 bpmn logo bpmn.io官方要求不给删或者隐藏,否则侵权 内部使用const bjsIoLogo = document.querySelector('.bjs-powered-by');while (bjsIoLogo.firstChild) { bjsIoLogo.removeChild(bjsIoLogo.firstChild);}
1.19 Автоматическое завершение тайм-аута
xml выглядит следующим образом:
<sequenceFlow id="Flow_1hu7yoy" sourceRef="Activity_1ig8oe5" targetRef="Event_1xmxdxy" />
<serviceTask id="Activity_1ig8oe5">
<incoming>Flow_1tvddwv</incoming>
<outgoing>Flow_1hu7yoy</outgoing>
</serviceTask>
<boundaryEvent id="Event_1bi4wq0" attachedToRef="Activity_1ig8oe5">
<timerEventDefinition id="TimerEventDefinition_0wsqmm3" />
</boundaryEvent>
Добавьте атрибут времени в timerEventDefinition, и все в порядке.
2 Реализация внутренних действий
В частности, как создать среду Activiti, я считаю, что каждый может найти ее на Baidu, я только расскажу, как сделать bpmn-js совместимым с Activiti.
3.1 Разбор файлов BPMN
На рисунке показано, как файл процесса в формате XML развертывается в ядре за несколько больших шагов.
3.2 Начните с загрузки xml из внешнего интерфейса и сохранения его во внутреннем интерфейсе.
HTTP-запрос будет содержать два основных параметра: bpmn_xml и svg_xml.
Поскольку activiti сохраняет файлы json в базе данных, нам нужно преобразовать файл bpmn_xml в json.
Метод преобразования, предоставленный activiti, меня не удовлетворяет.Я настроил метод преобразования и парсер.Официальный Activiti также позволяет настроить парсер.
Первый метод:
public static JsonNode converterXmlToJson(String bpmnXml) {
// 创建转换对象
BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
// XMLStreamReader读取XML资源
XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
StringReader stringReader = new StringReader(bpmnXml);
XMLStreamReader xmlStreamReader = null;
try {
xmlStreamReader = xmlInputFactory.createXMLStreamReader(stringReader);
} catch (XMLStreamException e) {
e.printStackTrace();
}
// UserTaskXMLConverter类是我自定义的
BpmnXMLConverter.addConverter(new UserTaskXMLConverter());
// 把xml转换成BpmnModel对象
BpmnModel bpmnModel = bpmnXMLConverter.convertToBpmnModel(xmlStreamReader);
// BpmnJsonConverter类是我自定义的
// 创建转换对象
BpmnJsonConverter bpmnJsonConverter = new BpmnJsonConverter();
// 把BpmnModel对象转换成json
JsonNode jsonNodes = bpmnJsonConverter.convertToJson(bpmnModel);
// 返回的json会被保存到数据库中
return jsonNodes;
}
В приведенном выше коде используется функция взаимного преобразования объекта BpmnModel и XML, предоставляемая модулем Activiti activiti-bpmn-converter. Путем создания объекта класса org.activiti.bpmn.converter.BpmnXMLConverter и вызова соответствующего метода обмен между объектом BpmnModel и XML может быть реализован Операция преобразования.
первый, пользовательский класс UserTaskXMLConverter связан с тем, что мое событие пользовательской задачи имеет настраиваемые атрибуты; при преобразовании xml в BpmnModel, если это событие пользовательской задачи, оно будет передано моему пользовательскому классу UserTaskXMLConverter
потомПреобразуйте BpmnModel в json, обратите внимание, что все атрибуты хранятся в каждом файле bpmnModel.attributes.
3.3 Пользовательский файл BpmnJsonConverter
Класс BpmnJsonConverter предоставляется в модуле activiti-json-converter, предоставленном Activiti, давайте сравним мои собственные и официальные.
Обнаружено, что в статике наших пользовательских классов есть несколько классов, начинающихся с Custom.Как следует из названия, эти классы являются классами преобразования для пользовательских задач, процессов и шлюзов.
В: Зачем настраивать эти классы?
отвечать:
1. 因为前端自定义属性(例如:多实例属性、默认流程属性)使用官方的toBpmnModel转换是会丢失自定义属性的,我们自定义类主要是将自定义属性放在attribute中,并且转换多实例属性为Activiti的BPMN规范接受。
2. convertElementToJson时加上自定义的属性键值
Код, связанный с преобразованием настраиваемых атрибутов пользовательской задачи:
// 多实例类型
String multiInstanceType = getPropertyValueAsString(PROPERTY_MULTIINSTANCE_TYPE, elementNode);
// 通过权重
String multiInstanceCondition = getPropertyValueAsString(PROPERTY_MULTIINSTANCE_CONDITION, elementNode);
if (StringUtils.isNotEmpty(multiInstanceType) && !"none".equalsIgnoreCase(multiInstanceType)) {
String name = getPropertyValueAsString(PROPERTY_NAME, elementNode);
MultiInstanceLoopCharacteristics multiInstanceObject = new MultiInstanceLoopCharacteristics();
if ("sequential".equalsIgnoreCase(multiInstanceType)) {
multiInstanceObject.setSequential(true);
} else {
multiInstanceObject.setSequential(false);
}
if (StringUtils.isNotEmpty(multiInstanceCondition)) {
try {
Integer.valueOf(multiInstanceCondition);
} catch (Exception ex) {
throw new WorkflowApiException(name + "配置成了会签,但通过权重不是一个整数");
}
multiInstanceObject.setCompletionCondition("${nextTaskEvaluator.isComplete(execution," + multiInstanceCondition + ")}");
} else {
throw new WorkflowApiException(name + "配置成了会签,但没有配置通过权重");
}
}
Процессор синтаксического анализа 3.4 Bpmn
Activiti поддерживает возможность участия пользовательского процессора синтаксического анализа BPMN (BpmnParseHandler) в анализе файлов ресурсов BPMN. Пользовательский процессор синтаксического анализа BPMN можно вызвать после анализа элемента (элемента) или после его анализа. В пользовательском процессоре синтаксического анализа мы можем изменить свойства некоторых объектов BPMN.
Добавление обработчиков синтаксического анализа BPMN позволяет настроить свойства «preBpmnParseHandlers» и «postBpmnParseHandlers» в файле конфигурации механизма Activiti. В приведенном ниже коде добавляется процессор синтаксического анализа для каждого из типов Pre (до) и Post (после).
Вышеприведенный код добавляет два типа процессоров парсинга BPMN.Причина разделения типов состоит в том, чтобы разделить типы процессоров более подробно; процессоры типа Pre всегда выполняются в первую очередь, то есть они определены во всех файлах процессов. Перед наземным элементом, а обработчик типа Post размещается в последнем исполнении, то есть после всех элементов, определенных в файле процесса. Если обработчик синтаксического анализа имеет определенные требования к порядку, его можно отличить по типам Pre и Post.
резюме
В общем, полная разработка пока довольно трудоемка, и нужно иметь определенное представление о bpmn-js и активити и иметь определенную долю терпения.
Ла-ла-ла~~, я закончила писать, я снова счастливая маленькая фея.