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