添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

springboot保存bpmn文件及用户自定义参数的保存与获取

假如有如下一份bpmn文件,其中usertask扩展了多个属性,那么如何对其进行保存和获取呢?

Tall is cheap, show you the code!

1.准备

假定bpmn如下:

实例流程

代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions
    xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:activiti="http://activiti.org/bpmn"
    xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
    xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
    xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://bpmn.io/schema/bpmn">
    <bpmn2:process id="p_1367770574455656450" name="jh0305" isExecutable="true">
        <bpmn2:documentation>jh0305</bpmn2:documentation>
        <bpmn2:startEvent id="Event_1" name="开始">
            <bpmn2:outgoing>SequenceFlow_11hdsvi</bpmn2:outgoing>
        </bpmn2:startEvent>
        <bpmn2:userTask id="UserTask_0ag1sf3" name="用户任务1" activiti:priority="0">
            <bpmn2:extensionElements>
                <activiti:customProperties assigneeType="0" />
            </bpmn2:extensionElements>
        </bpmn2:userTask>
        <bpmn2:sequenceFlow id="SequenceFlow_11hdsvi" sourceRef="Event_1" targetRef="UserTask_0ag1sf3" />
        <bpmn2:userTask id="Activity_10ovq4m" name="用户任务_2" activiti:formKey="" activiti:priority="1">
            <bpmn2:extensionElements>
                <activiti:customProperties assigneeType="1"/>
            </bpmn2:extensionElements>
        </bpmn2:userTask>
        <bpmn2:endEvent id="Event_0jg7oml" name="结束节点_0">
            <bpmn2:incoming>Flow_1hr1bdj</bpmn2:incoming>
        </bpmn2:endEvent>
        <bpmn2:sequenceFlow id="Flow_116afj0" sourceRef="UserTask_0ag1sf3" targetRef="Activity_10ovq4m" />
        <bpmn2:sequenceFlow id="Flow_1hr1bdj" sourceRef="Activity_10ovq4m" targetRef="Event_0jg7oml" />
    </bpmn2:process>
    <bpmndi:BPMNDiagram id="BPMNDiagram_p_1367770574455656450">
        <bpmndi:BPMNPlane id="BPMNPlane_p_1367770574455656450" bpmnElement="p_1367770574455656450">
            <bpmndi:BPMNEdge id="BPMNEdge_Flow_1hr1bdj" bpmnElement="Flow_1hr1bdj">
                <omgdi:waypoint x="790" y="258" />
                <omgdi:waypoint x="852" y="258" />
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge id="BPMNEdge_Flow_116afj0" bpmnElement="Flow_116afj0">
                <omgdi:waypoint x="620" y="258" />
                <omgdi:waypoint x="690" y="258" />
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_11hdsvi" bpmnElement="SequenceFlow_11hdsvi">
                <omgdi:waypoint x="448" y="258" />
                <omgdi:waypoint x="520" y="258" />
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNShape id="BPMNShape_Event_1" bpmnElement="Event_1">
                <omgdc:Bounds x="412" y="240" width="36" height="36" />
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape id="BPMNShape_UserTask_0ag1sf3" bpmnElement="UserTask_0ag1sf3">
                <omgdc:Bounds x="520" y="218" width="100" height="80" />
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape id="BPMNShape_Activity_10ovq4m" bpmnElement="Activity_10ovq4m">
                <omgdc:Bounds x="690" y="218" width="100" height="80" />
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape id="BPMNShape_Event_0jg7oml" bpmnElement="Event_0jg7oml">
                <omgdc:Bounds x="852" y="240" width="36" height="36" />
            </bpmndi:BPMNShape>
        </bpmndi:BPMNPlane>
    </bpmndi:BPMNDiagram>
</bpmn2:definitions>

注意:两个用户任务节点中的

<bpmn2:extensionElements>
    <activiti:customProperties assigneeType="0" />
</bpmn2:extensionElements>

activiti:customProperties即为自定义节点,其中有一个assigneeType属性

2.保存bpmn文件

使用了swagger和lombok

2.1 前端传输的对象模型

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
@Getter
@Setter
@ApiModel("流程模型对象")
public class BpmnModelVO implements Serializable {
    @ApiModelProperty("bpmn文件的xml字符串")
    String bpmnXml;
    @ApiModelProperty("流程名称")
    String name;
    @ApiModelProperty("流程的key")
    Long processKey;
    @ApiModelProperty("租户id")
    String tenantId;
    @ApiModelProperty(value = "模型id,新增时为空")
    String modelId;
}

2.2 自定义消息转换方法

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.activiti.bpmn.model.*;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public final class MyUserTaskJsonConverter extends UserTaskJsonConverter{
    private final String assigneeType = "assigneeType";
    private final String elememt = "customProperties";
    * 将bpmn元素转换成json
    @Override
    protected void convertElementToJson(ObjectNode propertiesNode, BaseElement baseElement) {
        super.convertElementToJson(propertiesNode, baseElement);
        UserTask userTask = (UserTask) baseElement;
        Map<String, List<ExtensionElement>> customerProperties = userTask.getExtensionElements();
        if (!CollectionUtils.isEmpty(customerProperties) && customerProperties.containsKey(elememt)) {
            ExtensionElement e = customerProperties.get(elememt).get(0);
            Map<String, List<ExtensionAttribute>> attributes = e.getAttributes();
            attributes.forEach((key, attr) -> {
                for (ExtensionAttribute extensionAttribute : attr) {
                    setPropertyValue(extensionAttribute.getName(), extensionAttribute.getValue(), propertiesNode);
    * 将json转换成bpmn元素
    @Override
    protected FlowElement convertJsonToElement(JsonNode elementNode, JsonNode modelNode, Map<String, JsonNode> shapeMap) {
        FlowElement flowElement = super.convertJsonToElement(elementNode,
                modelNode, shapeMap);
        //解析新增属性的业务逻辑
        UserTask userTask = (UserTask) flowElement;
        ExtensionElement ee = new ExtensionElement();
        ee.setName("activiti:" + elememt);
        ee.setNamespacePrefix("activiti");
        Map<String, List<ExtensionAttribute>> attributes = new HashMap<>();
        // 这里就是bpmn里自定义的assigneeType
        String propertyAssigneeType = getPropertyValueAsString(assigneeType, elementNode);
        if (StringUtils.hasLength(propertyAssigneeType)) {
            ExtensionAttribute assigneeTypeAttr = new ExtensionAttribute();
            assigneeTypeAttr.setName(assigneeType);
            assigneeTypeAttr.setValue(propertyAssigneeType);
            List<ExtensionAttribute> assigneeTypeList = new ArrayList<>(1);
            assigneeTypeList.add(assigneeTypeAttr);
            attributes.put(assigneeType, assigneeTypeList);
        ee.setAttributes(attributes);
        userTask.addExtensionElement(ee);
        return userTask;
}

2.3 实现BpmnJsonConverter,替换转换方法

public class CustomBpmnJsonConverter extends BpmnJsonConverter {
    public static void initCustomJsonConverter() {
        convertersToBpmnMap.put(STENCIL_TASK_USER, MyUserTaskJsonConverter.class);
        convertersToJsonMap.put(UserTask.class, MyUserTaskJsonConverter.class);
}

2.4 配置

@Configuration
public class ActivitiAutoConfiguration implements ProcessEngineConfigurationConfigurer {
    @Override
    public void configure(SpringProcessEngineConfiguration springProcessEngineConfiguration) {
        // 其他配置
        // 配置自定义用户任务解析器
        CustomBpmnJsonConverter.initCustomJsonConverter();
}

2.5 controller类

@RestController
@RequestMapping("model")
@Api(tags = "流程模型接口")
public class ModelController {
    private final RepositoryService repositoryService;
    private final ObjectMapper objectMapper;
    public ModelController(RepositoryService repositoryService, ObjectMapper objectMapper) {
        this.repositoryService = repositoryService;
        this.objectMapper = objectMapper;
    @PostMapping("/save")
    @ApiOperation("保存模型")
    public ResponseResult<String> saveBpmn(@RequestBody BpmnModelVO bpmn) {
        if (bpmn == null || StringUtils.isEmpty(bpmn.getBpmnXml()) || StringUtils.isEmpty(bpmn.getTenantId())
                || StringUtils.isEmpty(bpmn.getProcessKey()) || StringUtils.isEmpty(bpmn.getName()))
            return ResponseResult.error("参数不完整");
        Model modelData = null;
        if (StringUtils.hasLength(bpmn.getModelId()))
            modelData = repositoryService.getModel(bpmn.getModelId());
        if (modelData == null) {
            modelData = repositoryService.newModel();
        //与前端保持一致
        modelData.setKey("p_" + bpmn.getProcessKey());
        modelData.setName(bpmn.getName());
        modelData.setTenantId(bpmn.getTenantId());
        try {
            XMLInputFactory xif = XMLInputFactory.newInstance();
            InputStream is = new ByteArrayInputStream(bpmn.getBpmnXml().getBytes(StandardCharsets.UTF_8));
            XMLStreamReader xtr = xif.createXMLStreamReader(is);
            // 转为bpmnModel
            BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);
            // bpmnModel转json
            CustomBpmnJsonConverter converter = new CustomBpmnJsonConverter();
            ObjectNode jsonNodes = converter.convertToJson(bpmnModel);
            ObjectMapper objectMapper = new ObjectMapper();
            String jsonXml = objectMapper.writeValueAsString(jsonNodes);
            this.flowService.saveModel(repositoryService, modelData, jsonXml, bpmn.getProcessKey());
        } catch (XMLStreamException | JsonProcessingException e) {
            e.printStackTrace();
            return ResponseResult.error(e.getMessage());
        return ResponseResult.success(modelData.getId());
    @GetMapping("/getById/{modelId}")
    @ApiOperation("根据模型id获取模型bpmn的xml字符串")
    public ResponseResult<String> getBpmnXmnById(@ApiParam(value = "bpmn模型id", required = true) @PathVariable("modelId") String modelId) {
        Model modelData = repositoryService.getModel(modelId);
        if (modelData != null)
            try {
                byte[] modelEditorSource = repositoryService.getModelEditorSource(modelData.getId());
                ObjectNode modelNode = (ObjectNode) new ObjectMapper().readTree(modelEditorSource);
                CustomBpmnJsonConverter converter = new CustomBpmnJsonConverter();
                BpmnModel model = converter.convertToBpmnModel(modelNode);// 转model
                byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model);// 转xml
                return ResponseResult.success(new String(bpmnBytes));
            } catch (IOException e) {
                e.printStackTrace();
                return ResponseResult.error("获取失败");
        return ResponseResult.error("不存在该模型");
}

2.6 ResponseResult类

@Data
public class ResponseResult<T> {
	private int code;
	private String msg;
	private T data;
    private ResponseResult(){}
    public ResponseResult(int code, String msg) {
        this.code = code;
        this.msg = msg;
    private ResponseResult(ResultCode resultCode) {
        this.code = resultCode.getCode();
        this.msg = resultCode.getMsg();
    private ResponseResult(ResultCode resultCode, T data) {
        this.code = resultCode.getCode();
        this.msg = resultCode.getMsg();
        this.data = data;
    public static <T> ResponseResult<T> success() {
        return new ResponseResult<>(ResultCode.SUCCESS);
	public static <T> ResponseResult<T> success(T data) {
		return new ResponseResult<>(ResultCode.SUCCESS, data);
	public static <T> ResponseResult<T> error(ResultCode resultCode) {
		return new ResponseResult<>(resultCode);