前因
最近在准备开源一款流程引擎项目,主要包含 流程设计器 表单设计器 流程引擎,碰见了一个问题 开发过程中 经常需要拓展节点或节点元素,因为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[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;
}