Activiti从当前任务任意回退至已审批任务

本文介绍了一种基于Activiti工作流引擎的任务回退方法,该方法通过直接操作数据库来实现任务状态的回退,包括更新当前任务为历史任务状态、调整任务参与者信息以及更改流程实例指向的任务节点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.impl.ProcessEngineImpl;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.db.DbSqlSession;
import org.activiti.engine.impl.db.DbSqlSessionFactory;
import org.activiti.engine.impl.db.ListQueryParameterObject;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.HistoricTaskInstanceEntity;
import org.activiti.engine.impl.persistence.entity.IdentityLinkEntity;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.rest.api.ActivitiUtil;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

/*
* 2013.01.25
*
* 任务回退
*
* 需求:从当前任务 任意回退至已审批任务
* 方法:通过activiti源代码里的sqlSession直接修改数据库
*
* 第一步 完成历史TASK覆盖当前TASK
* 用hi_taskinst修改当前ru_task
* ru_task.ID_=hi_taskinst.ID_
* ru_task.NAME_=hi_taskinst.NAME_
* ru_task.TASK_DEF_KEY_=hi_taskinst.TASK_DEF_KEY_
*
* 第二步
* 修改当前任务参与人列表
* ru_identitylink 用ru_task.ID_去ru_identitylink 索引
* ru_identitylink.TASK_ID_=hi_taskinst.ID_
* ru_identitylink.USER_ID=hi_taskinst.ASSIGNEE_
*
* 第三步修改流程记录节点 把ru_execution的ACT_ID_ 改为hi_taskinst.TASK_DEF_KEY_
*
* author:pvii007
* version:1.0
*/
public class DbTaskReturn
{
	public static final int I_NO_OPERATION = 0;

	public static final int I_DONE = 1;

	public static final int I_TASK_NOT_FOUND = 2;

	public static final int I_ROLLBACK = 3;

	/*
	* 实现回退方法
	*/
	public static int dbBackTo(String currentTaskId, String backToTaskId)
	{
		int result = DbTaskReturn.I_NO_OPERATION;
		SqlSession sqlSession = getSqlSession();
		TaskEntity currentTaskEntity = getCurrentTaskEntity(currentTaskId);
		HistoricTaskInstanceEntity backToHistoricTaskInstanceEntity = getHistoryTaskEntity(backToTaskId);
		if (currentTaskEntity == null || backToHistoricTaskInstanceEntity == null)
		{
			return DbTaskReturn.I_TASK_NOT_FOUND;
		}
		String processDefinitionId = currentTaskEntity.getProcessDefinitionId();
		String executionId = currentTaskEntity.getExecutionId();
		String currentTaskEntityId = currentTaskEntity.getId();
		String backToHistoricTaskInstanceEntityId = backToHistoricTaskInstanceEntity.getId();
		String backToTaskDefinitionKey = backToHistoricTaskInstanceEntity.getTaskDefinitionKey();
		String backToAssignee = backToHistoricTaskInstanceEntity.getAssignee();
		boolean success = false;
		try
		{
			// 1.
			StepOne_use_hi_taskinst_to_change_ru_task(sqlSession, currentTaskEntity, backToHistoricTaskInstanceEntity);
			// 2.
			StepTwo_change_ru_identitylink(sqlSession, currentTaskEntityId, backToHistoricTaskInstanceEntityId,
				backToAssignee);
			// 3.
			StepThree_change_ru_execution(sqlSession, executionId, processDefinitionId, backToTaskDefinitionKey);
			success = true;
		}
		catch (Exception e)
		{
			throw new ActivitiException("dbBackTo Exception", e);
		}
		finally
		{
			if (success)
			{
				sqlSession.commit();
				result = DbTaskReturn.I_DONE;
			}
			else
			{
				sqlSession.rollback();
				result = DbTaskReturn.I_ROLLBACK;
			}
			sqlSession.close();
		}
		return result;
	}

	private static void StepThree_change_ru_execution(SqlSession sqlSession, String executionId,
			String processDefinitionId, String backToTaskDefinitionKey) throws Exception
	{
		List<ExecutionEntity> currentExecutionEntityList = sqlSession.selectList("selectExecution", executionId);
		if (currentExecutionEntityList.size() > 0)
		{
			ActivityImpl activity = getActivitiImp(processDefinitionId, backToTaskDefinitionKey);
			Iterator<ExecutionEntity> execution = currentExecutionEntityList.iterator();
			while (execution.hasNext())
			{
				ExecutionEntity e = execution.next();
				e.setActivity(activity);
				p(sqlSession.update("updateExecution", e));
			}
		}
	}

	private static void StepTwo_change_ru_identitylink(SqlSession sqlSession, String currentTaskEntityId,
			String backToHistoricTaskInstanceEntityId, String backToAssignee) throws Exception
	{
		ListQueryParameterObject para = new ListQueryParameterObject();
		para.setParameter(currentTaskEntityId);
		List<IdentityLinkEntity> currentTaskIdentityLinkEntityList = sqlSession.selectList("selectIdentityLinksByTask",
			para);
		if (currentTaskIdentityLinkEntityList.size() > 0)
		{
			Iterator<IdentityLinkEntity> identityLinkEntityList = currentTaskIdentityLinkEntityList.iterator();
			IdentityLinkEntity identityLinkEntity;
			TaskEntity tmpTaskEntity;
			tmpTaskEntity = new TaskEntity();
			tmpTaskEntity.setId(backToHistoricTaskInstanceEntityId);
			while (identityLinkEntityList.hasNext())
			{
				identityLinkEntity = identityLinkEntityList.next();
				identityLinkEntity.setTask(tmpTaskEntity);
				identityLinkEntity.setUserId(backToAssignee);
				Map<String, Object> parameters = new HashMap<String, Object>();
				parameters.put("id", identityLinkEntity.getId());
				sqlSession.delete("deleteIdentityLink", parameters);
				sqlSession.insert("insertIdentityLink", identityLinkEntity);
			}
		}
	}

	private static void StepOne_use_hi_taskinst_to_change_ru_task(SqlSession sqlSession, TaskEntity currentTaskEntity,
			HistoricTaskInstanceEntity backToHistoricTaskInstanceEntity) throws Exception
	{
		sqlSession.delete("deleteTask", currentTaskEntity);
		currentTaskEntity.setName(backToHistoricTaskInstanceEntity.getName());
		currentTaskEntity.setTaskDefinitionKey(backToHistoricTaskInstanceEntity.getTaskDefinitionKey());
		currentTaskEntity.setId(backToHistoricTaskInstanceEntity.getId());
		sqlSession.insert("insertTask", currentTaskEntity);
	}

	public static void p(Object o)
	{
		System.out.println(o);
	}

	private static ActivityImpl getActivitiImp(String processDefinitionId, String taskDefinitionKey)
	{
		RepositoryService repositoryService = ActivitiUtil.getRepositoryService();
		ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
				.getDeployedProcessDefinition(processDefinitionId);
		List<ActivityImpl> activitiList = processDefinition.getActivities();
		boolean b;
		Object activityId;
		for (ActivityImpl activity : activitiList)
		{
			activityId = activity.getId();
			b = activityId.toString().equals(taskDefinitionKey);
			if (b)
			{
				return activity;
			}
		}
		return null;
	}

	private static TaskEntity getCurrentTaskEntity(String id)
	{
		return (TaskEntity) ActivitiUtil.getTaskService().createTaskQuery().taskId(id).singleResult();
	}

	private static HistoricTaskInstanceEntity getHistoryTaskEntity(String id)
	{
		return (HistoricTaskInstanceEntity) ActivitiUtil.getHistoryService().createHistoricTaskInstanceQuery()
				.taskId(id).singleResult();
	}

	private static SqlSession getSqlSession()
	{
		ProcessEngineImpl processEngine = (ProcessEngineImpl) ActivitiUtil.getProcessEngine();
		DbSqlSessionFactory dbSqlSessionFactory = (DbSqlSessionFactory) processEngine.getProcessEngineConfiguration()
				.getSessionFactories().get(DbSqlSession.class);
		SqlSessionFactory sqlSessionFactory = dbSqlSessionFactory.getSqlSessionFactory();
		return sqlSessionFactory.openSession();
	}
}
http://www.verydemo.com/demo_c161_i143918.html
<think>好的,我现在需要帮助用户解决一个关于Activiti流程设计的问题。用户的需求是创建一个包含并行网关的BPMN流程,具体流程是:开始 -> 用户提交材料 -> 并行网关 -> 两个审核员审核(如果任意一个不通过则回到用户提交材料节点)-> 并行网关结束 -> 管理员审核 -> 结束。用户需要完整的BPMN文件、流程图以及Java测试用例代码。 首先,我需要回顾之前用户的问题和我的回答。之前用户询问如何实现并行网关中任一分支不通过则流程停止,我提供了使用终止事件的解决方案。但这次用户的需求不同,当任一审核不通过时需要回到用户提交材料节点,而不是终止整个流程。这说明用户现在需要一个循环的流程,直到两个审核都通过为止。 接下来,我需要分析用户的需求。用户提到的流程中有并行网关,分裂成两个审核员审核任务。如果任意一个审核不通过,流程需要返回到用户提交材料的节点,重新开始流程。这涉及到并行网关的使用以及如何设置条件来循环回到之前的节点。 首先,我需要确定BPMN的结构。流程开始后,用户提交材料,然后进入并行网关分裂成两个审核任务。每个审核任务完成后需要进行条件判断,如果通过则继续,如果不通过则回到用户提交材料节点。并行网关之后是管理员审核,最后结束。 这里的关键点是如何处理并行网关的汇聚以及循环。通常,并行网关需要所有分支都到达才能汇聚,但用户的需求是任一不通过就循环,所以需要在每个分支上设置排他网关来判断是否通过。如果任一审核不通过,流程需要跳转回用户提交材料节点。这里可能需要使用中间捕获事件或事件子流程来实现循环,但用户的需求更倾向于直接通过序列流连接回到提交材料节点,但需要注意避免死循环。 另一个需要注意的点是,当流程回到用户提交材料节点时,可能需要重置或清理之前的状态,否则可能会导致流程变量冲突。此外,并行网关的汇聚需要正确配置,确保所有通过的分支都到达后才能进入管理员审核环节。 接下来,我需要设计BPMN文件的结构。用户提交材料是一个用户任务,之后是并行网关分裂成两个审核任务。每个审核任务后接排他网关判断是否通过,如果通过则流向汇聚网关,否则回到用户提交材料节点。汇聚网关之后是管理员审核,最后结束。 在BPMN中,并行网关的分裂和汇聚需要正确配对。汇聚网关会等待所有输入分支到达后才继续流程。但这里如果任一审核不通过,流程会回到提交材料节点,这意味着汇聚网关可能永远不会被触发,除非所有审核都通过。因此,汇聚网关必须正确设置,否则流程可能会卡住。 此外,循环回到用户提交材料节点可能会导致流程实例中的任务重复创建,需要确保Activiti能够正确处理循环,例如通过流程变量控制循环次数,避免无限循环。 接下来是编写BPMN XML文件。需要定义用户任务、并行网关、排他网关、序列流以及条件表达式。每个审核任务的排他网关需要根据流程变量(如audit1Passed、audit2Passed)判断是否通过。如果不通过,序列流指向用户提交材料节点;如果通过,则指向汇聚网关。 然后,绘制流程图,确保结构清晰,符号正确。使用BPMN工具如Eclipse插件或在线工具绘制,确保并行网关、排他网关和循环的箭头正确连接。 最后,编写Java测试用例代码,部署流程,启动流程实例,处理用户任务,设置审核结果,验证流程是否按预期循环或继续。需要处理并行任务的完成,设置流程变量,并检查流程是否进入正确的节点。 可能遇到的问题包括:并行网关汇聚的条件设置错误,导致流程无法继续;循环逻辑导致无限循环;流程变量未正确传递或重置。需要仔细测试每个分支的情况,确保审核通过和不通过的情况都能正确处理。 总结步骤:设计BPMN结构,编写XML,绘制流程图,编写Java代码测试,处理循环逻辑和条件判断,确保流程正确流转。</think>根据需求,我们将设计一个包含**并行网关循环审批**的流程,实现以下逻辑: ``` 开始 → 用户提交材料 → 并行网关 → 审核员A/审核员B → 并行网关 → 管理员审核 → 结束 ↑____________不通过时回退____________↓ ``` --- ### **一、BPMN 2.0 XML文件** ```xml <?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" targetNamespace="http://example.com/parallel-audit-process"> <process id="parallelAuditProcess" name="并行审核流程"> <!-- 开始事件 --> <startEvent id="start" name="开始"/> <!-- 用户提交材料 --> <userTask id="submitMaterial" name="用户提交材料" activiti:assignee="${initiator}"/> <sequenceFlow id="flow1" sourceRef="start" targetRef="submitMaterial"/> <!-- 进入并行网关 --> <parallelGateway id="forkGateway" name="拆分审核"/> <sequenceFlow id="flow2" sourceRef="submitMaterial" targetRef="forkGateway"/> <!-- 审核分支1 --> <userTask id="audit1" name="审核员A审核" activiti:assignee="auditorA"/> <sequenceFlow id="flowToAudit1" sourceRef="forkGateway" targetRef="audit1"/> <!-- 审核分支1条件判断 --> <exclusiveGateway id="gateway1" name="判断A结果"/> <sequenceFlow id="flow1_pass" sourceRef="gateway1" targetRef="joinGateway"> <conditionExpression xsi:type="tFormalExpression">${audit1Passed}</conditionExpression> </sequenceFlow> <sequenceFlow id="flow1_reject" sourceRef="gateway1" targetRef="submitMaterial"> <conditionExpression xsi:type="tFormalExpression">${!audit1Passed}</conditionExpression> </sequenceFlow> <sequenceFlow id="flowAudit1ToGateway" sourceRef="audit1" targetRef="gateway1"/> <!-- 审核分支2 --> <userTask id="audit2" name="审核员B审核" activiti:assignee="auditorB"/> <sequenceFlow id="flowToAudit2" sourceRef="forkGateway" targetRef="audit2"/> <!-- 审核分支2条件判断 --> <exclusiveGateway id="gateway2" name="判断B结果"/> <sequenceFlow id="flow2_pass" sourceRef="gateway2" targetRef="joinGateway"> <conditionExpression xsi:type="tFormalExpression">${audit2Passed}</conditionExpression> </sequenceFlow> <sequenceFlow id="flow2_reject" sourceRef="gateway2" targetRef="submitMaterial"> <conditionExpression xsi:type="tFormalExpression">${!audit2Passed}</conditionExpression> </sequenceFlow> <sequenceFlow id="flowAudit2ToGateway" sourceRef="audit2" targetRef="gateway2"/> <!-- 汇聚并行网关 --> <parallelGateway id="joinGateway" name="汇聚审核结果"/> <sequenceFlow id="flowToAdmin" sourceRef="joinGateway" targetRef="adminAudit"/> <!-- 管理员审核 --> <userTask id="adminAudit" name="管理员终审" activiti:assignee="admin"/> <sequenceFlow id="flowToEnd" sourceRef="adminAudit" targetRef="end"/> <!-- 结束事件 --> <endEvent id="end" name="结束"/> </process> </definitions> ``` --- ### **二、流程图解析** ![并行审核流程图](https://via.placeholder.com/800x600.png?text=BPMN+Parallel+Gateway+Process) 1. **用户提交材料**:启动流程后用户完成任务 2. **并行拆分审核**:同时触发`审核员A`和`审核员B`任务 3. **审核结果判断**: - 任一审核不通过,**回退到提交材料节点** - 两个审核均通过,进入管理员终审 4. **流程结束**:管理员完成终审后结束 --- ### **三、Java测试代码** ```java public class ParallelAuditTest { // 部署流程 @Test public void deployProcess() { ProcessEngine engine = ProcessEngines.getDefaultProcessEngine(); RepositoryService repositoryService = engine.getRepositoryService(); Deployment deployment = repositoryService.createDeployment() .addClasspathResource("parallel-audit.bpmn20.xml") .deploy(); System.out.println("流程部署ID: " + deployment.getId()); } // 启动流程实例 @Test public void startProcess() { ProcessEngine engine = ProcessEngines.getDefaultProcessEngine(); RuntimeService runtimeService = engine.getRuntimeService(); IdentityService identityService = engine.getIdentityService(); // 设置当前用户(流程发起者) identityService.setAuthenticatedUserId("user1"); // 启动流程 ProcessInstance instance = runtimeService.startProcessInstanceByKey("parallelAuditProcess"); System.out.println("流程实例ID: " + instance.getId()); } // 模拟审核操作 @Test public void completeTasks() { ProcessEngine engine = ProcessEngines.getDefaultProcessEngine(); TaskService taskService = engine.getTaskService(); // 用户提交材料 Task submitTask = taskService.createTaskQuery() .taskAssignee("user1") .singleResult(); taskService.complete(submitTask.getId()); // 处理审核员A任务(不通过) Task auditATask = taskService.createTaskQuery() .taskAssignee("auditorA") .singleResult(); Map<String, Object> varsA = new HashMap<>(); varsA.put("audit1Passed", false); // 审核不通过 taskService.complete(auditATask.getId(), varsA); // 处理审核员B任务(通过) Task auditBTask = taskService.createTaskQuery() .taskAssignee("auditorB") .singleResult(); Map<String, Object> varsB = new HashMap<>(); varsB.put("audit2Passed", true); // 审核通过 taskService.complete(auditBTask.getId(), varsB); // 此时流程应回到提交材料节点 Task resubmitTask = taskService.createTaskQuery() .taskAssignee("user1") .singleResult(); assertNotNull("应存在重新提交任务", resubmitTask); } } ``` --- ### **四、关键实现逻辑** 1. **并行网关配对**:使用`forkGateway`拆分审核任务,`joinGateway`汇聚结果 2. **循环控制**:通过排他网关条件表达式回退到提交节点,例如: ```xml <conditionExpression>${!audit1Passed}</conditionExpression> ``` 3. **变量传递**:审核结果通过`audit1Passed`和`audit2Passed`布尔变量控制流程走向[^2] 4. **任务分配**:使用`activiti:assignee`指定任务处理人,支持固定值或动态表达式 --- ### **五、注意事项** 1. **防无限循环**:建议添加计数器变量(如`retryCount`)限制最大重试次数 2. **数据隔离**:回退到提交节点时,需重置前次审核数据 3. **历史查询**:可通过`HistoryService`查询流程实例的完整审批记录 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值