1.概述
流程回退一直以来是个老旧的难题,也一直没有好的解决方法,本文就来详述流程回退的解决办法。首先我们来分析一下不同的流程审批情况,并在对应的节点上实现流程的回退处理,以及应该提供的回退处理,当然我们说的回退不是指通过在流程节点上画一条线回退到想回的节点上。
回退时,需要解决两种情况:
- 回退到发起人
- 回退到上一步及逐步回退
因为回退至任一节点上,Activiti本身的api是不支持的,我们只能通过扩展activiti的的api,以实现自由跳转才达到回退至任一节点上,但有情况是例外的,回退的时候,需要注意,否则activiti在跳转的时候,数据是容易出问题的,主要是在并发的节点分支里跳到外面时(如下图所示,B、D节点回到A节点时),其执行的实例Id会变化,因此,需要注意对这种情况下的流程跳转作一些限制。
那么我们需要在当前审批的任务上,需要进行回退到任何一个节点,实现自由跳转时,如何扩展,如下为我们扩展activiti来实现自由跳转的实现方式:
/** * 将节点之后的节点删除然后指向新的节点。 * @param actDefId 流程定义ID * @param nodeId 流程节点ID * @param aryDestination 需要跳转的节点 * @return Map<String,Object> 返回节点和需要恢复节点的集合。 */ @SuppressWarnings("unchecked") private Map<String,Object> prepare(String actDefId,String nodeId,String[] aryDestination){ Map<String,Object> map=new HashMap<String, Object>(); //修改流程定义 ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity)repositoryService.getProcessDefinition(actDefId); ActivityImpl curAct= processDefinition.findActivity(nodeId); List<PvmTransition> outTrans= curAct.getOutgoingTransitions(); try{ List<PvmTransition> cloneOutTrans=(List<PvmTransition>) FileUtil.cloneObject(outTrans); map.put("outTrans", cloneOutTrans); } catch(Exception ex){ } /** * 解决通过选择自由跳转指向同步节点导致的流程终止的问题。 * 在目标节点中删除指向自己的流转。 */ for(Iterator<PvmTransition> it=outTrans.iterator();it.hasNext();){ PvmTransition transition=it.next(); PvmActivity activity= transition.getDestination(); List<PvmTransition> inTrans= activity.getIncomingTransitions(); for(Iterator<PvmTransition> itIn=inTrans.iterator();itIn.hasNext();){ PvmTransition inTransition=itIn.next(); if(inTransition.getSource().getId().equals(curAct.getId())){ itIn.remove(); } } } curAct.getOutgoingTransitions().clear(); if(aryDestination!=null && aryDestination.length>0){ for(String dest:aryDestination){ //创建一个连接 ActivityImpl destAct= processDefinition.findActivity(dest); TransitionImpl transitionImpl = curAct.createOutgoingTransition(); transitionImpl.setDestination(destAct); } } map.put("activity", curAct); return map; } /** * 将临时节点清除掉,加回原来的节点。 * @param map * void */ @SuppressWarnings("unchecked") private void restore(Map<String,Object> map){ ActivityImpl curAct=(ActivityImpl) map.get("activity"); List<PvmTransition> outTrans=(List<PvmTransition>) map.get("outTrans"); curAct.getOutgoingTransitions().clear(); curAct.getOutgoingTransitions().addAll(outTrans); } /** * 通过指定目标节点,实现任务的跳转 * @param taskId 任务ID * @param destNodeIds 跳至的目标节点ID * @param vars 流程变量 */ public synchronized void completeTask(String taskId,String[] destNodeIds,Map<String,Object> vars) { TaskEntity task=(TaskEntity)taskService.createTaskQuery().taskId(taskId).singleResult(); String curNodeId=task.getTaskDefinitionKey(); String actDefId=task.getProcessDefinitionId(); Map<String,Object> activityMap= prepare(actDefId, curNodeId, destNodeIds); try{ taskService.complete(taskId); } catch(Exception ex){ throw new RuntimeException(ex); } finally{ //恢复 restore(activityMap); } }
若我们需要进行跳转,就需要知道回退上一步时,其上一步是什么节点。如何仅是通过流程获得其回退的节点,这是达不到业务的需求的,因为有时我们需要回退到某个节点处理后,下一步需要回到原来的节点上,如我们在上图E节点上,回退时,E回退需要回到D或C上,完成后再回到B,这种情况下我们可以要求E必须需要去到G1节点上,往下执行。这种回退就会显得人性化,同时也保证流程实例在后续的执行过程中,其信号及各参数是正常的,这时就要求我们需要有一个完整记录流程实例执行经过的各个节点ID的数据,并且通过以下的数据可以快速找到当前节点回退时,应该回退到哪一个节点上,并且当时这个节点的执行人员是谁。
2.如何记录流程的执行过程
为了更好记录流程经过的树节点,我们采用了一个树结构来存储流程实例执行时,经过的流程节点,如上图所示,其执行的树型图所示所示:
我们需要在各个节点那里可以找到其退回至上一步环节的父节点那里,这需要一个算法,如在B或D那里回退,我们让他退回A,在C回退我们让他回到B,若我们在E位置回退,我们需要让他回到G1那里。这个算法的实现不算复杂,有这个树型的执行树数据后,一切变得很简单。但要注意一点,我们在回退时,需要记录他是从哪个节点回退过来的,若用户处理完成后,可以要求他直接回到原回退的节点去,也可以按流程定义重新走一次审批。假如执行到E,让他回退时并且重新审批,其执行的树图如下所示:
注意G1,那里有指向E,当完成时,可以让他来跳到E上,这就是任务完成后,可以找到它应该跳至哪一个任务节点上。
3.扩展表记录流程的执行的路径
/*==============================================================*/ /* Table: BPM_RU_PATH */ /*==============================================================*/ CREATE TABLE BPM_RU_PATH ( PATH_ID_ VARCHAR(64) NOT NULL, INST_ID_ VARCHAR(64) NOT NULL COMMENT '流程实例ID', ACT_DEF_ID_ VARCHAR(64) NOT NULL COMMENT 'Act定义ID', ACT_INST_ID_ VARCHAR(64) NOT NULL COMMENT 'Act实例ID', SOL_ID_ VARCHAR(64) NOT NULL COMMENT '解决方案ID', NODE_ID_ VARCHAR(255) NOT NULL COMMENT '节点ID', NODE_NAME_ VARCHAR(255) COMMENT '节点名称', NODE_TYPE_ VARCHAR(50) COMMENT '节点类型', START_TIME_ DATETIME NOT NULL COMMENT '开始时间', END_TIME_ DATETIME COMMENT '结束时间', DURATION_ INT COMMENT '持续时长', DURATION_VAL_ INT COMMENT '有效审批时长', ASSIGNEE_ VARCHAR(64) COMMENT '处理人ID', TO_USER_ID_ VARCHAR(64) COMMENT '代理人ID', IS_MULTIPLE_ VARCHAR(20) COMMENT '是否为多实例', EXECUTION_ID_ VARCHAR(64) COMMENT '活动执行ID', USER_IDS_ VARCHAR(300) COMMENT '原执行人IDS', PARENT_ID_ VARCHAR(64) COMMENT '父ID', LEVEL_ INT COMMENT '层次', OUT_TRAN_ID_ VARCHAR(255) COMMENT '跳出路线ID', TOKEN_ VARCHAR(255) COMMENT '路线令牌', JUMP_TYPE_ VARCHAR(50) COMMENT '跳到该节点的方式 正常跳转 自由跳转 回退跳转', NEXT_JUMP_TYPE_ VARCHAR(50) COMMENT '下一步跳转方式', OPINION_ VARCHAR(500) COMMENT '审批意见', REF_PATH_ID_ VARCHAR(64) COMMENT '引用路径ID 当回退时,重新生成的结点,需要记录引用的回退节点,方便新生成的路径再次回退。', TENANT_ID_ VARCHAR(64) COMMENT '租用机构ID', CREATE_BY_ VARCHAR(64) COMMENT '创建人ID', CREATE_TIME_ DATETIME COMMENT '创建时间', UPDATE_BY_ VARCHAR(64) COMMENT '更新人ID', UPDATE_TIME_ DATETIME COMMENT '更新时间', PRIMARY KEY (PATH_ID_) ); ALTER TABLE BPM_RU_PATH COMMENT '流程实例运行路线';
4.如何创建执行路径
有了上面的表结构后,如何让activiti在执行的过程中,往上面的表加上我们需要的数据,这时我们就需要利用activiti的全局事件监听器,具体的实现请参考我的 【全局事件监听处理】。
<bean id="globalEventListener" class="com.redxun.bpm.activiti.listener.GlobalEventListener"> <property name="handlers"> <map> <entry key="TASK_CREATED" value="taskCreateListener"/> <entry key="TASK_COMPLETED" value="taskCompleteListener"/> <entry key="TASK_ASSIGNED" value="taskAssignedListener"/> <entry key="PROCESS_COMPLETED" value="processCompleteListener"/> <entry key="ACTIVITY_STARTED" value="activityStartedListener"/> <entry key="ACTIVITY_COMPLETED" value="activityCompletedListener"/> <entry key="ACTIVITY_SIGNALED" value="activitySignaledListener"/> <entry key="PROCESS_STARTED" value="processStartEventListener"/> </map> </property> </bean>
其中Activiti提供了两个不错的事件监听,一个是执行实体创建事件ACTIVITY_STARTED,一个实体完成的事件ACTIVITY_COMPLETED。我们分别在这两个事件上加上bpm_ru_path表的记录创建与更新即可。在其回退的时候,通过算法找到其需要回退的节点,然后通过上文提供的自由跳转方法,即可以实现流程的回退。
package com.redxun.bpm.activiti.listener; import java.util.Date; import java.util.Map; import javax.annotation.Resource; import org.activiti.engine.RuntimeService; import org.activiti.engine.delegate.event.ActivitiEvent; import org.activiti.engine.delegate.event.impl.ActivitiActivityEventImpl; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.redxun.bpm.activiti.entity.ActNodeDef; import com.redxun.bpm.activiti.service.ActRepService; import com.redxun.bpm.activiti.util.ProcessHandleHelper; import com.redxun.bpm.core.entity.BpmInst; import com.redxun.bpm.core.entity.BpmRuPath; import com.redxun.bpm.core.entity.IExecutionCmd; import com.redxun.bpm.core.entity.ProcessNextCmd; import com.redxun.bpm.core.entity.ProcessStartCmd; import com.redxun.bpm.core.entity.config.ActivityConfig; import com.redxun.bpm.core.entity.config.BpmEventConfig; import com.redxun.bpm.core.manager.BpmInstManager; import com.redxun.bpm.core.manager.BpmNodeSetManager; import com.redxun.bpm.core.manager.BpmRuPathManager; import com.redxun.bpm.enums.TaskEventType; import com.redxun.core.constants.MBoolean; import com.redxun.core.script.GroovyEngine; import com.redxun.saweb.util.IdUtil; /** * 活动节点开始时的监听器 * @author keitch * */ public class ActivityStartedListener implements EventHandler{ private Log logger=LogFactory.getLog(ActivityStartedListener.class); @Resource BpmRuPathManager bpmRuPathManager; @Resource BpmInstManager bpmInstManager; @Resource ActRepService actRepService; @Resource RuntimeService runtimeService; @Resource BpmNodeSetManager bpmNodeSetManager; @Resource GroovyEngine groovyEngine; /** * 执行脚本事件 * @param eventImpl */ public void executeEventScript(ActivitiActivityEventImpl eventImpl){ String solId=(String)runtimeService.getVariable(eventImpl.getExecutionId(), "solId"); //处理事件 ActivityConfig actConfig=bpmNodeSetManager.getActivityConfig(solId, eventImpl.getActivityId()); if(actConfig.getEvents().size()>0){ BpmEventConfig bpmEventConfig=null; for(BpmEventConfig eventConfig:actConfig.getEvents()){ if(TaskEventType.ACTIVITY_STARTED.name().equals(eventConfig.getEventKey())){ bpmEventConfig=eventConfig; break; } } //执行脚本 if(bpmEventConfig!=null && StringUtils.isNotEmpty(bpmEventConfig.getScript())){ logger.debug("==================execute the ActivityStartedListener complete listener:"+bpmEventConfig.getScript()); Map<String,Object> vars=runtimeService.getVariables(eventImpl.getExecutionId()); vars.put("executionId",eventImpl.getExecutionId()); groovyEngine.executeScripts(bpmEventConfig.getScript(),vars); } } } /** * 创建执行路径的数据,用于流程图的追踪,流程回退及执行等 */ @Override public void handle(ActivitiEvent event) { logger.debug("enter the event ActivityStartedListener handler is .....============"); ActivitiActivityEventImpl eventImpl=(ActivitiActivityEventImpl)event; String activityId=eventImpl.getActivityId(); String entityName=eventImpl.getActivityName(); logger.debug("entity:"+activityId + " entityName:"+entityName); IExecutionCmd cmd=ProcessHandleHelper.getProcessCmd(); ActNodeDef actNodeDef=actRepService.getActNodeDef(eventImpl.getProcessDefinitionId(), activityId); //判断一些并行的网关的结束点,防止其生成多条记录 if(eventImpl.getActivityType().indexOf("Gateway")!=-1 || StringUtils.isNotEmpty(actNodeDef.getMultiInstance())){ BpmRuPath ruPath= bpmRuPathManager.getFarestPath(eventImpl.getProcessInstanceId(),eventImpl.getActivityId()); if(ruPath!=null){ cmd.setNodeId(activityId); if("userTask".equals(eventImpl.getActivityType())){ //获得会签人员列表,并且进行会签人员设置 if(StringUtils.isEmpty(ruPath.getUserIds())){ String userIds=(String)runtimeService.getVariable(eventImpl.getExecutionId(), "signUserIds_"+activityId); ruPath.setUserIds(userIds); bpmRuPathManager.update(ruPath); } } return; } } //创建执行路径 BpmRuPath path=new BpmRuPath(); path.setPathId(IdUtil.getId()); path.setActDefId(eventImpl.getProcessDefinitionId()); path.setActInstId(eventImpl.getProcessInstanceId()); path.setExecutionId(eventImpl.getExecutionId()); path.setNodeName(actNodeDef.getNodeName()); path.setNodeId(activityId); path.setNodeType(actNodeDef.getNodeType()); path.setStartTime(new Date()); if(cmd instanceof ProcessStartCmd){//若为启动时,需要从线程中获得 ProcessStartCmd startCmd=(ProcessStartCmd)cmd; path.setInstId(startCmd.getBpmInstId()); path.setSolId(startCmd.getSolId()); }else{ BpmInst bpmInst=bpmInstManager.getByActInstId(eventImpl.getProcessInstanceId()); path.setInstId(bpmInst.getInstId()); path.setSolId(bpmInst.getSolId()); path.setNextJumpType(((ProcessNextCmd)cmd).getNextJumpType()); } //是否为多实例 if(StringUtils.isNotEmpty(actNodeDef.getMultiInstance())){ path.setIsMultiple(MBoolean.YES.name()); }else{ path.setIsMultiple(MBoolean.NO.name()); } //记录跳转的原节点,并且把跳转记录挂至该节点上 BpmRuPath parentPath=null; if(cmd!=null && StringUtils.isNotEmpty(cmd.getNodeId())){ parentPath=bpmRuPathManager.getFarestPath(eventImpl.getProcessInstanceId(),cmd.getNodeId()); } if(parentPath!=null){ path.setParentId(parentPath.getPathId()); path.setLevel(parentPath.getLevel()+1); }else{ path.setLevel(1); path.setParentId("0"); } //是否由回退时产生的,若是需要记录回退时的流程ID BpmRuPath bpmRuPath=ProcessHandleHelper.getBackPath(); if(bpmRuPath!=null){ path.setRefPathId(bpmRuPath.getPathId()); } //当从开始启动时,进入两次,这时需要记录其父ID //或在任务节点后的非任务点时,传递其父Id if(!"userTask".equals(actNodeDef.getNodeType())){ cmd.setNodeId(eventImpl.getActivityId()); } bpmRuPathManager.create(path); } }
完成的事件处理
package com.redxun.bpm.activiti.listener; import java.util.Date; import java.util.Map; import javax.annotation.Resource; import org.activiti.engine.RuntimeService; import org.activiti.engine.delegate.event.ActivitiEvent; import org.activiti.engine.delegate.event.impl.ActivitiActivityEventImpl; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.redxun.bpm.activiti.util.ProcessHandleHelper; import com.redxun.bpm.core.entity.BpmRuPath; import com.redxun.bpm.core.entity.IExecutionCmd; import com.redxun.bpm.core.entity.ProcessNextCmd; import com.redxun.bpm.core.entity.config.ActivityConfig; import com.redxun.bpm.core.entity.config.BpmEventConfig; import com.redxun.bpm.core.manager.BpmNodeSetManager; import com.redxun.bpm.core.manager.BpmRuPathManager; import com.redxun.bpm.enums.TaskEventType; import com.redxun.bpm.enums.TaskOptionType; import com.redxun.core.script.GroovyEngine; import com.redxun.saweb.context.ContextUtil; /** * 活动节点结束时的监听器 * @author keitch * */ public class ActivityCompletedListener implements EventHandler{ private Log logger=LogFactory.getLog(ActivityCompletedListener.class); @Resource BpmRuPathManager bpmRuPathManager; @Resource BpmNodeSetManager bpmNodeSetManager; @Resource GroovyEngine groovyEngine; @Resource RuntimeService runtimeService; /** * 执行脚本事件 * @param eventImpl */ public void executeEventScript(ActivitiActivityEventImpl eventImpl){ String solId=(String)runtimeService.getVariable(eventImpl.getExecutionId(), "solId"); //处理事件 ActivityConfig actConfig=bpmNodeSetManager.getActivityConfig(solId, eventImpl.getActivityId()); if(actConfig.getEvents().size()>0){ BpmEventConfig bpmEventConfig=null; for(BpmEventConfig eventConfig:actConfig.getEvents()){ if(TaskEventType.ACTIVITY_COMPLETED.name().equals(eventConfig.getEventKey())){ bpmEventConfig=eventConfig; break; } } //执行脚本 if(bpmEventConfig!=null && StringUtils.isNotEmpty(bpmEventConfig.getScript())){ logger.debug("==================execute the ActivityCompletedListener complete listener:"+bpmEventConfig.getScript()); Map<String,Object> vars=runtimeService.getVariables(eventImpl.getExecutionId()); vars.put("executionId",eventImpl.getExecutionId()); groovyEngine.executeScripts(bpmEventConfig.getScript(),vars); } } } @Override public void handle(ActivitiEvent event) { logger.debug("enter the event ActivityCompletedListener handler is .....============"); ActivitiActivityEventImpl eventImpl=(ActivitiActivityEventImpl)event; //执行配置的事件脚本 executeEventScript(eventImpl); IExecutionCmd cmd=ProcessHandleHelper.getProcessCmd(); BpmRuPath ruPath=bpmRuPathManager.getFarestPath(eventImpl.getProcessInstanceId(),eventImpl.getActivityId() ); if(ruPath!=null){ ruPath.setAssignee(ContextUtil.getCurrentUserId()); //TODO 设置代理人,表示代理谁来执行 //ruPath.setAgnentUserId(aValue); if(cmd instanceof ProcessNextCmd){ ruPath.setToUserId(((ProcessNextCmd)cmd).getAgentToUserId()); } ruPath.setEndTime(new Date()); Long duration=ruPath.getEndTime().getTime()-ruPath.getStartTime().getTime(); ruPath.setDuration(duration.intValue()); //TODO,结合工作日历计算有效时间 ruPath.setDurationVal(duration.intValue()); if(cmd!=null && "userTask".equals(ruPath.getNodeType())){ if(StringUtils.isNotBlank(cmd.getJumpType())){ ruPath.setJumpType(cmd.getJumpType()); ruPath.setOpinion(cmd.getOpinion()); }else{ ruPath.setJumpType(TaskOptionType.AGREE.name()); ruPath.setOpinion("同意"); } } //更新其数据 bpmRuPathManager.update(ruPath); } } }
5.流程回退处理
有了以上的执行数据,流程的回退,就可以通过算法找到其需要回退的流程节点,从而可以实现流程的回退处理,注意以下的获得当前任务的回退节点Id,然后指定这个节点Id为执行完成后,需要跳转至这个节点上。
注意这部分代码 BpmRuPath bpmRuPath = getBackNodeId(task.getProcessInstanceId(), task.getTaskDefinitionKey());
/** * 任务往下跳转 * * @param taskId * @param jsonData * @param vars * @throws Exception */ public void doNext(ProcessNextCmd cmd) throws Exception { boolean isSetBackPath = false; try { TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(cmd.getTaskId()).singleResult(); UserTaskConfig userTaskConfig=bpmNodeSetManager.getTaskConfig(task.getSolId(), task.getTaskDefinitionKey()); //String processInstanceId = task.getProcessInstanceId(); // 加上executionId,用来记录执行的路径 cmd.setNodeId(task.getTaskDefinitionKey()); // 加上线程变量 ProcessHandleHelper.setProcessCmd(cmd); BpmInst bpmInst = bpmInstManager.getByActInstId(task.getProcessInstanceId()); BpmFormInst bpmFormInst = bpmFormInstManager.get(bpmInst.getFormInstId()); try { String newJson = JSONUtil.copyJsons(bpmFormInst.getJsonData(), cmd.getJsonData()); bpmFormInst.setJsonData(newJson); bpmFormInstManager.saveOrUpdate(bpmFormInst); } catch (Exception ex) { logger.error(ex.getCause()); } Map<String, Object> vars = handleTaskVars(task, cmd.getJsonData()); // 加上外围传过来的变量 if (cmd.getVars() != null) { vars.putAll(cmd.getVars()); } // 若为回退,则处理回退的操作 if (TaskOptionType.BACK.name().equals(cmd.getJumpType())) { BpmRuPath bpmRuPath = getBackNodeId(task.getProcessInstanceId(), task.getTaskDefinitionKey()); // 没有找到回退的节点,提示用户 if (bpmRuPath == null) { ProcessHandleHelper.getProcessMessage().getErrorMsges().add("本环节不能回退!没有找到上一步的回退审批环节!"); return; } else {// 设置回退的节点 cmd.setDestNodeId(bpmRuPath.getNodeId()); ProcessHandleHelper.setBackPath(bpmRuPath); isSetBackPath = true; } } else if (TaskOptionType.BACK_TO_STARTOR.name().equals(cmd.getJumpType())) {// 回退至发起人 ActNodeDef afterNode = actRepService.getNodeAfterStart(task.getProcessDefinitionId()); if (afterNode == null) { ProcessHandleHelper.getProcessMessage().getErrorMsges().add("没有找到发起人所在的审批环节!"); return; } else { cmd.setDestNodeId(afterNode.getNodeId()); } } else { // 查找是否为原路返回的模式,即当前任务是否由回退处理的 BpmRuPath ruPath = bpmRuPathManager.getFarestPath(task.getProcessInstanceId(), task.getTaskDefinitionKey()); if (ruPath != null && "".equals(ruPath.getNextJumpType())) { BpmRuPath toNodePath = bpmRuPathManager.get(ruPath.getParentId()); if (toNodePath != null) { cmd.setDestNodeId(toNodePath.getNodeId()); } } } //加上前置处理 if(StringUtils.isNotEmpty(userTaskConfig.getPreHandle())){ Object preBean=AppBeanUtil.getBean(userTaskConfig.getPreHandle()); if(preBean instanceof TaskPreHandler){ TaskPreHandler handler=(TaskPreHandler)preBean; handler.taskPreHandle(cmd, task, bpmInst.getBusKey()); } } // 以下为任务的跳转处理 if (StringUtils.isNotEmpty(cmd.getDestNodeId())) {// 进行指定节点的跳转 actTaskService.completeTask(cmd.getTaskId(), new String[] { cmd.getDestNodeId() }, vars); } else {// 正常跳转 taskService.complete(cmd.getTaskId(), vars); } //加上后置处理 if(StringUtils.isNotEmpty(userTaskConfig.getAfterHandle())){ Object preBean=AppBeanUtil.getBean(userTaskConfig.getAfterHandle()); if(preBean instanceof TaskAfterHandler){ TaskAfterHandler handler=(TaskAfterHandler)preBean; handler.taskAfterHandle(cmd, task.getTaskDefinitionKey(), bpmInst.getBusKey()); } } } catch (Exception e) { e.printStackTrace(); logger.error(e.getCause()); throw e; } finally { ProcessHandleHelper.clearProcessCmd(); if (isSetBackPath) { ProcessHandleHelper.clearBackPath(); } } }
具体的实现效果可以参考如下在线示例,
http://www.redxun.cn:8020/saweb/index.do,
user:admin
pwd:1
http://redxun.iteye.com/blog/2406509
需要在流程解决方案的节点配置中,打开回退按钮,如下图所示:
相关推荐
activiti在设计的时候没有回退相关的操作,回退是中国特有的特色。这里写一个比较简单的回退。不支持回退到并行网关前面节点,虽然回退到前面节点不会报错 但会导致任务无法结束。使用没有并行网关的回退。
本文将深入探讨如何在 Activiti 5.22 版本中实现撤回操作,确保这一功能的实现不影响流程设计,并且通过 ExecutionEntity 对象来管理任务的生命周期,以达到撤销并重新指定任务节点的目的。 Activiti 是一个开源的...
activiti没有撤回,由于业务的需求需要实现撤回,在参考别人代码后以两种方式实现了任意节点的跳转。代码真实可用,如有问题可联系我 第一种方式: 1、获取当前节点,获取跳转节点 2、获取节点的所有流出流向,把...
Activiti6-流程跟踪监控图-节点-流程线高亮显示-支持通过、不通过、驳回、退回 支持内容: 已完成节点高亮显示、当前执行中节点红色显示 支持一个节点多条流出线,包括通过、不通过、驳回、退回,按照已执行操作正确...
9. **流程实例管理**:这部分内容会涉及如何查询、控制和监视流程实例,包括获取当前运行的流程实例,查询历史流程实例,以及对流程实例进行回退、跳过、挂起等操作。 10. **异常处理与调试**:在实际应用中,可能...
10. **异常处理**:处理流程中的错误和异常,如捕获异常并进行回退或补偿操作。 作为初级入门者,你需要通过学习这些代码示例来掌握 Activiti 的基本操作,理解如何将流程模型与实际代码相结合。同时,你也可以参与...
并行网关(Parallel Gateway)在 Activiti 流程引擎中扮演着重要的角色,它是流程设计中的一个关键组件,用于实现分支和合并的功能。在本篇学习笔记中,我们将深入探讨并行网关的工作原理、使用场景以及如何在 ...
10. **异常处理与回退策略** 在流程执行过程中,可能会遇到异常情况。理解如何处理这些异常,如错误处理和回滚操作,是保证流程正常运行的关键。 通过以上步骤,你将能够成功运行一个Activiti项目并启动流程实例。...
4.2 异常处理与回退:模拟业务异常情况,演示如何设计流程的异常处理机制,以及如何实现流程回退功能。 4.3 动态流程变更:介绍如何在运行时动态修改流程定义,以适应业务变化。 总结,本讲义旨在通过理论结合实践...
9. **异常处理**:通过事件和异常处理机制,确保流程在出现问题时能够正常流转或回退。 综上所述,Activiti jar包的使用涉及到流程定义、流程实例、任务管理和SpringMVC的集成等多个方面,为企业的流程自动化提供了...
标题中的“基于Activiti流程监控的毕业设计管理系统”是一个典型的IT项目,主要涉及的是企业级工作流管理系统Activiti在实际应用场景中的应用。Activiti是一款开源的工作流引擎,它能够帮助企业和组织实现业务流程...
4. 错误处理与回退:深入源码,学习如何设置错误处理策略和流程回退机制。 通过以上内容的学习,开发者可以更好地理解和运用Activiti,解决实际项目中的业务流程自动化问题。源码分析将有助于提升对Activiti内部...
这部分将涵盖如何查看流程实例的状态、挂起或激活流程实例,以及回退和跳过流程节点。 7. **表单与变量**:Activiti支持与业务数据的交互,表单和变量是实现这一目标的关键。用户将了解如何定义表单字段,如何使用...
6. **版本回退**:如果新版本存在兼容性或稳定性问题,可以尝试回退到已知稳定的老版本。 在标签中提到的"Activiti Designer 5.12",这是Activiti Designer的一个特定版本。Activiti Designer 5.x系列是基于...
总结来说,"activiti-5.16.3" 包含的"activiti-explorer.war"是Activiti流程引擎的一个重要组成部分,它提供了直观的用户界面,便于管理和监控业务流程。通过学习和使用这个版本,开发者不仅可以提升对Activiti的...
9. **流程实例管理**:除了设计模型,还需要处理流程实例的生命周期,如启动、暂停、继续、结束和回退等操作。 10. **日志与监控**:集成后,应集成日志系统记录流程运行情况,同时可能需要实时监控流程状态,以便...
8. **流程版本控制**:理解如何处理流程的升级和回退,以及历史流程实例的查询。 通过实践"activiti.zip"中的代码示例,你将逐步掌握 Activiti 的核心功能,并能将其应用于实际项目中。同时,不断查阅官方文档,...
Activiti支持异常处理和流程回退,通过源码我们可以学习如何设置错误处理节点,以及在流程出错时如何恢复或终止流程。 通过深入学习《Activiti实战》配套源码,你不仅能掌握Activiti的基本用法,还能了解其内部...
3. 状态管理支持状态的转换和回退操作,确保业务流程的灵活性和可控性。 4. 邮件服务集成邮件发送功能,支持在流程执行过程中发送通知邮件。 5. 消息传递提供消息传递机制,支持在不同模块之间传递和获取消息。 6. ...
10. **异常处理和回退策略**:在设计流程时,要考虑异常情况的处理,例如定义错误事件和回退策略,以确保流程的健壮性。 以上是Spring Boot整合Activiti的基本框架,实际应用中可能还需要结合具体的业务场景进行...