flowable每个节点自带的属性是有限的,而在实际业务中可能会遇到一些比较适合配置到各个节点属性,但是自带属性里面又没有的,所以这个时候我们就需要自己自定义属性了,这里主要记录下自己处理的过程,方便以后遇到该问题好查阅。
修改前端
修改文件:stencilset_bpmn.json
新增2个属性:
<!--新增2属性-->
{
"name" : "taskApprovalOperateConfigPackage",
"properties" : [ {
"id" : "taskApprovalOperateNode",
"type" : "Text",
"title" : "审批操作配置",
"value" : "",
"description" : "The descriptive name of the BPMN element.",
"popular" : true
} ]
},{
"name" : "returnSpecifiedStepConfigPackage",
"properties" : [ {
"id" : "returnSpecifiedStepNode",
"type" : "Text",
"title" : "退回指定节点",
"value" : "",
"description" : "The descriptive name of the BPMN element.",
"popular" : true
} ]
}
<!--配置给usertask节点-->
{
"type" : "node",
"id" : "UserTask",
"title" : "User task",
"description" : "A manual task assigned to a specific person",
"view" : "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n xmlns:svg=\"http://www.w3.org/2000/svg\"\n xmlns:oryx=\"http://www.b3mn.org/oryx\"\n xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n\n width=\"102\"\n height=\"82\"\n version=\"1.0\">\n <defs></defs>\n <oryx:magnets>\n \t<oryx:magnet oryx:cx=\"1\" oryx:cy=\"20\" oryx:anchors=\"left\" />\n \t<oryx:magnet oryx:cx=\"1\" oryx:cy=\"40\" oryx:anchors=\"left\" />\n \t<oryx:magnet oryx:cx=\"1\" oryx:cy=\"60\" oryx:anchors=\"left\" />\n \t\n \t<oryx:magnet oryx:cx=\"25\" oryx:cy=\"79\" oryx:anchors=\"bottom\" />\n \t<oryx:magnet oryx:cx=\"50\" oryx:cy=\"79\" oryx:anchors=\"bottom\" />\n \t<oryx:magnet oryx:cx=\"75\" oryx:cy=\"79\" oryx:anchors=\"bottom\" />\n \t\n \t<oryx:magnet oryx:cx=\"99\" oryx:cy=\"20\" oryx:anchors=\"right\" />\n \t<oryx:magnet oryx:cx=\"99\" oryx:cy=\"40\" oryx:anchors=\"right\" />\n \t<oryx:magnet oryx:cx=\"99\" oryx:cy=\"60\" oryx:anchors=\"right\" />\n \t\n \t<oryx:magnet oryx:cx=\"25\" oryx:cy=\"1\" oryx:anchors=\"top\" />\n \t<oryx:magnet oryx:cx=\"50\" oryx:cy=\"1\" oryx:anchors=\"top\" />\n \t<oryx:magnet oryx:cx=\"75\" oryx:cy=\"1\" oryx:anchors=\"top\" />\n \t\n \t<oryx:magnet oryx:cx=\"50\" oryx:cy=\"40\" oryx:default=\"yes\" />\n </oryx:magnets>\n <g pointer-events=\"fill\" oryx:minimumSize=\"50 40\">\n\t<rect id=\"text_frame\" oryx:anchors=\"bottom top right left\" x=\"1\" y=\"1\" width=\"94\" height=\"79\" rx=\"10\" ry=\"10\" stroke=\"none\" stroke-width=\"0\" fill=\"none\" />\n\t<rect id=\"bg_frame\" oryx:resize=\"vertical horizontal\" x=\"0\" y=\"0\" width=\"100\" height=\"80\" rx=\"10\" ry=\"10\" stroke=\"#bbbbbb\" stroke-width=\"1\" fill=\"#f9f9f9\" />\n\t\t<text \n\t\t\tfont-size=\"12\" \n\t\t\tid=\"text_name\" \n\t\t\tx=\"50\" \n\t\t\ty=\"40\" \n\t\t\toryx:align=\"middle center\"\n\t\t\toryx:fittoelem=\"text_frame\"\n\t\t\tstroke=\"#373e48\">\n\t\t</text>\n\t\n\t<g id=\"userTask\" transform=\"translate(3,3)\">\n\t\t<path oryx:anchors=\"top left\"\n \t\tstyle=\"fill:#d1b575;stroke:none;\"\n \t\t d=\"m 1,17 16,0 0,-1.7778 -5.333332,-3.5555 0,-1.7778 c 1.244444,0 1.244444,-2.3111 1.244444,-2.3111 l 0,-3.0222 C 12.555557,0.8221 9.0000001,1.0001 9.0000001,1.0001 c 0,0 -3.5555556,-0.178 -3.9111111,3.5555 l 0,3.0222 c 0,0 0,2.3111 1.2444443,2.3111 l 0,1.7778 L 1,15.2222 1,17 17,17\" \n />\n\t\t\n\t</g>\n \n\t<g id=\"parallel\">\n\t\t<path oryx:anchors=\"bottom\" fill=\"none\" stroke=\"#bbbbbb\" d=\"M46 70 v8 M50 70 v8 M54 70 v8\" stroke-width=\"2\" />\n\t</g>\n\t\n\t<g id=\"sequential\">\n\t\t<path oryx:anchors=\"bottom\" fill=\"none\" stroke=\"#bbbbbb\" stroke-width=\"2\" d=\"M46,76h10M46,72h10 M46,68h10\"/>\n\t</g>\n\t\n\n\t<g id=\"compensation\">\n\t\t<path oryx:anchors=\"bottom\" fill=\"none\" stroke=\"#bbbbbb\" d=\"M 62 74 L 66 70 L 66 78 L 62 74 L 62 70 L 58 74 L 62 78 L 62 74\" stroke-width=\"1\" />\n\t</g>\n </g>\n</svg>",
"icon" : "activity/list/type.user.png",
"groups" : [ "Activities" ],
"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", "skipexpressionpackage", "categorypackage" ,"dueTaskHandlerStrategyNodepackage","taskApprovalOperateConfigPackage","returnSpecifiedStepConfigPackage"],
"hiddenPropertyPackages" : [ ],
"roles" : [ "Activity", "sequence_start", "sequence_end", "ActivitiesMorph", "all" ]
}
前端修改之后页面的效果
image
这样配好之后我们去下载xml的时候会发现没有这两个新增的属性,这个时候在流程实例流转的时候也是获取不到新增属性的值的。
image
是因为flowable并没有支持自定义属性的存储,所以这个时候就要自己对自定义属性进行解析了。
后端修改
-
我们可以从页面找到下载的请求路径。可以看到是app/rest/models/**
image
因为是源码我这边为了尽量不改他的源码,所以自己修改了请求地址,自己写了下载的实现。
- 修改url-config跳转地址:app/rest/models/** 改成 XX/app/rest/models/**
getModelBpmn20ExportUrl: function (modelId) {
return FLOWABLE.CONFIG.contextRoot + '/XX/app/rest/models/' + modelId + '/bpmn20?version=' + Date.now();
},
- 新建ModelController 继承 AbstractModelBpmnResource:
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.flowable.ui.modeler.rest.app;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URLEncoder;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.ui.common.service.exception.BadRequestException;
import org.flowable.ui.common.service.exception.BaseModelerRestException;
import org.flowable.ui.common.service.exception.InternalServerErrorException;
import org.flowable.ui.modeler.domain.AbstractModel;
import org.flowable.ui.modeler.domain.Model;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.flowable.ui.modeler.service.XXModelServiceImpl;
/**
* @author
*/
@RestController
@RequestMapping("/xx/app")
public class ModelController extends AbstractModelBpmnResource {
private static final Logger LOGGER = LoggerFactory.getLogger(ModelController.class);
@Autowired
protected XXModelServiceImpl xxModelService;
/**
* GET /rest/models/{modelId}/bpmn20 -> Get BPMN 2.0 xml
*/
@RequestMapping(value = "/rest/models/{processModelId}/bpmn20", method = RequestMethod.GET)
public void getProcessModelBpmn20Xml(HttpServletResponse response, @PathVariable String processModelId) throws IOException {
LOGGER.info("开始下载xml文件1");
if (processModelId == null) {
throw new BadRequestException("No process model id provided");
}
Model model = xxModelService.getModel(processModelId);
generateBpmn20Xml(response, model);
}
protected void generateBpmn20Xml(HttpServletResponse response, AbstractModel model) {
String name = model.getName().replaceAll(" ", "_") + ".bpmn20.xml";
String encodedName = null;
try {
encodedName = "UTF-8''" + URLEncoder.encode(name, "UTF-8");
} catch (Exception e) {
LOGGER.warn("Failed to encode name " + name);
}
String contentDispositionValue = "attachment; filename=" + name;
if (encodedName != null) {
contentDispositionValue += "; filename*=" + encodedName;
}
response.setHeader("Content-Disposition", contentDispositionValue);
if (model.getModelEditorJson() != null) {
try {
ServletOutputStream servletOutputStream = response.getOutputStream();
response.setContentType("application/xml");
BpmnModel bpmnModel = xxModelService.getBpmnModel(model);
byte[] xmlBytes = xxModelService.getBpmnXML(bpmnModel);
BufferedInputStream in = new BufferedInputStream(new ByteArrayInputStream(xmlBytes));
byte[] buffer = new byte[8096];
while (true) {
int count = in.read(buffer);
if (count == -1) {
break;
}
servletOutputStream.write(buffer, 0, count);
}
// Flush and close stream
servletOutputStream.flush();
servletOutputStream.close();
} catch (BaseModelerRestException e) {
throw e;
} catch (Exception e) {
LOGGER.error("Could not generate BPMN 2.0 XML", e);
throw new InternalServerErrorException("Could not generate BPMN 2.0 xml");
}
}
}
}
- 新建XXModelServiceImpl 继承 ModelServiceImpl
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.flowable.ui.modeler.service;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.flowable.ui.modeler.service.ModelServiceImpl;
import org.flowable.editor.language.json.converter.BpmnJsonConverter;
import org.flowable.editor.language.json.converter.XXBpmnJsonConverter;
import org.springframework.transaction.annotation.Transactional;
/**
* @author
*/
@Service("xxModelService")
@Primary
public class XXModelServiceImpl extends ModelServiceImpl {
protected BpmnJsonConverter bpmnJsonConverter = new XXBpmnJsonConverter();
}
- 新建XXBpmnJsonConverter 继承 BpmnJsonConverter
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.flowable.editor.language.json.converter;
import org.flowable.editor.language.json.converter.XXCustomizeUserTaskJsonConverter;
import org.flowable.editor.language.json.converter.BpmnJsonConverter;
/**
* @author
*/
public class XXBpmnJsonConverter extends BpmnJsonConverter {
static {
convertersToBpmnMap.put(STENCIL_TASK_USER,XXCustomizeUserTaskJsonConverter.class);
}
}
- 新建自定义userTaskjson解析器XXCustomizeUserTaskJsonConverter 继承UserTaskJsonConverter
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.flowable.editor.language.json.converter;
import java.util.Map;
import org.flowable.editor.language.json.converter.UserTaskJsonConverter;
import org.flowable.editor.language.json.converter.BaseBpmnJsonConverter;
import org.flowable.bpmn.model.BaseElement;
import org.flowable.editor.language.json.converter.ActivityProcessor;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElementsContainer;
import com.fasterxml.jackson.databind.node.ArrayNode;
import org.flowable.bpmn.model.FlowElement;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.flowable.bpmn.model.UserTask;
import org.flowable.bpmn.model.CustomProperty;
import org.apache.commons.lang3.StringUtils;
import org.flowable.editor.language.json.converter.util.CollectionUtils;
import org.flowable.editor.constants.StencilConstants;
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Arrays;
import org.flowable.bpmn.model.ExtensionAttribute;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.flowable.editor.language.json.converter.util.ExtensionAttributeUtils;
/**
* @author
*/
public class XXCustomizeUserTaskJsonConverter extends UserTaskJsonConverter {
private static final Logger LOGGER = LoggerFactory.getLogger(XXCustomizeUserTaskJsonConverter.class);
//任务逾期策略
private static final String TASK_HANDLER_STRATEGY_NODE="duetaskhandlerstrategynode";
private static final String TASK_HANDLER_STRATEGY_NODE_KEY="dueTaskHandlerStrategyNode";
//审批操作配置
private static final String TASK_APPROVAL_OPERATE_NODE="taskapprovaloperatenode";
private static final String TASK_APPROVAL_OPERATE_NODE_KEY="taskApprovalOperateNode";
//可退回操作
private static final String TASK_HANDLER_STRATEGY_STEP_NODE="returnspecifiedstepnode";
private static final String TASK_HANDLER_STRATEGY_STEP_KEY="returnSpecifiedStepNode";
public static void fillBpmnTypes(
Map<Class<? extends BaseElement>, Class<? extends BaseBpmnJsonConverter>> convertersToJsonMap) {
convertersToJsonMap.put(UserTask.class, XXCustomizeUserTaskJsonConverter.class);
}
@Override
protected FlowElement convertJsonToElement(JsonNode elementNode, JsonNode modelNode,
Map<String, JsonNode> shapeMap) {
FlowElement flowElement = super.convertJsonToElement(elementNode, modelNode, shapeMap);
LOGGER.info("进入自定义属性解析");
if(flowElement instanceof UserTask){
ObjectMapper objectMapper = new ObjectMapper();
UserTask userTask = (UserTask) flowElement;
try {
LOGGER.info("节点:" + objectMapper.writeValueAsString(userTask));
}catch (JsonProcessingException e) {
LOGGER.error("节点序列化异常.");
}
String taskHandlerStrategyNode = getPropertyValueAsString(TASK_HANDLER_STRATEGY_NODE, elementNode);
LOGGER.info("新增自定义属性,任务处理策略[" + TASK_HANDLER_STRATEGY_NODE + "]=" + taskHandlerStrategyNode);
Map<String,List<ExtensionAttribute>> atts = new HashMap<String,List<ExtensionAttribute>>();
ExtensionAttribute ea1 = ExtensionAttributeUtils.generate(TASK_HANDLER_STRATEGY_NODE_KEY,taskHandlerStrategyNode);
String taskApprovalOperate = getPropertyValueAsString(TASK_APPROVAL_OPERATE_NODE,elementNode);
LOGGER.info("新增自定义属性,任务审批结果["+TASK_APPROVAL_OPERATE_NODE+"]="+taskApprovalOperate);
ExtensionAttribute ea2 = ExtensionAttributeUtils.generate(TASK_APPROVAL_OPERATE_NODE_KEY,taskApprovalOperate);
atts.put("XX-FLOWABLE-EXT ",Arrays.asList(ea1,ea2));
String returnSpecifiedStep = getPropertyValueAsString(TASK_HANDLER_STRATEGY_STEP_NODE,elementNode);
LOGGER.info("新增自定义属性,任务审批结果["+TASK_HANDLER_STRATEGY_STEP_NODE+"]="+returnSpecifiedStep);
ExtensionAttribute ea3 = ExtensionAttributeUtils.generate(TASK_HANDLER_STRATEGY_STEP_KEY,returnSpecifiedStep);
atts.put("XX-FLOWABLE-EXT ",Arrays.asList(ea1,ea2,ea3));
flowElement.setAttributes(atts);
}
return flowElement;
}
}
- 创建ExtensionAttributeUtils 对 拓展属性进行操作
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.flowable.editor.language.json.converter.util;
import java.util.Collection;
import org.flowable.bpmn.model.ExtensionAttribute;
/**
* @author
*/
public class ExtensionAttributeUtils {
public static ExtensionAttribute generate(String key,String val){
ExtensionAttribute ea = new ExtensionAttribute();
ea.setNamespace("http://xx.com.cn");
ea.setName(key);
ea.setNamespacePrefix("XX");
ea.setValue(val);
return ea;
}
}
修改代码重新部署之后再下载xml:
image
可以看到新增属性再xml里面已经保存成功了。
发起一支流程,去实例中看看能不能取到该新增属性的值。
image