一、需求定义:
1.允许向已办理过的任意节点流转:从历史任务表中读取以前办理过的任务,从当前任务指向该历史任务节点,并创建该历史节点的新任务,并将原历史任务的审批人分配给现历史任务; 如ABCDE五个节点,目前停留在D节点,点击按钮,跳出包含A、B、C、D下拉菜单的页面,例如选择B,创建D到B的流向,创建基于B的新任务,将历史任务B的审批人分配给新任务B(也应允许重新为新任务B分配审批人),同时向该审批人发送邮件,删除D到B的流向;
2.在历史任务表中应体现以上过程,保留流转的历史痕迹;
3.该功能按钮放置在taskRun.jsp下方;
4.将调整审批人的功能合并到该功能中,如选择当前节点,比如停留在D节点,而用户选择下拉菜单中的E,则该功能为调整审批人;
5.系统管理员、流程管理员具有该功能的使用权限;
6.会签节点不允许拉回。
二、实现思路
1.首先自t_bpm_process_task表中查询出所有已办理的任务,过滤掉重复的(一些步骤可能被重复办理),将数据按照activityName:assignee的格式返回给前端(这里存在的问题是多次重复办理的可能会只出现最后一次办理的审批人);
/** * 任务处理页面 * TODO(方法详细描述说明、方法参数的具体涵义) * @author chao.gao * @date 2015-03-15 下午2:08:58 * @return */ public String toFree(){ List<ProcessTaskEntity> processTaskList = processTaskService.queryByProcessExecutionId(processExecutionId); if(CollectionUtils.isEmpty(processTaskList)){ throw new ProcessActivityException("不能调整,请联系流程管理员!"); }else{ JSONObject map = new JSONObject(); for(ProcessTaskEntity task : processTaskList){ if(task.getActivityName().contains("start")){ map.put("重新填写", task.getCreateUserCode()); } map.put(task.getActivityName(), task.getCreateUserCode()); } taskVo.setActivityUserMap(map); } taskVo.setProcessExecutionId(processExecutionId); return SUCCESS; }
2.页面结构相对简单,只有一个select格式的下拉菜单用于显示activityName的列表,另外一个员工选择器,用于选择将拉回的任务分配给的审批人。关于在自由流过程中遇到的一些前端问题的总结见我的另外一篇博文《关于自由流功能开发过程中前端的一些问题汇总》。这样做的目的是尽可能的将该功能的覆盖面扩大,比如更改当前节点审批人、已审批的步骤审批人有误等,而不是仅仅是拉回某一节点且将该任务分配给该节点的历史审批人。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> .... <title>调整节点/审批人</title> <script type="text/javascript"> $(function(){ var pkValue = $("#pkValue").val(); var sponsorCode = $("#sponsorCode").val(); var currentUser = $("#changepeopleid").val(); var processDefineId = $("#processDefineId").val(); var processDefineName = $("#processDefineName").val(); var processExecutionId = $("#processExecutionId").val(); $("#changepeoplename_add").on('click',function(){ $.selectorShow({ type : 'user', //multiSelect : true, callback : function(record) { $("#changepeoplenames").val(record.name); $("#changepeopleid").val(record.code); } }); }); $("#btnCancel").bind("click",function(){ $("div[id^='xubox_shade']", window.parent.document).remove(); $("div[id^='xubox_layer']", window.parent.document).remove(); }); $("#btnOk").on("click",function(){ var url = "../workflow/free.action"; if($("#changepeoplenames").val()==""){ $.oaAlert({ type: 0,//0标识需要提示疑问或者错误信息 1 提示正确信息 tipMsg:'处理人不能为空!' }); return ; } var paras = { taskVo : { pkValue : pkValue, currentUser : $("#changepeopleid").val(), sponsorCode : sponsorCode, processDefineId: processDefineId, processDefineName: processDefineName, processExecutionId : processExecutionId, activityName :$("select[name='activityNameChoose']").val() } }; //暂时不可用 var btn = $(this); btn.hide(); //流程下一步执行 $.oaPlugin.ajaxJson({ data : paras, url : url, success : function(data) { //var layer = window.parent.layer; //关闭当前窗口 //layer.close(layer.index); if(data.validate!=null && data.validate!=undefined && data.validate.result == false && data.validate.message!=undefined){ ... } else { ... }); } }, ... }); }); }); </script> </head> <body> ... <div class="design_pro_attribute"> <table class="workflow_add_next"> <tr> <input disabled type="text" class="addtable_text_middle" id="changepeoplenames" name="changepeoplenames" value="" /> <a href="#" class="addtable_sele_but select_but" id="changepeoplename_add" /> <img src="${resources}/images/but_add.png" />添加</a> <a href="#" class="addtable_sele_but select_but" id="addtable_clear_ydhzjzg" /> <img src="${resources}/images/but_del.png" />清空</a> </tr> <tr> <td> <select name="activityNameChoose" id="activityNameChoose"> </select> </td> <td> </td> </tr> </table> <p class="operation"><input type="button" class="oa_button query_but" id="btnOk" value="确定" /> <input type="button" class="oa_button cancel_but" value="取消" id="btnCancel" /> </p> </div> <script type="text/javascript"> var activityUserMap = ${taskVo.activityUserMap}; $.each(activityUserMap, function(key, value) { var temp= "<option value='" + key + "'>" + key + "</option>"; $('select[name="activityNameChoose"]').append(temp); }); </script> </body> </html>
3.页面选中需要拉回的步骤和需要指派的审批人后,向后端发出拉回或更改审批人的请求,如选中的节点名称为当前节点,自动选择更改审批人的流程;如果当前选中的节点非当前节点,则走拉回的处理逻辑;
三、Jbpm核心代码
Jbpm的自由流功能简而言之就是实现一个在流程定义的各节点之间自由穿梭、跳跃,而不拘泥受限于流程定义的节点走向。所以其核心逻辑是创建一个由此节点至彼节点的路线(在流程定义图中并未有这样一条路线),然后将此节点的任务自动办理然后循着新的路线跳至彼节点并生成彼节点的任务,并该任务指派给某个选中的审批人。
简单的实现逻辑就是创建一个路线,然后再执行完上述任务事将其删除。在我们写的封装有JBPM各种操作的JbpmOperateService中是有这样两个方法的,如下,
但同时我写在该方法上写了这样的注释
JBPM 自由流(动态路由),存在着很强的人为干预性因素(中国特色)现业务需求方提出能不能够发起人在发起单据时,自由选择下个审批节点,现提供一种解决办法就是动态创建transition转移。
动态创建transition,在多线程同时访问时,可能会出现很多问题,所以自由流程慎用!
这是根据网上的相关描述所加的代码,从以上注释我们可以看出,创建和删除transition是线程不安全的,应当慎重使用。除此以外,JBPM也提供了一种基于命令模式的机制,可以将一些JBPM操作放在命令中,由引擎调用,由JBPM负责同步。
JBPM4.4采用命令(org.jbpm.apiNaNd.Command)设计模式作为其实现流程逻辑的核心思想,所有命令都需要实现Command接口,在这个接口提供的execute方法里面实现
逻辑。注意:每个命令都是一个独立的事务操作,即每个execute方法的实现都被一个Hibernate所包含。在jbpm4.4中给开发者留有很多的扩展空间,比如这个Command命令就是其中之一,jbpm4.4实际上是鼓励开发者去定制自己的“用户命令”,去实现特定的业务需求,因为这个命令模式中可以使开发者能在流程(因为在execute中有了Environment,有了它可以获得流程中的任何东西 )和业务之间进行穿梭,可以说是穿梭自由啊,所以我们称之为自由流!
关于命令模式会在以后的JBPM与设计模式中进行介绍。所以我们希望通过org.jbpm.api.Command来实现这样的一个临时性的跳转,事务、同步等都交给JBPM。
下面我们来看一下,我们自由流实现的逻辑,这个实际是复用了拉回的逻辑:
package com.gaochao.oa.module.bpm.workflow.server.service.impl; import java.util.List; import org.jbpm.api.Execution; import org.jbpm.api.ProcessEngine; import org.jbpm.api.cmd.Command; import org.jbpm.api.cmd.Environment; import org.jbpm.api.model.Activity; import org.jbpm.api.task.Task; import org.jbpm.pvm.internal.model.ActivityImpl; import org.jbpm.pvm.internal.model.ExecutionImpl; import org.jbpm.pvm.internal.model.ProcessDefinitionImpl; import org.jbpm.pvm.internal.model.TransitionImpl; /**自由流的核心类 * TODO(描述类的职责) * @date 2014-11-26 下午3:14:14 * @version <b>1.0.0</b> */ public class GetBackCommand implements Command<Activity> { private static final long serialVersionUID = 1L; private static final String DYNAMIC_TRANSITION_NAME = "dynamic_transition_name"; private String executionId; //待取回任务的execution private String preTaskName; //待取回任务的任务名 public GetBackCommand(String executionId, String preTaskName) { this.executionId = executionId; this.preTaskName = preTaskName; } /** * * @date 2014-11-26 下午3:14:35 * @see org.jbpm.api.cmd.Command#execute(org.jbpm.api.cmd.Environment) * @param arg0 * @return * @throws Exception */ @Override public Activity execute(Environment environment) throws Exception { ProcessEngine engine = environment.get(ProcessEngine.class); ExecutionImpl execution = (ExecutionImpl) engine.getExecutionService().findExecutionById(executionId); ProcessDefinitionImpl definition = (ProcessDefinitionImpl) engine.getRepositoryService().createProcessDefinitionQuery() .processDefinitionId(execution.getProcessDefinitionId()).uniqueResult(); //需要取回的任务 ActivityImpl activity = definition.getActivity(preTaskName); if(Execution.STATE_INACTIVE_CONCURRENT_ROOT.equals(execution.getState())){ //不支持并发取回 return null; }else{ TransitionImpl transition = null ; ActivityImpl fromActivity = null; try{ //直流 fromActivity = execution.getActivity(); transition = fromActivity.createOutgoingTransition(); transition.setDestination(activity); transition.setName(DYNAMIC_TRANSITION_NAME); //添加transition List<TransitionImpl> transitionImpls=(List<TransitionImpl>)activity.getIncomingTransitions(); transitionImpls.add(transition); //查询execution中的当前任务并完成它 Task task = engine.getTaskService().createTaskQuery().processInstanceId(execution.getProcessInstance().getId()) .activityName(execution.getActivityName()).uniqueResult(); if(task==null){ Execution tempExecution = execution.findActiveExecutionIn(execution.getActivityName()); if(tempExecution==null)return null;//并发时返回null engine.getExecutionService().signalExecutionById(tempExecution.getId(),DYNAMIC_TRANSITION_NAME); }else{ engine.getTaskService().completeTask(task.getId(), DYNAMIC_TRANSITION_NAME); } }catch(Exception e){ throw e; }finally{ //移除transition if(activity!=null&&activity.getIncomingTransitions().contains(transition)){ activity.getIncomingTransitions().remove(transition); } if(fromActivity!=null&& fromActivity.getOutgoingTransitions().contains(transition)){ fromActivity.getOutgoingTransitions().remove(transition); } } } return activity; } }
四总结
通过使用JBPM的命令模式,我们愉快地实现了线程安全的自由流功能,而不需要做任何额外的线程、事务控制等底层的逻辑处理,而将这种处理全部交给核心引擎。
我们基于设计模式的思路简单剖析一下JBPM的命令模式实现。命令模式主要有以下三种角色,命令Command、命令的接收者Receiver(命令实际的作用者)、命令的控制器(执行命令)。命令模式也主要是用于调用者(Invoker:流程引擎)和被调用者(Receiver:流程执行环境)之间通过命令(Command:GoBackCommand)解耦,实现代码清晰、可扩展、易维护的一种思想。