flowable 旧版流程定义兼容新流程

场景:flowable 正常使用的时候,每一版本的流程都是根据该版本的流程图进行执行的。然后 总有些不走寻常路的需求,比如:旧版本数据兼容新版本流程图。本章主要是对如何让旧流程兼容新流程的一种方法的描述

环境
springboot:2.2.0.RELEASE
flowable:6.4.2

流程图示例场景

旧流程图
新流程图

操作:希望旧流程图的待办数据可以按照新流程图进行执行。

实现思路分析:

  1. 调用完成任务的时候,会在内存初始化流程图,然后根据任务信息,找到对应流程图的那个指定节点,再按照流程图的线条继续往下执行。

  2. 从获取流程定义 bpmnXML 的 源码中可以发现,围绕几个核心类:
    org.flowable.engine.impl.cmd.GetBpmnModelCmd
    org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntityImpl
    org.flowable.engine.impl.persistence.entity.DeploymentEntityImpl
    org.flowable.engine.impl.persistence.entity.ResourceEntityImpl
    根据上面的类,可以找到基本围绕下面这几张表

获取流程BpmnXML的方法

    @Test
    public void getBpmnXml(){
        String processDefinitionId = "test-0901-2:1:8";
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);

        byte[] bytes = modelService.getBpmnXML(bpmnModel);

        System.out.println();
        System.out.println(new String(bytes));
    }

表关系

数据关系图

  1. 获取Bpmn数据
ACT_GE_BYTEARRAY
ResourceEntityImpl

调用基本Api (flowable 底层中util需要在 org.flowable.common.engine.impl.interceptor.Command 作用域下才有效 )

        ProcessDefinitionEntity newProcessDefinitionEntity = CommandContextUtil.getProcessDefinitionEntityManager().findById(newProcessDefinitionId);

        ResourceEntityManager resourceEntityManager = CommandContextUtil.getResourceEntityManager();

        // 获取流
        ResourceEntity newResourceEntity = resourceEntityManager
                .findResourcesByDeploymentId(newProcessDefinitionEntity.getDeploymentId())
                .stream()
                .filter(ResourceEntity::isGenerated)
                .collect(Collectors.toList()).get(0);

  1. 在上面获取到流程图的xml,需要对把新流程图的 ID 替换回旧流程的,再插入到数据库,从源码中可以找到2个工具类 :
    org.flowable.bpmn.converter.BpmnXMLConverter
    org.flowable.editor.language.json.converter.BpmnJsonConverter
    通过这2个工具类就可以把流程图 在 BpmnModel 和 xml 中切换。

替换资源代码

        InputStream newInputStream = new ByteArrayInputStream(newResourceEntity.getBytes());
        BpmnModel newBpmnModel = bpmnXMLConverter.convertToBpmnModel(new InputStreamSource(newInputStream), true, true);
        InputStream oldInputStream = new ByteArrayInputStream(oldResourceEntity.getBytes());
        BpmnModel oldBpmnModel = bpmnXMLConverter.convertToBpmnModel(new InputStreamSource(oldInputStream), true, true);

        // 替换流程定义
        newBpmnModel.getMainProcess().setId(oldBpmnModel.getMainProcess().getId());

        ObjectNode objectNode = bpmnJsonConverter.convertToJson(newBpmnModel);
        BpmnModel jsonBpmnModel = bpmnJsonConverter.convertToBpmnModel(objectNode);
        byte[] newBpmnBytes = bpmnXMLConverter.convertToXML(jsonBpmnModel);

        oldResourceEntity.setBytes(newBpmnBytes);
        resourceEntityManager.update(oldResourceEntity, true);

修改BpmnModel的时候,不能直接修改bpmnXMLConverter.convertToXML(jsonBpmnModel),由于保存到数据库的流中的xml已经存在了部分 attribute,而源码 org.flowable.bpmn.converter.UserTaskXMLConverter#writeAdditionalAttributes在生成任务的时候会再次插入,重复插入会导致流程图解析失败。所以这里需要通过BpmnJsonConvertor中转一下。

UserTaskXMLConverter#writeAdditionalAttributes

以上就完成第一部分流程图升级

  1. 在流程图升级的时候,发现一个问题,在流程图替换成功后,旧流程的待办,节点不在新流程图中,会导致任务无法向下继续走,所以需要对流程图进行校验,由于不同场景校验标准不同,所以需要用策略 模式对校验方法进行抽象。

抽象接口:DeploymentChangedStrategy

/**
 * @ClassName: DeploymentChangedStrategy
 * @Author: ren
 * @Description:
 * @CreateTime: 2020/9/3 0003 上午 10:57
 * @Version:
 **/
public interface DeploymentChangedStrategy {

    /**
     *
     * 校验
     * @param oldProcessDefinitionId
     * @param newProcessDefinitionId
     * @return
     */
    boolean checkDeployEnabled(String oldProcessDefinitionId, String newProcessDefinitionId);

    /**
     *  开始操作
     * @param processDefinitionId
     */
    void before(String processDefinitionId);

    /**
     *  结束操作
     * @param processDefinitionId
     */
    void after(String processDefinitionId);

    /**
     * 策略名称
     * @return
     */
    String strategyName();
}

基于抽象接口进行的基本实现:AbstractDeploymentChangedStrategyImpl

package com.example.oldguy.modules.app.plugins.deploymentchanged;

import com.example.oldguy.common.utils.SpringContextUtils;
import org.flowable.bpmn.model.*;
import org.flowable.engine.RepositoryService;

import java.util.Set;
import java.util.stream.Collectors;

/**
 * @ClassName: AbstractDeploymentChangedStrategyImpl
 * @Author: ren
 * @Description:
 * @CreateTime: 2020/9/3 0003 上午 11:16
 * @Version:
 **/
public abstract class AbstractDeploymentChangedStrategyImpl implements DeploymentChangedStrategy{

    protected RepositoryService repositoryService;

    public AbstractDeploymentChangedStrategyImpl() {
        this.repositoryService = SpringContextUtils.getBean(RepositoryService.class);
    }

    @Override
    public void before(String processDefinitionId) {
        repositoryService.suspendProcessDefinitionById(processDefinitionId);
    }

    @Override
    public void after(String processDefinitionId) {
        repositoryService.activateProcessDefinitionById(processDefinitionId);
    }


    protected Set getFlowIdSet(BpmnModel oldBpmnModel) {

        return oldBpmnModel.getMainProcess().getFlowElements().stream()
                .filter(obj -> obj instanceof UserTask ||
                        obj instanceof ParallelGateway ||
                        obj instanceof InclusiveGateway ||
                        obj instanceof SubProcess ||
                        obj instanceof CallActivity
                )
                .map(FlowElement::getId)
                .collect(Collectors.toSet());
    }
}

全流程匹配策略:AbstractDeploymentChangedStrategyImpl

package com.example.oldguy.modules.app.plugins.deploymentchanged;

import com.example.oldguy.common.utils.SpringContextUtils;
import org.flowable.bpmn.model.*;
import org.flowable.engine.RepositoryService;

import java.util.Set;
import java.util.stream.Collectors;

/**
 * @ClassName: AbstractDeploymentChangedStrategyImpl
 * @Author: ren
 * @Description:
 * @CreateTime: 2020/9/3 0003 上午 11:16
 * @Version:
 **/
public abstract class AbstractDeploymentChangedStrategyImpl implements DeploymentChangedStrategy{

    protected RepositoryService repositoryService;

    public AbstractDeploymentChangedStrategyImpl() {
        this.repositoryService = SpringContextUtils.getBean(RepositoryService.class);
    }

    @Override
    public void before(String processDefinitionId) {
        repositoryService.suspendProcessDefinitionById(processDefinitionId);
    }

    @Override
    public void after(String processDefinitionId) {
        repositoryService.activateProcessDefinitionById(processDefinitionId);
    }


    protected Set getFlowIdSet(BpmnModel oldBpmnModel) {

        return oldBpmnModel.getMainProcess().getFlowElements().stream()
                .filter(obj -> obj instanceof UserTask ||
                        obj instanceof ParallelGateway ||
                        obj instanceof InclusiveGateway ||
                        obj instanceof SubProcess ||
                        obj instanceof CallActivity
                )
                .map(FlowElement::getId)
                .collect(Collectors.toSet());
    }
}

代办任务匹配策略:CreatedTaskCheckDeploymentChangedStrategyImpl

package com.example.oldguy.modules.app.plugins.deploymentchanged.impl;

import com.example.oldguy.common.utils.SpringContextUtils;
import com.example.oldguy.modules.app.dao.jpas.ExecutionDtoMapper;
import com.example.oldguy.modules.app.plugins.deploymentchanged.AbstractDeploymentChangedStrategyImpl;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.runtime.ExecutionQuery;

import java.util.List;
import java.util.Set;

/**
 * @ClassName: CreatedTaskCheckDeploymentChangedStrategyImpl
 * @Author: ren
 * @Description: 根据已经创建的任务进行
 * @CreateTime: 2020/9/3 0003 上午 10:59
 * @Version:
 **/
@Slf4j
public class CreatedTaskCheckDeploymentChangedStrategyImpl extends AbstractDeploymentChangedStrategyImpl {

    private ExecutionDtoMapper executionDtoMapper;

    public CreatedTaskCheckDeploymentChangedStrategyImpl() {
        super();
        executionDtoMapper = SpringContextUtils.getBean(ExecutionDtoMapper.class);
    }

    @Override
    public boolean checkDeployEnabled(String oldProcessDefinitionId, String newProcessDefinitionId) {

        log.info("旧版本流程升级-" + strategyName());

        List actElementIds = executionDtoMapper.findActIdFromProcessDefinitionId(oldProcessDefinitionId);

        BpmnModel newBpmnModel = repositoryService.getBpmnModel(newProcessDefinitionId);
        Set newFlowElementSet = getFlowIdSet(newBpmnModel);

        return newFlowElementSet.containsAll(actElementIds);
    }

    @Override
    public String strategyName() {
        return "正在执行任务节点校验";
    }
}

以上是策略模块的代码,下面是完成的Command代码

package com.example.oldguy.modules.app.plugins.deploymentchanged;

import com.example.oldguy.common.utils.SpringContextUtils;
import com.example.oldguy.modules.app.exceptions.FlowableRuntimeException;
import com.example.oldguy.modules.app.plugins.deploymentchanged.impl.AllElementCheckDeploymentChangedStrategyImpl;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.common.engine.impl.util.io.InputStreamSource;
import org.flowable.editor.language.json.converter.BpmnJsonConverter;
import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.flowable.engine.impl.persistence.entity.ResourceEntity;
import org.flowable.engine.impl.persistence.entity.ResourceEntityManager;
import org.flowable.engine.impl.util.CommandContextUtil;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
import java.util.stream.Collector;
import java.util.stream.Collectors;

/**
 * @ClassName: DeploymentChangedCmd
 * @Author: ren
 * @Description: 资源迁移
 * @CreateTime: 2020/8/29 0029 下午 5:01
 * @Version:
 **/
@Slf4j
public class DeploymentChangedCmd implements Command {

    private static BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
    private static BpmnJsonConverter bpmnJsonConverter = new BpmnJsonConverter();

    /**
     * 用于替换的流程定义
     */
    private String newProcessDefinitionId;

    /**
     * 被替换流程定义
     */
    private String oldProcessDefinitionId;

    private DeploymentChangedStrategy strategy;


    public DeploymentChangedCmd(String sourceProcessDefinitionId, String targetProcessDefinitionId) {
        this.newProcessDefinitionId = sourceProcessDefinitionId;
        this.oldProcessDefinitionId = targetProcessDefinitionId;
        this.strategy = new AllElementCheckDeploymentChangedStrategyImpl();
    }

    public DeploymentChangedCmd(String newProcessDefinitionId, String oldProcessDefinitionId, DeploymentChangedStrategy strategy) {
        this.newProcessDefinitionId = newProcessDefinitionId;
        this.oldProcessDefinitionId = oldProcessDefinitionId;
        this.strategy = strategy;
    }

    @Override
    public String execute(CommandContext commandContext) {

        strategy.before(oldProcessDefinitionId);

        if (!strategy.checkDeployEnabled(oldProcessDefinitionId, newProcessDefinitionId)) {
            strategy.after(oldProcessDefinitionId);
            throw new FlowableRuntimeException("在途流程不满足新流程定义-使用策略:" + strategy.strategyName());
        }

        ProcessDefinitionEntity newProcessDefinitionEntity = CommandContextUtil.getProcessDefinitionEntityManager().findById(newProcessDefinitionId);
        ProcessDefinitionEntity oldProcessDefinitionEntity = CommandContextUtil.getProcessDefinitionEntityManager().findById(oldProcessDefinitionId);

        ResourceEntityManager resourceEntityManager = CommandContextUtil.getResourceEntityManager();

        // 获取流
        ResourceEntity newResourceEntity = resourceEntityManager
                .findResourcesByDeploymentId(newProcessDefinitionEntity.getDeploymentId())
                .stream()
                .filter(ResourceEntity::isGenerated)
                .collect(Collectors.toList()).get(0);

        ResourceEntity oldResourceEntity = resourceEntityManager
                .findResourcesByDeploymentId(oldProcessDefinitionEntity.getDeploymentId())
                .stream()
                .filter(ResourceEntity::isGenerated)
                .collect(Collectors.toList()).get(0);

        InputStream newInputStream = new ByteArrayInputStream(newResourceEntity.getBytes());
        BpmnModel newBpmnModel = bpmnXMLConverter.convertToBpmnModel(new InputStreamSource(newInputStream), true, true);
        InputStream oldInputStream = new ByteArrayInputStream(oldResourceEntity.getBytes());
        BpmnModel oldBpmnModel = bpmnXMLConverter.convertToBpmnModel(new InputStreamSource(oldInputStream), true, true);

        // 替换流程定义
        newBpmnModel.getMainProcess().setId(oldBpmnModel.getMainProcess().getId());

        ObjectNode objectNode = bpmnJsonConverter.convertToJson(newBpmnModel);
        BpmnModel jsonBpmnModel = bpmnJsonConverter.convertToBpmnModel(objectNode);
        byte[] newBpmnBytes = bpmnXMLConverter.convertToXML(jsonBpmnModel);

        oldResourceEntity.setBytes(newBpmnBytes);
        resourceEntityManager.update(oldResourceEntity, true);

        // 清除缓存
        CommandContextUtil.getProcessEngineConfiguration().getProcessDefinitionCache().remove(oldProcessDefinitionId);
        log.info("清理缓存:" + oldProcessDefinitionId);

        strategy.after(oldProcessDefinitionId);
        return new String(newBpmnBytes);
    }
}

最好做一个备份表,用于保存修改前的二进制数据,避免丢数据倒是流程图无法回滚。

你可能感兴趣的:(flowable 旧版流程定义兼容新流程)