场景:flowable 正常使用的时候,每一版本的流程都是根据该版本的流程图进行执行的。然后 总有些不走寻常路的需求,比如:旧版本数据兼容新版本流程图。本章主要是对如何让旧流程兼容新流程的一种方法的描述
环境:
springboot:2.2.0.RELEASE
flowable:6.4.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));
    }
表关系

- 获取Bpmn数据


调用基本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);
- 在上面获取到流程图的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
以上就完成第一部分流程图升级
- 在流程图升级的时候,发现一个问题,在流程图替换成功后,旧流程的待办,节点不在新流程图中,会导致任务无法向下继续走,所以需要对流程图进行校验,由于不同场景校验标准不同,所以需要用策略 模式对校验方法进行抽象。
抽象接口: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<String> 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<String> 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<String> actElementIds = executionDtoMapper.findActIdFromProcessDefinitionId(oldProcessDefinitionId);
        BpmnModel newBpmnModel = repositoryService.getBpmnModel(newProcessDefinitionId);
        Set<String> 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<String> {
    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);
    }
}
最好做一个备份表,用于保存修改前的二进制数据,避免丢数据倒是流程图无法回滚。
