前因
最近在準備開源一款流程引擎項目,主要包含 流程設計器 表單設計器 流程引擎,碰見了一個問題 開發過程中 經常需要拓展節點或節點元素,因爲bpmn規範可能不滿足實際項目需求。記錄一下 解決思路。
先上效果圖
涉及技術
前端使用 bpmn.js
- 拓展 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
}
]
},
- 通過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.$attrs[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
}
- 前端將這個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()
}
- 後臺解析結果,即可拿到對應拓展屬性與業務對接
附上後臺讀取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;
}