flowable 中级 - 2. flowable 自定义xml扩展

1,624 阅读8分钟

1、简介

因为业务扩展,我们会对xml做自定义扩展,放存放的业务属性。这样做的好处是减少了扩展表。

以前很多需求都是通过扩展表来做,导致做库里面很多扩展表,在实际业务操作过程中也是频繁查询操作这些表

目前会通过扩展xml来存放以下相关业务

  1. 用户任务节点类型:填写节点、分派节点、审核节点
  2. 用户节点的审批类型:会签还是或签
  3. 不同人在不同节点的按钮权限
  4. 按钮的重命名

1.1 扩展基础

8.1. 自定义扩展
BPMN 2.0标准对流程的所有的参与者都很有用。最终用户不会因为依赖专有解决方案,而被供应商“绑架”。Flowable之类的开源框架,也可以提供与大型供应商的解决方案相同(经常是更好;-)的实现。有了BPMN 2.0标准,从大型供应商解决方案向Flowable的迁移,可以十分简单平滑。
 
缺点则是标准通常是不同公司(不同观点)大量讨论与妥协的结果。作为阅读BPMN 2.0 XML流程定义的开发者,有时会觉得某些结构或方法十分笨重。Flowable将开发者的感受放在最高优先,因此引入了一些Flowable BPMN扩展(extensions)。这些“扩展”并不在BPMN 2.0规格中,有些是新结构,有些是对特定结构的简化。
 
尽管BPMN 2.0规格明确指出可以支持自定义扩展,我们仍做了如下保证:
 
自定义扩展保证是在标准方式的基础上进行简化。因此当你决定使用自定义扩展时,不用担心无路可退(仍然可以用标准方式)。
 
使用自定义扩展时,总是通过flowable:命名空间前缀,明确标识出XML元素、属性等。请注意Flowable引擎也支持activiti:命名空间前缀。
 
因此是否使用自定义扩展,完全取决于你自己。有些其他因素会影响选择(图形化编辑器的使用,公司策略,等等)。我们提供扩展,只是因为相信标准中的某些地方可以用更简单或效率更高的方式处理。请不要吝啬给我们反馈你对扩展的评价(正面的或负面的),也可以给我们提供关于自定义扩展的新想法。说不定某一天,你的想法会成为标准的一部分!

​ 参考链接: bpmnCustomExtensions

2、整体逻辑

aaaaaaaa.png

如果模型保存时 前端提交的是flowable规范的xml(自己做了扩展),此时如果不做任何处理,最终模型保存到表 act_de_model 中的 model_editor_json 会丢失自定义扩展的部分。

当然如果前端如果直接提供json的话,那么在模型保存就直接调用model对象的setModelEditorJson方法设置即可。代替改动的是在模型部署的时候需要将json转化成支持扩展的xml

String json = changeXmlToJson(jsonXml,key);
// 不管是新增还是更新都需要设置setModelEditorJson, 否则引擎都会实际跳过真正的执行逻辑
model.setModelEditorJson(json);

如果在模型保存时传递的xml,那么在部署也通常传递xml,那么部署可以不做任何处理;引擎部署接受xml,里面只要符合flowable规范即可。但是模型保存不支持很多自定义的部分

3、重要实现代码

这里全部都按照模型保存前端给的是xml。我们需要处理自定义扩展的xml

3.1 xml到model的json

    /**
     * 模型保存一定需要是json对象,部署时直接存的是xml
     * xml转化成json对象:需要先将xml转化成BpmnModel对象, 再将BpmnModel转化成model(json)
     *
     * @param xml
     * @return
     */
    public String changeXmlToJson(String xml,String key) {
        try {
            XMLInputFactory xif = XmlUtil.createSafeXmlInputFactory();
            InputStreamReader xmlIn = new InputStreamReader(new ByteArrayInputStream(xml.getBytes()), "UTF-8");
            XMLStreamReader xtr = xif.createXMLStreamReader(xmlIn);
            BpmnModel bpmnModel = bpmnXmlConverter.convertToBpmnModel(xtr);
            if (CollectionUtils.isEmpty(bpmnModel.getProcesses())) {
                throw new BadRequestException("No process found in definition " );
            } else {
                if (bpmnModel.getLocationMap().size() == 0) {
                    BpmnAutoLayout bpmnLayout = new BpmnAutoLayout(bpmnModel);
                    bpmnLayout.execute();
                }
                bpmnModel.getProcesses().get(0).setId(key);
                ObjectNode modelNode = bpmnJsonConverter.convertToJson(bpmnModel);
                return modelNode.toString();
            }
        } catch (BadRequestException var14) {
            throw var14;
        } catch (Exception var15) {
            throw new BadRequestException("Import failed for "  + ", error message " + var15.getMessage());
        }
    }

这里的 bpmnJsonConverter 已经不是引擎的转化器了,而是我自己扩展的构造器

这里重点是 bpmnJsonConverter.convertToJson(bpmnModel);

/**
 * https://www.modb.pro/db/178699
 *
 * 解决自定义的的属性最终不能存到数据库
 *  xml <-> bpmnModel (没问题) <-> model (到model这一步丢失业务自定义的东西,回到bpmnModel也同样丢失东西)
 */
public class CustomBpmnJsonConverter extends BpmnJsonConverter {

    static {
        CustomUserTaskJsonConverter.customFillTypes(convertersToBpmnMap, convertersToJsonMap);
        CustomStartEventJsonConverter.customFillTypes(convertersToBpmnMap, convertersToJsonMap);
        CustomEndEventJsonConverter.customFillTypes(convertersToBpmnMap, convertersToJsonMap);
    }
}

可以看到 CustomBpmnJsonConverter 里面主要重写了 开始节点、用户任务节点、结束节点。我们那其中的用户任务节点来展示下

package com.shimao.iot.workorder.workflow.converter.modelJon;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
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 org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

import java.util.Map;

@Primary
@Component
public class CustomUserTaskJsonConverter extends UserTaskJsonConverter {

    //注入自定义CustomUserTaskJsonConverter
    static void customFillTypes(Map<String, Class<? extends BaseBpmnJsonConverter>> convertersToBpmnMap, Map<Class<? extends BaseElement>, Class<? extends BaseBpmnJsonConverter>> convertersToJsonMap) {
        fillJsonTypes(convertersToBpmnMap);
        fillBpmnTypes(convertersToJsonMap);
    }

    public static void fillJsonTypes(Map<String, Class<? extends BaseBpmnJsonConverter>> convertersToBpmnMap) {
        convertersToBpmnMap.put(STENCIL_TASK_USER, CustomUserTaskJsonConverter.class);
    }

    public static void fillBpmnTypes(Map<Class<? extends BaseElement>, Class<? extends BaseBpmnJsonConverter>> convertersToJsonMap) {
        convertersToJsonMap.put(UserTask.class, CustomUserTaskJsonConverter.class);
    }

    //重写UserTask Element和Json转换方法
    @Override
    protected void convertElementToJson(ObjectNode propertiesNode, BaseElement baseElement, BpmnJsonConverterContext converterContext) {
        super.convertElementToJson(propertiesNode, baseElement, converterContext);

        //获取扩展属性并进行设置
        UserTask userTask = (UserTask) baseElement;

        CommonJsonConverter.convertElementToJson(propertiesNode, userTask);
    }


    //重写Json和UserTask Element转换方法
    /**
     * 将Json转为UserTask中的Element
     *
     * JsonNode elementNode 的结构如下
     * {"bounds":{"lowerRight":{"x":460.0,"y":358.0},"upperLeft":{"x":360.0,"y":278.0}},"resourceId":"Activity_01w2bw5","childShapes":[],"stencil":{"id":"UserTask"},"properties":{"overrideid":"Activity_01w2bw5","name":"审批节点","usertaskassignment":{"assignment":{"type":"static","candidateGroups":[{"value":"role_26"}]}},"formkeydefinition":"824","formproperties":{"formProperties":[{"id":"input_1663641166000_56196","name":"单行文本","type":null,"expression":null,"variable":null,"default":null,"required":false,"readable":true,"writable":true},{"id":"textarea_1663641168000_1270","name":"多行文本","type":null,"expression":null,"variable":null,"default":null,"required":false,"readable":true,"writable":true}]},"customUserTaskType":"check","button":[{"name":"领取","code":"claim","sort":"1","default":"true","enable":"true","alias":"","owner":""},{"name":"通过","code":"check","sort":"1","default":"true","enable":"false","alias":"","owner":""},{"name":"拒绝","code":"check","sort":"1","default":"true","enable":"false","alias":"","owner":""},{"name":"转交","code":"transfer","sort":"1","default":"false","enable":"false","alias":"","owner":""}],"asynchronousdefinition":false,"exclusivedefinition":true,"isforcompensation":false,"tasklisteners":{"taskListeners":[]},"executionlisteners":{"executionListeners":[]}},"outgoing":[{"resourceId":"Flow_1wn5r4e"}]}
     *
     *
     * @param elementNode
     * @param modelNode
     * @param shapeMap
     * @param converterContext
     * @return
     */
    @Override
    protected FlowElement convertJsonToElement(JsonNode elementNode, JsonNode modelNode, Map<String, JsonNode> shapeMap, BpmnJsonConverterContext converterContext) {
        UserTask userTask = (UserTask)super.convertJsonToElement(elementNode, modelNode, shapeMap, converterContext);

        CommonJsonConverter.convertJsonToExtensionAttribute(elementNode, userTask);
        CommonJsonConverter.convertJsonToExtensionElement(elementNode, userTask);
        return userTask;
    }

}

这里的公共的CommonJsonConverter转换器具体如下

package com.shimao.iot.workorder.workflow.converter.modelJon;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.flowable.bpmn.model.BaseElement;
import org.flowable.bpmn.model.ExtensionAttribute;
import org.flowable.bpmn.model.ExtensionElement;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.editor.language.json.converter.BpmnJsonConverterUtil;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CommonJsonConverter {

    protected static ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 统一处理扩展属性的解析 放在 json对象
     * @param attributes 一个对象里面的所有元素,并列平铺放在同一级
     * @param propertiesNode
     * @return
     */
    private static ObjectNode handlerAttributeValue(Map<String, List<ExtensionAttribute>> attributes, ObjectNode propertiesNode){
        if (propertiesNode == null) {
            propertiesNode = objectMapper.createObjectNode();
        }
        for (Map.Entry<String, List<ExtensionAttribute>> entry : attributes.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue().get(0).getValue();
            propertiesNode.put(key, value);
        }
        return propertiesNode;
    }

    public static void convertElementToJson(ObjectNode propertiesNode, BaseElement baseElement) {
        // 处理自定义的属性
        Map<String, List<ExtensionAttribute>> attributes = baseElement.getAttributes();
        ObjectNode customExtensionAttributeNode = handlerAttributeValue(attributes, null);
        // 单独加一层 customExtensionAttribute, 方便后续统一解析 json 到 Element
        propertiesNode.set("customExtensionAttributes", customExtensionAttributeNode);

        // 处理自定义的 extensionElements
        // 目前是 <flowable:button> 标签
        ObjectNode customExtensionElements = objectMapper.createObjectNode();
        Map<String, List<ExtensionElement>> extensionElements = baseElement.getExtensionElements();
        for(Map.Entry<String, List<ExtensionElement>> entry: extensionElements.entrySet()){
            // 这一层读取的就是 button 类似的标签
            String attributeName = entry.getKey();
            List<ExtensionElement> attributeValueList = entry.getValue();

            // 这里的多个类似的 button 对象会被嵌套放在列表里面
            ArrayNode itemNodeList = objectMapper.createArrayNode();
            for(ExtensionElement item: attributeValueList) {
                // 处理 类似button  ExtensionElement 里面的属性
                Map<String, List<ExtensionAttribute>> extensionElementAttributes = item.getAttributes();
                ObjectNode itemNode = handlerAttributeValue(extensionElementAttributes, null);
                itemNodeList.add(itemNode);
            }
            customExtensionElements.set(attributeName, itemNodeList);
        }
        // 单独加一层 customExtensionElements, 方便后续统一解析 json 到 Element
        propertiesNode.set("customExtensionElements", customExtensionElements);
    }



    public static void convertJsonToExtensionAttribute(JsonNode elementNode, FlowElement flowElement) {
        // customExtensionAttributes 取出来后就是下面格式
        // {"customUserTaskType":"check"}
        JsonNode customExtensionAttributes = BpmnJsonConverterUtil.getProperty("customExtensionAttributes", elementNode);
        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> customExtensionAttributesMap = mapper.convertValue(customExtensionAttributes, new TypeReference<Map<String, Object>>(){});

        // 处理 FlowElement(UserTask、) 的扩展属性
        for (Map.Entry<String, Object> entry: customExtensionAttributesMap.entrySet()) {
            ExtensionAttribute customArrExtensionAttribute = new ExtensionAttribute();
            customArrExtensionAttribute.setName(entry.getKey());
            customArrExtensionAttribute.setValue(entry.getValue().toString());
            customArrExtensionAttribute.setNamespace("http://flowable.org/bpmn");
            customArrExtensionAttribute.setNamespacePrefix("flowable");
            flowElement.addAttribute(customArrExtensionAttribute);
        }
    }

    public static void convertJsonToExtensionElement(JsonNode elementNode, FlowElement flowElement){
        // customExtensionElements 取出来后就是下面格式
        // {"button":[{"name":"领取","code":"claim","sort":"1","default":"true","enable":"true","alias":"","owner":""},{"name":"通过","code":"check","sort":"1","default":"true","enable":"false","alias":"","owner":""},{"name":"拒绝","code":"check","sort":"1","default":"true","enable":"false","alias":"","owner":""},{"name":"转交","code":"transfer","sort":"1","default":"false","enable":"false","alias":"","owner":""}]}
        JsonNode customExtensionElements = BpmnJsonConverterUtil.getProperty("customExtensionElements", elementNode);

        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> customExtensionElementsMap = mapper.convertValue(customExtensionElements, new TypeReference<Map<String, Object>>(){});

        // 处理 UserTask 下的 ExtensionElement
        Map<String, List<ExtensionElement>> extensionElements = new HashMap<>(); // 自定义的扩展元素
        for (Map.Entry<String, Object> entryLabel: customExtensionElementsMap.entrySet()) { // entryLabel 表示定义了多少种类型
            List<Map<String, Object>> labelList = (List<Map<String, Object>>)entryLabel.getValue(); // 每种类型里面定义了多少个标签 如:<flowabe:button>
            List<ExtensionElement> extensionElementList = new ArrayList<>();
            for (Map<String, Object> label: labelList) { // 标签列表层
                ExtensionElement extensionElement = new ExtensionElement();
                Map<String, List<ExtensionAttribute>> attributes = new HashMap<>();
                for (Map.Entry<String, Object> labelAttribute: label.entrySet()) { // 具体属性层
                    ExtensionAttribute customArrExtensionAttribute = new ExtensionAttribute();
                    customArrExtensionAttribute.setName(labelAttribute.getKey());
                    customArrExtensionAttribute.setValue(labelAttribute.getValue().toString());

                    List<ExtensionAttribute> value = new ArrayList<>();
                    value.add(customArrExtensionAttribute);

                    attributes.put(labelAttribute.getKey(), value);
                }
                // 不加 下面两行,会导致xml在拼自定义标签时 没有<flowabe:> 这部分,直接就是<button>
                extensionElement.setNamespace("http://flowable.org/bpmn");
                extensionElement.setNamespacePrefix("flowable");
                extensionElement.setAttributes(attributes);
                extensionElement.setName(entryLabel.getKey());
                extensionElementList.add(extensionElement);
            }
            extensionElements.put(entryLabel.getKey(), extensionElementList);
        }
        flowElement.setExtensionElements(extensionElements);
    }
}

重写后就最终存储到表里面的 model_editor_json 字段已经有我们自定义的部分了

3.2 json到xml

从 model_editor_json 到 给前端展示的xml,这里model到bpmnModel就会少我们自定义的部分,所以也需要处理

Model model = modelService.getModel(procDefEntity.getModelId());

// 自行处理 Model 到 BpmnModel。里面主要解决转化过程中自定义部分丢失问题
BpmnModel bpmnModel = modelToBpmnModel(model); 

// byte[] bpmnBytes = modelService.getBpmnXML(model); 直接调用引擎的方法,通过model去获取xml会丢失我们自定义的标签,属性。需要自行处理
byte[] bpmnBytes = modelService.getBpmnXML(bpmnModel);
    /**
     * 将Model对象转化成BpmnModel对象。这两个是差异很大的对象
     *  引擎其实提供了 Model对象转化成BpmnModel。 但是在转化的过程中丢失子自定义部分,故此需要自定义
     * @param model
     * @return
     */
    public BpmnModel modelToBpmnModel(Model model){
        ConverterContext converterContext = new ConverterContext(modelService, objectMapper);
        try {
            ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree(model.getModelEditorJson());
            return bpmnJsonConverter.convertToBpmnModel(editorJsonNode, converterContext);
        } catch (Exception e) {
            log.error("Could not generate BPMN 2.0 model for {}", model.getId(), e);
            throw new InternalServerErrorException("Could not generate BPMN 2.0 model");
        }
    }

modelToBpmnModel 方法里面的重点还是使用了自己的构造器 。

重点 bpmnJsonConverter.convertToBpmnModel(editorJsonNode, converterContext);

以上整体就实现了自定义的扩展功能

博文更改日志

  • 2022-11-15 初步完成。1、2、3节里面的核心重点