bpmn-js 流程设计器 与 flowable/activiti 拓展的可行性研究

前因

最近在准备开源一款流程引擎项目,主要包含 流程设计器 表单设计器 流程引擎,碰见了一个问题 开发过程中 经常需要拓展节点或节点元素,因为bpmn规范可能不满足实际项目需求。记录一下 解决思路。

先上效果图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

涉及技术

前端使用 bpmn.js

  1. 拓展 flowable.json或者 activiti.json,新增我们 拓展的节点及元素。
{
  "name": "Flowable",
  "uri": "http://flowable.org/bpmn",
  "prefix": "flowable",
  "xml": {
    "tagAlias": "lowerCase"
  },
  "associations": [],
  "types": [
    {
       // 拓展节点名称
      "name": "CustomProperties",
      "superClass": [
        "Element"
      ],
      "meta": {
      	// * 表示所有bpmn节点都可继承该属性
        "allowedIn": [
          "*"
        ]
      },
      "properties": [
       // 拓展属性
         {
          "name": "values",
          "type": "CustomProperty",
          "isMany": true
        },
        {
          "name": "userIdList",
          "isAttr": true,
          "type": "String"
        },
		{
          "name": "userNameList",
          "isAttr": true,
          "type": "String"
        },
		{
          "name": "assigneeField",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "handlerStrategy",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "roleGroupCode",
          "isAttr": true,
          "type": "String"
        },
		{
          "name": "roleCode",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "findUserType",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "combineType",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "relationNodeId",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "actionList",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "taskType",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "nodeType",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "isSequential",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "proportion",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "expression",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "skipExpression",
          "isAttr": true,
          "type": "String"
        },{
          "name": "formName",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "selectFormKey",
          "isAttr": true,
          "type": "String"
        },
        {
          "name": "selectPath",
          "isAttr": true,
          "type": "String"
        }
        
      ]
    },
    {
      "name": "CustomProperty",
      "superClass": [
        "Element"
      ],
      "properties": [
        {
          "name": "id",
          "type": "String",
          "isAttr": true
        },
        {
          "name": "name",
          "type": "String",
          "isAttr": true
        },
        {
          "name": "value",
          "type": "String",
          "isAttr": true
        }
      ]
    },
  1. 通过bpmn.js 新增或者修改对应节点元素XML
   // 先判断当前element 是否包含 flowable:CustomProperties ,如果包含 则找出来更新对应属性,如果不存在,则创建后, 在更新整个 extensionElements 即可
 createOrUpdateCustomProperties(property, value) {
      const that = this
      const bpmnModeler = that.bpmnModeler()
      const bpmnFactory = bpmnModeler.get('bpmnFactory')
      let extensionElements = bpmnHelper.getPropertie(that.element, 'extensionElements')
      if (!extensionElements) {
        extensionElements = elementHelper.createElement('bpmn:ExtensionElements', null, this.element, bpmnFactory)
      }
      const length = extensionElements.get('values').length
      let customProperties
      let customPropertiesIndex = -1
      for (let i = 0; i < length; i++) {
        if (extensionElements.get('values')[i] && extensionElements.get('values')[i].$type === 'flowable:CustomProperties') {
          customProperties = extensionElements.get('values')[i]
          customPropertiesIndex = i
        }
      }
      if (!customProperties) {
        customProperties = elementHelper.createElement('flowable:CustomProperties', null, this.element, bpmnFactory)
      }

      const data = {}
      data[property] = value
      customProperties[property] = value

      if (customPropertiesIndex > -1) {
        extensionElements.get('values')[customPropertiesIndex] = customProperties
      } else {
        extensionElements.get('values').push(customProperties)
      }
      const modeling = bpmnModeler.get('modeling')
      // 更新
      modeling.updateProperties(this.element, {
        extensionElements: extensionElements
      })
    }
elementHelper 如下:
'use strict'

var ElementHelper = {}
module.exports = ElementHelper

/**
 * Creates a new element and set the parent to it
 *
 * @method ElementHelper#createElement
 *
 * @param {String} elementType of the new element
 * @param {Object} properties of the new element in key-value pairs
 * @param {moddle.object} parent of the new element
 * @param {BpmnFactory} factory which creates the new element
 *
 * @returns {djs.model.Base} element which is created
 */
ElementHelper.createElement = function(elementType, properties, parent, factory) {
  var element = factory.create(elementType, properties)
  element.$parent = parent
  return element
}

  1. 前端将这个xml 文件 传递给后端,后端可以通过如下代码解析:
  BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
        byte[] bytes = processXml.getBytes();
        ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
        XMLInputFactory xif = XMLInputFactory.newInstance();
        InputStreamReader in = null;
        try {
            in = new InputStreamReader(inputStream, "UTF-8");
            XMLStreamReader xtr = xif.createXMLStreamReader(in);
            BpmnModel bpmnModel = bpmnXMLConverter.convertToBpmnModel(xtr);
            // 注意 这个 bpmnModel 已经包含了我们刚才定义的属性
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (XMLStreamException e) {
          e.printStackTrace()
        }
  1. 后台解析结果,即可拿到对应拓展属性与业务对接
    在这里插入图片描述
附上后台读取Utils 方法
    public static final String CUSTOME_EXTENSIONELEMENT = "customProperties";

    /**
     * 功能描述: 从 flowElement 获取 指定名称的 拓展元素
     *
     *
     * @param flowElement 元素
     * @param extensionElementName 拓展元素名称
     * @return : org.flowable.bpmn.model.ExtensionElement
     * @author : zhoulin.zhu
     * @date : 2020/6/19 18:28
     */
    public static ExtensionElement getExtensionElementFromFlowElementByName(FlowElement flowElement, String extensionElementName) {

        if (flowElement == null) {
            return null;
        }
        if (StringUtils.isEmpty(extensionElementName)) {
            extensionElementName = CUSTOME_EXTENSIONELEMENT;
        }
        Map<String, List<ExtensionElement>> extensionElements = flowElement.getExtensionElements();
        for (Map.Entry<String, List<ExtensionElement>> stringEntry : extensionElements.entrySet()) {
            if (stringEntry.getKey().equals(extensionElementName)) {
                for (ExtensionElement extensionElement : stringEntry.getValue()) {
                    if (extensionElement.getName().equals(extensionElementName)) {
                        return extensionElement;
                    }
                }
            }
        }

        return null;
    }

    /**
     * 功能描述: 从拓展元素 获取 拓展 属性值
    /**
     * 功能描述: 从拓展元素 获取 拓展 属性值
     *
     *
     * @param extensionElement 拓展元素
     * @param attributesName 属性名称
     * @return : java.lang.String
     * @author : zhoulin.zhu
     * @date : 2020/6/19 18:30
     */
    public static  String getAttributesFromExtensionElementByName(ExtensionElement extensionElement, String attributesName) {

        if (extensionElement == null
                || StringUtils.isEmpty(attributesName)) {
            return null;
        }
        Map<String, List<ExtensionAttribute>> stringListMap = extensionElement.getAttributes();
        for (Map.Entry<String, List<ExtensionAttribute>> listEntry : stringListMap.entrySet()) {
            if (listEntry.getKey().equals(attributesName)) {
                return listEntry.getValue() != null && listEntry.getValue().size() > 0 ? listEntry.getValue().get(0).getValue() : null;
            }
        }
        return null;
    }

源代码地址

  1. 前端项目.
  2. 后端项目

引用

链接: link.
[1]:bpmn.js.
[2]: 表单设计器: 表单设计器.

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐