Дизайнер процессов

Vue.js

Давно не обновлял статью.В то время,когда не обновлял статью,был в отступлении и практиковался,старался улучшить свои навыки.Ведь мой флаг в 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 Как добавить пользовательские теги 🌟

Я предлагаю вам посмотреть: ![пример пользовательской метамодели](GitHub.com/BPMN-IO/BPM…)

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 и активити и иметь определенную долю терпения.

Ла-ла-ла~~, я закончила писать, я снова счастливая маленькая фея.