由于工作需要给flowable工作流设计器添加自定义属性,以满足功能实现。所以这篇文章介绍下用flowable 开源的的flowable-ui 前端添加自定义属性,后端解析属性值的例子。
序号 | 技术点名称 | 版本 |
1 | Flowable | 6.8.0 |
使用的是flowable6.8.0 版的代码
GitHub - flowable/flowable-engine at flowable-release-6.8.0https://github.com/flowable/flowable-engine/tree/flowable-release-6.8.0然后依照下面链接启动flowable-ui项目
手把手教大家编译 flowable 源码-腾讯云开发者社区-腾讯云松哥最近正在录制 TienChin 项目视频~采用 Spring Boot+Vue3 技术栈,里边会涉及到各种好玩的技术,小伙伴们来和松哥一起做一个完成率超 90% 的项目,戳戳戳这里-->TienChin 项目配套视频来啦。----要说这个编译源码其实没什么技术含量,但是由于国内的网络问题,Spring 等各种常...https://cloud.tencent.com/developer/article/2108059
找到flowable-engine-flowable-6.8.0\modules\flowable-ui\flowable-ui-modeler-logic\src\main\resources\stencilset_bpmn.json 路径文件 这个json 就是设计器的所有内容,
\flowable-engine-flowable-6.8.0\modules\flowable-ui\flowable-ui-modeler-frontend\src\main\resources\static\modeler\i18n\zh-CN.json 文件是汉化文件
下面是stencilset_bpmn.json 的文件格式:
{
"title": "BPMN 2.0标准工具",
"namespace": "http://b3mn.org/stencilset/bpmn2.0#",
"description": "BPMN process editor",
"propertyPackages": [{
"name": "overrideidpackage",
"properties": [{
"id": "overrideid",
"type": "String",
"title": "Id",
"value": "",
"description": "元素的唯一标识.",
"popular": true
}]
}, {
"name": "namepackage",
"properties": [{
"id": "name",
"type": "String",
"title": "名称",
"value": "",
"description": "BPMN元素的描述名称.",
"popular": true,
"refToView": "text_name"
}]
}],
"stencils": [{
"type": "node",
"id": "UserTask",
"title": "用户活动",
"description": "分配给特定人的任务 ",
"view": "此处代码已省略,请查看本书配套资源内容",
"groups": ["活动列表"],
"propertyPackages": ["overrideidpackage", "namepackage",
"documentationpackage", "asynchronousdefinitionpackage",
"exclusivedefinitionpackage", "executionlistenerspackage",
"multiinstance_typepackage", "multiinstance_cardinalitypackage",
"multiinstance_collectionpackage", "multiinstance_variablepackage",
"multiinstance_conditionpackage", "isforcompensationpackage",
"usertaskassignmentpackage", "formkeydefinitionpackage",
"formreferencepackage", "duedatedefinitionpackage",
"prioritydefinitionpackage", "formpropertiespackage", "tasklistenerspackage"],
"hiddenPropertyPackages": [],
"roles": ["Activity", "sequence_start", "sequence_end", "ActivitiesMorph", "all"]
}]
}
从以上配置文件内容中可以看出,最顶层有五个元素:title,namespace,description,
propertyPackages和stencils,分别代表标题,命名空间,描述,属性集合,节点属性,其中
propertyPackages表示的是设计器下栏的各个属性。通过propertyPackages 可以设置属性的名称,样式,字段类型。
type 对应的是当前属性的类型,和flowable-engine-flowable-6.8.0\modules\flowable-ui\flowable-ui-modeler-frontend\src\main\resources\static\modeler\editor-app\configuration\properties.js
里面的内容一一对应
stencils.propertyPackages 的值可以表示哪些活动使用哪些属性。
就可以看到用户任务下有允许催办属性了。
上面我们完成了通过设置type 为boolean,添加允许催办属性,那么string,text属性大家也都可以在properties.js 文件中看到,按需添加即可。
如果是添加下拉框属性,我们可以仿照多实例的下拉框属性实现。
,{
"name" : "usertaskovertimehandlertypepackage",
"properties" : [{
"id" :"overtimehandlertype",
"type" : "fd-overtimehandlertype",
"title" : "逾期处理方式",
"value" : "0",
"description" : "逾期处理方式",
"popular" : true
}]
}
注意id 和type ,我们去properties.js 文件下添加该类
注意1:properties.js 文件下的自定义名称为: oryx-上面的id-上面的type
注意2:不要用驼峰格式命名。
这些步骤我都是抄多实例怎么实现的。
下面是html 的下拉框格式,ng-controller 是用的angular.js 语言 下面红框里的内容我们自己定义
在 flowable-engine-flowable-6.8.0\modules\flowable-ui\flowable-ui-modeler-frontend\src\main\resources\static\modeler\index.html 文件下设置自定义的js路径
下面图片是js的内容,注意除默认值外其他两个字段需要和第二步html 里的值保持一致。
重启服务就可以看到flowable-ui 界面上出现下拉框的属性了。如果是其他样式的属性,可以借鉴flowable-ui 中相同的样式是如何实现的。
package org.flowable.ui.application.converter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.flowable.bpmn.constants.BpmnXMLConstants;
import org.flowable.bpmn.model.*;
import org.flowable.editor.language.json.converter.BaseBpmnJsonConverter;
import org.flowable.editor.language.json.converter.BpmnJsonConverterContext;
import org.flowable.editor.language.json.converter.UserTaskJsonConverter;
import java.util.List;
import java.util.Map;
/**
* @author admin
*/
public class CustomUserTaskJsonConverter extends UserTaskJsonConverter {
/**
* 用户任务类型
*/
public static final String ACTIVE_TYPE = "activetype";
/**
* 允许撤回
*/
public static final String ALLOW_RECALL = "allowrecall";
/**
* 允许终止
*/
public static final String ALLOW_STOP = "allowstop";
/**
* 允许跳过
*/
public static final String ALLOW_SKIP = "allowskip";
/**
* 允许修改下一个节点参与者
*/
public static final String ALLOW_UPDATE_NEXT_ONE = "allowupdatenextone";
/**
* 允许修改后续节点参与者
*/
public static final String ALLOW_UPDATE_NEXT_ALL = "allowupdatenextall";
/**
* 允许上传附件
*/
public static final String ALLOW_UPLOAD = "allowupload";
/**
* 工作期限(单位:天)
*/
public static final String LIMIT_TIME = "limittime";
/**
* 逾期处理方式
*/
public static final String OVER_TIME_HANDLER_TYPE = "overtimehandlertype";
/**
* 预警提醒(逾期前天数)
*/
public static final String OVER_TIME_WARN_TIME = "overtimewarntime";
/**
* 预警提醒(逾期前天数)
*/
public static final String CIRCULATION_WARN_TIME = "circulationwarntime";
@Override
protected void convertElementToJson(ObjectNode propertiesNode, BaseElement baseElement, BpmnJsonConverterContext converterContext) {
super.convertElementToJson(propertiesNode, baseElement, converterContext);
UserTask userTask = (UserTask) baseElement;
//解析
Map> extensionElements = userTask.getExtensionElements();
if(extensionElements != null && extensionElements.containsKey(ACTIVE_TYPE)){
ExtensionElement e = extensionElements.get(ACTIVE_TYPE).get(0);
setPropertyValue(ACTIVE_TYPE, e.getElementText(), propertiesNode);
}
if(extensionElements != null && extensionElements.containsKey(ALLOW_RECALL)){
ExtensionElement e = extensionElements.get(ALLOW_RECALL).get(0);
setPropertyValue(ALLOW_RECALL, e.getElementText(), propertiesNode);
}
if(extensionElements != null && extensionElements.containsKey(ALLOW_STOP)){
ExtensionElement e = extensionElements.get(ALLOW_STOP).get(0);
setPropertyValue(ALLOW_STOP, e.getElementText(), propertiesNode);
}
if(extensionElements != null && extensionElements.containsKey(ALLOW_SKIP)){
ExtensionElement e = extensionElements.get(ALLOW_SKIP).get(0);
setPropertyValue(ALLOW_SKIP, e.getElementText(), propertiesNode);
}
if(extensionElements != null && extensionElements.containsKey(ALLOW_UPDATE_NEXT_ONE)){
ExtensionElement e = extensionElements.get(ALLOW_UPDATE_NEXT_ONE).get(0);
setPropertyValue(ALLOW_UPDATE_NEXT_ONE, e.getElementText(), propertiesNode);
}
if(extensionElements != null && extensionElements.containsKey(ALLOW_UPDATE_NEXT_ALL)){
ExtensionElement e = extensionElements.get(ALLOW_UPDATE_NEXT_ALL).get(0);
setPropertyValue(ALLOW_UPDATE_NEXT_ALL, e.getElementText(), propertiesNode);
}
if(extensionElements != null && extensionElements.containsKey(ALLOW_UPLOAD)){
ExtensionElement e = extensionElements.get(ALLOW_UPLOAD).get(0);
setPropertyValue(ALLOW_UPLOAD, e.getElementText(), propertiesNode);
}
if(extensionElements != null && extensionElements.containsKey(LIMIT_TIME)){
ExtensionElement e = extensionElements.get(LIMIT_TIME).get(0);
setPropertyValue(LIMIT_TIME, e.getElementText(), propertiesNode);
}
if(extensionElements != null && extensionElements.containsKey(OVER_TIME_HANDLER_TYPE)){
ExtensionElement e = extensionElements.get(OVER_TIME_HANDLER_TYPE).get(0);
setPropertyValue(OVER_TIME_HANDLER_TYPE, e.getElementText(), propertiesNode);
}
if(extensionElements != null && extensionElements.containsKey(OVER_TIME_WARN_TIME)){
ExtensionElement e = extensionElements.get(OVER_TIME_WARN_TIME).get(0);
setPropertyValue(OVER_TIME_WARN_TIME, e.getElementText(), propertiesNode);
}
if(extensionElements != null && extensionElements.containsKey(CIRCULATION_WARN_TIME)){
ExtensionElement e = extensionElements.get(CIRCULATION_WARN_TIME).get(0);
setPropertyValue(CIRCULATION_WARN_TIME, e.getElementText(), propertiesNode);
}
}
@Override
protected FlowElement convertJsonToElement(JsonNode elementNode, JsonNode modelNode, Map shapeMap, BpmnJsonConverterContext converterContext) {
FlowElement flowElement = super.convertJsonToElement(elementNode, modelNode, shapeMap, converterContext);
UserTask userTask = (UserTask) flowElement;
//解析自定义扩展属性
this.addExtansionPropertiesElement(userTask,elementNode,ACTIVE_TYPE);
this.addExtansionPropertiesElement(userTask,elementNode,ALLOW_RECALL);
this.addExtansionPropertiesElement(userTask,elementNode,ALLOW_STOP);
this.addExtansionPropertiesElement(userTask,elementNode,ALLOW_SKIP);
this.addExtansionPropertiesElement(userTask,elementNode,ALLOW_UPDATE_NEXT_ONE);
this.addExtansionPropertiesElement(userTask,elementNode,ALLOW_UPDATE_NEXT_ALL);
this.addExtansionPropertiesElement(userTask,elementNode,ALLOW_UPLOAD);
this.addExtansionPropertiesElement(userTask,elementNode,LIMIT_TIME);
this.addExtansionPropertiesElement(userTask,elementNode,OVER_TIME_HANDLER_TYPE);
this.addExtansionPropertiesElement(userTask,elementNode,OVER_TIME_WARN_TIME);
this.addExtansionPropertiesElement(userTask,elementNode,CIRCULATION_WARN_TIME);
return userTask;
}
private void addExtansionPropertiesElement(UserTask userTask, JsonNode elementNode, String name) {
ExtensionElement extensionElement = new ExtensionElement();
extensionElement.setName(name);
//BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX
extensionElement.setNamespacePrefix("model");
extensionElement.setNamespace(BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE);
String customProperties = getPropertyValueAsString(name, elementNode);
extensionElement.setElementText(customProperties);
userTask.addExtensionElement(extensionElement);
}
public static void fillTypes(Map>
convertersToBpmnMap, Map, Class extends
BaseBpmnJsonConverter>> convertersToJsonMap) {
fillJsonTypes(convertersToBpmnMap);
fillBpmnTypes(convertersToJsonMap);
}
public static void fillJsonTypes(Map>
convertersToBpmnMap) {
convertersToBpmnMap.put(STENCIL_TASK_USER, CustomUserTaskJsonConverter.class);
}
public static void fillBpmnTypes(Map, Class extends
BaseBpmnJsonConverter>> convertersToJsonMap) {
convertersToJsonMap.put(UserTask.class, CustomUserTaskJsonConverter.class);
}
}
package org.flowable.ui.application.converter;
import org.flowable.editor.language.json.converter.BpmnJsonConverter;
/**
* @author admin
*/
public class CustomBpmnJsonConverter extends BpmnJsonConverter {
static {
//这里可以加入所有自定义的属性内容
convertersToBpmnMap.put(STENCIL_TASK_USER, CustomUserTaskJsonConverter.class);
}
}
package org.flowable.ui.application;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.ExtensionElement;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.UserTask;
import org.flowable.editor.language.json.converter.BpmnJsonConverter;
import org.flowable.editor.language.json.converter.util.CollectionUtils;
import org.flowable.ui.application.converter.CustomBpmnJsonConverter;
import org.junit.jupiter.api.Test;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Slf4j
public class RunCustomJsonConverterDemo {
@Test
public void runCustomJsonConverterDemo() throws Exception {
//读取Flowable Modeler保存的Json
InputStream processModelJsonInputStream =
getClass().getClassLoader().getResourceAsStream("model-json/model.json");
ObjectMapper mapper = new ObjectMapper();
JsonNode processJsonNode = mapper.readTree(processModelJsonInputStream);
//获取流程模型的Json
//使用自定义转换类由Json转换为BpmnModel对象
BpmnJsonConverter bpmnJsonConverter = new CustomBpmnJsonConverter();
BpmnModel bpmnModel = bpmnJsonConverter.convertToBpmnModel(processJsonNode);
Process mainProcess = bpmnModel.getMainProcess();
UserTask userTask = (UserTask) mainProcess.getFlowElement("sid-DA2AB9F9-3CB2-40C5-945B-95B6DADFA1E1");
//查询用户任务对象并打印属性
Map> extensionElements = userTask.getExtensionElements();
List allowurgingExtension = extensionElements.get("allowrecall");
if (CollectionUtils.isNotEmpty(allowurgingExtension)) {
log.info("扩展属性activetype值为:{}", allowurgingExtension.get(0).getElementText());
}
byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(bpmnModel);// 转xml
log.info("bpmnBytes:"+new String(bpmnBytes));
}
}
其中model.json 是flowable-ui点击保存传过来的