扩展Activiti-5.12轻松实现流程节点间自由跳转和任意驳回/撤回

由于项目需要,最近对开源工作流引擎Activiti-5.12的功能做了一下扩展,实现了以下功能:
1.自由流(流程节点间自由跳转和任意驳回/撤回)
2.流程会签任务串并行模式切换

[size=large][b]一、自由流[/b][/size]
在已有流程模型的的基础上,每个流程实例当前任务可以任意驳回/撤回或者向后续节点任意跳转而无需在相关的两个节点之间显示地画跳转路径(也就是所谓的“中国式”自由流),通过在Activiti的流程组件TaskService接口中增加以下两个方法来达到上述目的:
public void complateTask(String taskid,String destTaskKey)
public void complateTask(String taskid,Map<String,Object> vars,String destTaskKey)

这两个方法和以下原始方法相比较就是多了一个destTaskKey参数:
public void complateTask(String taskid)
public void complateTask(String taskid,Map<String,Object> vars)

destTaskKey参数告诉流程引擎在结束taskID任务后直接跳转到destTaskKey对应的任务节点,目前可以实现简单人工任务和会签人工任务的驳回/撤回或者向后连跳n个节点,暂不支持子流程任务和流程调用任务。

注意:我们只是对activiti进行了功能扩展,因此activiti原来所有功能没有受到任何影响,该怎么用还是怎么用,只是如果流程中临时有任意驳回/撤回需求或者任意跳转需求时(为了避免在流程图中画N X N条路径,同时也不想修改流程模型),就可以通过上述两个扩展接口轻松搞定。

另外在TaskService接口中增加了驳回到上个环节的默认接口:
/**
* 将当前任务驳回到上一个任务处理人处,并更新流程变量参数
* @param taskId
* @param variables
*/
void rejecttoPreTask(String taskId, Map<String, Object> variables);

/**
* 将当前任务驳回到上一个任务处理人处
* @param taskId
*/
void rejecttoPreTask(String taskId);

[size=large][b]2.流程会签任务串并行模式切换[/b][/size]
如果流程中存在多实例任务(会签任务),那么可以通过流程变量在处理任务时或者在发起流程实例的时候设置多实例任务执行的方式为串行还是并行。
[b]切换方式一[/b] 在发起流程时设置对应的会签任务的是串行还是并行模式:
Map variables= new HashMap();
variables.put("usertask1_user", Arrays.asList("user1".split(",")));
variables.put("usertask2_user", Arrays.asList("user2,user22,user23".split(",")));
variables.put("usertask3_user", Arrays.asList("user3,user32".split(",")));
variables.put("usertask4_user", Arrays.asList("user4,user42".split(",")));
params.put("usertask5_user", Arrays.asList("user5".split(",")));
variables.put("businessType", "test");
/**
* 会签任务usertask2流程模型中被定义并行模式,这里在流程实例启动时通过流程变量将会签任务usertask2的并行模式改为串行模式
* 然后该流程实例的对应会签任务usertask2将以串行方式执行
*/
variables.put("usertask2"+MultiInstanceActivityBehavior.multiInstanceMode_variable_const, MultiInstanceActivityBehavior.multiInstanceMode_sequential);
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey(process_key, businessKey, map);


[b]切换方式二[/b] 在完成任务时设置后续会签任务的是串行还是并行模式:
/**
* 会签任务usertask2在对应的流程实例启动时已经被切换为串行模式,这里在完成usertask1任务时通过流程变量将会签任务usertask2的串行模式再改为并行模式
* 然后该流程实例的对应会签任务usertask2将以并行方式执行
*/
Map variables= new HashMap(); variables.put("usertask2"+MultiInstanceActivityBehavior.multiInstanceMode_variable_const, MultiInstanceActivityBehavior.multiInstanceMode_parallel);
taskService.claim(taskId, username);//领取任务
taskService.complete(taskId, variables);//完成任务并设置后续会签任务的执行模式


变量的命名规范为:
* taskkey.bpmn.behavior.multiInstance.mode
* 取值范围为:
* parallel 对应于常量MultiInstanceActivityBehavior.multiInstanceMode_parallel
* sequential MultiInstanceActivityBehavior.multiInstanceMode_sequential
* 说明:taskkey为对应的任务的定义id,根据具体的会签任务来取值,.bpmn.behavior.multiInstance.mode部分是固定值,对应常量MultiInstanceActivityBehavior.multiInstanceMode_variable_const
*
* 会签多实例任务执行模式可以在设计流程时统一配置,在任务处理时每个实例中的会签任务都可以通过变量设置和改变串并行模式,变量可以启动流程实例时动态设置,也可以在上个活动任务完成时设置。

[size=large][b]3.小花絮[/b][/size]
为了在任务历史表act_hi_taskinst表delete_reason_字段的值更加具体和详细,特意为activiti增加了一个全局组件:
org.activiti.engine.impl.identity.UserInfoMap
public interface UserInfoMap {
String getUserName(String userAccount);//根据账号获取用户中文名称
Object getUserAttribute(String userAccount,String userAttribute);//根据账号获取用户的属性值
Object getUserAttributeByID(String userID,String userAttribute);//根据ID获取用户的属性值

}


UserInfoMap用来为流程引擎中的相关功能提供获取流程处理人各种和业务相关的属性的方法。通过在流程全局配置文件activiti.cfg.xml中为流程引擎配置UserInfoMap组件:
<property name="userInfoMap" class="com.frameworkset.platform.sysmgrcore.purviewmanager.PDPUserInfoMapImpl"/>


具体配置请查看案例文件:[url=https://github.com/yin-bp/activiti-engine-5.12/blob/master/src/test/resources/activiti.cfg.xml]activiti.cfg.xml[/url]

[url=https://github.com/yin-bp/activiti-engine-5.12/blob/master/src/main/java/org/activiti/engine/impl/identity/UserInfoMapImpl.java]org.activiti.engine.impl.identity.UserInfoMapImpl[/url]是UserInfoMap组件的默认实现。

以下是应用UserInfoMap组件后act_hi_taskinst表delete_reason_字段的几个值内容实例:
[b][提交-usertask1]任务被[张三-zhangsan]完成
[会签并行-usertask2]任务被[李四-lisi]转到节点[提交-usertask1]
[提交-usertask1]任务被[张三-zhangsan]完成
[会签并行-usertask2]任务被[张三-zhangsan]完成
[会签并行-usertask2]任务被[李四-lisi]完成
[会签并行-usertask2]任务被[王五-wangwu]完成
[驳回测试-usertask5]任务被[阿甘-agan]完成
[审批校验-usertask6]任务被[欧巴马-oubama]完成[/b]

[size=large][b]4.后记[/b][/size]

本次改造是基于[url=https://github.com/bbossgroups/bbossgroups-3.5/tree/bboss3.6.2]bboss 3.6.2[/url]分支和[url=https://github.com/yin-bp/activiti-engine-5.12]Activiti 5.12.0[/url]版本,都是目前两个开源项目的最新版本或者较新版本(activiti最新小版本为5.12.1)。

相关文档:
[url=http://yin-bp.iteye.com/blog/1506335]开源工作流引擎activiti与bboss整合使用方法浅析[/url]
<think>我们参考了引用[1][2]中的思路:Activiti6.0中实现任意节点跳转需要自定义命令(Command),并且由于Activiti6.0抛弃了PVM,所以操作对象是BPMN模型中的FlowNode。同时,引用[3]提供了一个删除执行实例(execution)的命令示例,这可以用于删除当前节点。 实现任意节点跳转的关键步骤: 1. 获取当前流程实例的当前节点(FlowNode)。 2. 获取目标节点(FlowNode)。 3. 删除当前节点上的所有活动(比如任务、定时器等)。 4.流程实例的当前活动节点设置为目标节点。 5. 继续执行流程(从目标节点开始流转)。 但是,Activiti6.0中,流程实例的执行是通过执行对象(Execution)来表示的。一个流程实例可能包含多个执行对象(例如并行网关分支)。因此,我们需要考虑多执行实例的情况。 以下是一个自定义命令的框架,用于实现节点跳转: 步骤: 1. 创建一个实现Command接口的类,例如JumpToNodeCmd。 2. 在execute方法中实现跳转逻辑。 注意:由于Activiti6.0使用了命令模式责任链模式,自定义命令可以无缝集成到引擎中。 代码示例(基于引用[1][2][3]的思路,结合Activiti6.0 API): ```java import org.activiti.bpmn.model.FlowNode; import org.activiti.bpmn.model.Process; import org.activiti.engine.ActivitiException; import org.activiti.engine.impl.interceptor.Command; import org.activiti.engine.impl.interceptor.CommandContext; import org.activiti.engine.impl.persistence.entity.ExecutionEntity; import org.activiti.engine.impl.persistence.entity.ExecutionEntityManager; import org.activiti.engine.impl.util.ProcessDefinitionUtil; import java.util.List; public class JumpToNodeCmd implements Command<Void> { private String executionId; // 当前执行实例ID private String targetNodeId; // 目标节点ID public JumpToNodeCmd(String executionId, String targetNodeId) { this.executionId = executionId; this.targetNodeId = targetNodeId; } @Override public Void execute(CommandContext commandContext) { // 获取执行实例管理器 ExecutionEntityManager executionEntityManager = commandContext.getExecutionEntityManager(); // 获取当前执行实例 ExecutionEntity executionEntity = executionEntityManager.findById(executionId); if (executionEntity == null) { throw new ActivitiException("执行实例不存在: " + executionId); } // 获取流程定义 Process process = ProcessDefinitionUtil.getProcess(executionEntity.getProcessDefinitionId()); // 获取目标节点 FlowNode targetNode = (FlowNode) process.getFlowElement(targetNodeId); if (targetNode == null) { throw new ActivitiException("目标节点不存在: " + targetNodeId); } // 1. 删除当前执行实例的当前活动(包括任务等) // 注意:这里需要删除当前执行实例及其执行实例(如果有的话) // 但注意,我们只跳转当前分支,所以这里只处理当前执行实例(以及它的执行实例?) // 实际情况可能更复杂,比如并行网关会有多个执行实例。这里我们假设只跳转单个分支(executionId对应的分支) // 删除当前执行实例的所有执行实例(递归删除) List<ExecutionEntity> childExecutions = executionEntityManager.findChildExecutionsByParentExecutionId(executionId); for (ExecutionEntity child : childExecutions) { // 递归删除执行实例(这里简化处理,直接删除所有执行实例) executionEntityManager.deleteChildExecutions(child, "跳转节点", true); } // 2. 将当前执行实例指向目标节点 executionEntity.setCurrentFlowElement(targetNode); // 3. 触发目标节点的执行(进入目标节点) targetNode.leave(executionEntity); return null; } } ``` 使用方式: ```java // 在服务层调用 String executionId = "当前执行实例ID"; String targetNodeId = "目标节点ID"; commandExecutor.execute(new JumpToNodeCmd(executionId, targetNodeId)); ``` 注意事项: 1. 权限控制:确保只有有权限的用户才能执行跳转。 2. 业务数据:跳转节点后,原节点产生的业务数据可能需要手动处理(如任务评论、附件等)。 3. 历史记录:跳转操作不会自动记录,可以考虑添加历史记录。 4. 多实例:上述代码未处理多实例(会签、循环等)的情况。对于多实例,需要额外处理(删除多实例的所有执行实例,并重新创建)。 5. 事件监听:跳转后,原节点的结束事件不会触发,目标节点的开始事件会触发(如果目标节点是活动节点,则进入时会触发执行监听器的start事件)。 由于多实例并行分支的情况比较复杂,上述代码是一个基础版本。在实际应用中,需要根据具体场景扩展。 另外,引用[1]中提到“自定义命令有一些使用限制”,所以使用前请充分测试。 如何获取当前执行实例ID? 在任务节点上,可以通过任务获取执行实例ID: ```java Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); String executionId = task.getExecutionId(); ``` 或者,在流程实例中,可以通过运行时查询执行实例。 补充:如果跳转的节点是用户任务,那么跳转后会自动创建新的任务。 关于回退:回退可以看作是一种特殊的跳转,即从当前节点跳转到之前的某个节点。因此,上述命令同样适用于回退。 但是,回退时可能需要考虑已执行节点的变量、历史等,这可能需要更复杂的处理。 总结:实现任意节点跳转的核心是自定义命令,通过改变执行实例的当前节点并触发离开操作,使流程转向目标节点。 相关问题: 1. 如何实现多实例任务的跳转? 2. 节点跳转后,流程变量如何处理? 3. Activiti6.0中如何获取流程历史节点? 如果需要更复杂的跳转(如支持多实例、并行网关等),建议参考flowable的跳转实现,或者使用Activiti扩展库。 最后,请注意,在生产环境中使用自定义跳转命令前,务必进行充分测试,以避免流程状态不一致。</think>在 Activiti 6.0 中实现任意节点跳转(包括回退)需要通过**自定义命令(Command)** 来实现。以下是基于您提供的引用资料实现的完整方案: ### 核心实现原理 1. **抛弃 PVM 模型**:Activiti 6.0 移除了 PVM(Process Virtual Machine),需直接操作 BPMN 模型元素(`FlowNode`) 2. **命令模式**:通过实现 `Command` 接口创建自定义跳转命令 3. **关键操作**: - 删除当前执行实例(execution) - 设置目标节点为当前活动节点 - 触发目标节点的执行 ### 完整实现代码 ```java import org.activiti.bpmn.model.*; import org.activiti.engine.impl.interceptor.Command; import org.activiti.engine.impl.interceptor.CommandContext; import org.activiti.engine.impl.persistence.entity.*; import org.activiti.engine.impl.util.ProcessDefinitionUtil; import java.io.Serializable; import java.util.*; public class JumpToNodeCmd implements Command<Void>, Serializable { private final String targetNodeId; // 目标节点ID private final String executionId; // 当前执行实例ID public JumpToNodeCmd(String executionId, String targetNodeId) { this.executionId = executionId; this.targetNodeId = targetNodeId; } @Override public Void execute(CommandContext commandContext) { // 1. 获取执行实例管理器 ExecutionEntityManager executionManager = commandContext.getExecutionEntityManager(); // 2. 获取当前执行实例 ExecutionEntity currentExecution = executionManager.findById(executionId); if (currentExecution == null) { throw new RuntimeException("执行实例不存在: " + executionId); } // 3. 获取流程定义 Process process = ProcessDefinitionUtil.getProcess(currentExecution.getProcessDefinitionId()); // 4. 获取目标节点 FlowNode targetNode = (FlowNode) process.getFlowElement(targetNodeId); if (targetNode == null) { throw new RuntimeException("目标节点不存在: " + targetNodeId); } // 5. 删除当前任务(关键步骤) deleteCurrentTasks(commandContext, currentExecution); // 6. 设置当前活动节点为目标节点 currentExecution.setCurrentFlowElement(targetNode); // 7. 触发目标节点的执行 targetNode.getOutgoingFlows().forEach(sequenceFlow -> { ExecutionEntity newExecution = executionManager.createChildExecution(currentExecution); newExecution.setCurrentFlowElement(sequenceFlow); newExecution.take(sequenceFlow); }); return null; } // 删除当前所有关联任务 private void deleteCurrentTasks(CommandContext commandContext, ExecutionEntity execution) { TaskEntityManager taskManager = commandContext.getTaskEntityManager(); List<TaskEntity> tasks = taskManager.findTasksByExecutionId(execution.getId()); tasks.forEach(task -> { // 删除任务关联的身份链接 commandContext.getIdentityLinkEntityManager() .deleteIdentityLinksByTaskId(task.getId()); // 删除任务实例 taskManager.delete(task); }); } } ``` ### 使用示例 ```java // 在服务层调用跳转命令 public void jumpToNode(String taskId, String targetNodeId) { // 1. 获取当前任务 Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); // 2. 执行自定义跳转命令 managementService.executeCommand(new JumpToNodeCmd( task.getExecutionId(), // 当前执行实例ID targetNodeId // 目标节点ID )); // 3. (可选)业务数据更新 businessService.updateFlowStatus(task.getProcessInstanceId(), "JUMPED"); } ``` ### 技术要点说明 1. **节点获取机制**: ```java Process process = ProcessDefinitionUtil.getProcess(definitionId); FlowNode targetNode = (FlowNode) process.getFlowElement(nodeId); ``` 直接通过 BPMN 模型获取节点元素[^2] 2. **执行实例处理**: ```java executionManager.createChildExecution(currentExecution); newExecution.take(sequenceFlow); ``` 创建新的执行实例接管流程[^3] 3. **任务清理**: ```java deleteIdentityLinksByTaskId(task.getId()); taskManager.delete(task); ``` 必须清理任务相关数据避免状态不一致 ### 使用注意事项 1. **节点类型限制**: - ✅ 支持跳转:用户任务/服务任务/排他网关 - ⚠️ 谨慎处理:并行网关/事件网关(需额外处理分支) - ❌ 不支持:开始事件(需重启流程实例) 2. **业务数据一致性**: ```java // 在跳转后更新业务状态 businessService.updateStatus(processInstanceId, "REJECTED"); ``` 3. **历史记录**: - 跳转前的任务会保留在 `ACT_HI_TASKINST` - 新任务将生成新记录 - 建议添加自定义监听器记录跳转日志 4. **多实例任务**: ```java // 需要特殊处理会签等场景 if (task.getScopeId() != null) { // 处理多实例任务 } ``` ### 典型应用场景 1. **审批回退**:主管 → 提交人 ```java jumpToNode(taskId, "submitTask"); ``` 2. **跳过环节**:直接跳过部门审核 ```java jumpToNode(taskId, "ceoApprove"); ``` 3. **流程重置**:错误处理 → 重新开始 ```java jumpToNode(taskId, "startEvent"); ``` > **重要提示**:在生产环境使用前,务必对以下场景进行测试: > - 并行网关分支跳转 > - 流程节点跳转 > - 包含补偿事件的处理 > - 高并发场景下的数据一致性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值