神刀安全网

说说在 jBPM 工作流中如何实现【会签】功能

会签(会审),指的是在流程中某个业务需要经过多人表决,并且根据表决意见的汇总结果以及设定的规则,来决定流程的走向,它是审批流程中常见的需求。

会签可以分为两种:

  1. 单步 – 只使用一个活动来处理。
  2. 多步 – 由多个活动所组成的。

单步会签比较常见,也容易实现,主要的解决方案是在会签活动的主任务基础上,动态创建若干个子任务来实现,它的具体解决方案是:

  1. 编写专门用于会签活动的任务分配处理器(实现 AssignmentHandler),在这个处理器中,通过流程变量来获取参加会议的用户 id,并为这些用户动态地创建 “会签任务” 对象。
  2. 编写完成 “会签任务” 的命令,在命令中设定会签的业务逻辑。

会签的业务逻辑有以下 4 种情况:

情况 说明
一票否决制 参加会签的用户中,只要有一个人不同意,会签就结束,进入 “会签否决” 转移;如果所有人都同意,则进入 “会签通过” 转移。
一票通过制 逻辑与 “一票否决制”相反。
按比例否决制 全部参加会签的用户提交任务后,根据提交的意见,按比例来决定是否进入 “会签否决” 转移。
意见收集制 简单,全部会签用户通过任务表单提交任务后,收集这些意见,然后结束任务。

多步会签,相对较复杂,建议使用动态创建子流程的方式来实现。

至于更复杂的业务场景,比如将第三方业务系统接入会签,可以考虑使用 JMS 活动来发送消息,并监听第三方业务系统的应答模式,异步地实现会签需求。

说说在 jBPM 工作流中如何实现【会签】功能

会签流程定义

jPDL:

<?xml version="1.0" encoding="UTF-8"?>  <process name="JointlySign" xmlns="http://jbpm.org/4.4/jpdl">    <start name="start1" g="166,199,48,48">       <transition to="会签"/>    </start>    <task name="会签" g="259,196,92,52">       <!-- 定义任务分配处理器,它会根据参与者(participants)动态地创建出相应的子任务-->       <assignment-handler class="net.deniro.jbpm.JointSignAssignment">          <!-- 这里,也可以为 participants 设定为流程变量的值,这样就可以动态地决定参与会签的用户啦(通过上一步任务表单来选定会签的任务)-->          <field name="participants">             <list>                <string value="Deniro"/>                <string value="Jack"/>                <string value="Lucy"/>             </list>          </field>       </assignment-handler>       <!-- 会签否决-->       <transition name="to end" to="end1"/>       <!-- 会签通过,则进入【执行】-->       <transition name="to execute" to="执行"/>    </task>    <state name="执行" g="365,268,92,52">       <transition to="end1"/>    </state>    <end name="end1" g="508,199,48,48"/> </process> 
  • assignment-handler 定义了任务分配处理器,它会根据参与者(participants)动态地创建出相应的子任务。
  • 可以在 participants 设定为流程变量的值,这样就可以动态地决定参与会签的用户啦(通过上一步任务表单来选定会签的任务)
  • 会签被否决,则流程结束。
  • 全体通过会签,则进入【执行】活动。

会签活动的任务处理器:

public class JointSignAssignment implements AssignmentHandler {      //会签参与者 ID 列表(在流程定义中注入)     private List<String> participants;      //任务服务     private final static TaskService taskService = Configuration.getProcessEngine()             .getTaskService();      @Override     public void assign(Assignable assignable, OpenExecution execution) throws Exception {         String instanceId = execution.getProcessInstance().getId();          //获取会签活动任务对象         Task task = taskService.createTaskQuery().processInstanceId(instanceId)                 .activityName(execution.getName()).uniqueResult();          //创建会签子任务         createSubTasks3(task);     }    ... } 

创建会签子任务有三种实现方法。

基于主任务:

private void createSubTasks(Task task) {     if (participants == null) {         return;     }      for (String participant : participants) {         //基于主任务,创建会签子任务         Task subTask = taskService.newTask(task.getId());          //设置会签参与者为子任务的可处理者         subTask.setAssignee(participant);         taskService.addTaskParticipatingUser(task.getId(), participant, Participation.CLIENT);     } } 

这样做有问题,因为这个 taskService.newTask() 方法会立即持久化子任务及其历史,而此时的主任务还未提交。因此这样创建的子任务无法关联到主任务,会抛出持久化异常。

脱离主任务:

private void createSubTasks2(Task task) {     if (participants == null) {         return;     }      for (String participant : participants) {         //创建独立的会签子任务         Task subTask = taskService.newTask();          //设置会签参与者为子任务的可处理者         subTask.setAssignee(participant);         taskService.addTaskParticipatingUser(task.getId(), participant, Participation.CLIENT);     } } 

这样也有问题,因为这样凭空创建的任务,虽然不会在持久化时出现异常,但它无法关联到主任务。这样创建的任务其实是孤立的,这在后续的会签操作、级联删除以及历史分析,都会出现很大的问题。

使用主任务的 Task 对象:

private void createSubTasks3(Task task) {     if (participants == null) {         return;     }      for (String participant : participants) {         //使用主任务的 Task 对象的 createSubTask 方法(不会持久化)来创建会签子任务         Task subTask = ((OpenTask)task).createSubTask();          //设置会签参与者为子任务的可处理者         subTask.setAssignee(participant);          //关联会签任务到主任务         taskService.addTaskParticipatingUser(task.getId(), participant, Participation.CLIENT);     } } 

这样做既可以关联到主任务以及流程实例,又可以随着主任务一同被持久化。就选这种方法啦O(∩_∩)O哈哈~

基于【一票否决制】的会签任务提交命令类设计如下:

public class SubmitJoinSignTaskCmd implements Command<Boolean> {      //传递会签意见的任务变量     public static final String VAR_SIGN = "sign";      //会签通过时的转移路径名称(由构造函数传入)     private String passTransitionName;      //会签否决时的转移路径名称(由构造函数传入)     private String noPassTransitionName;      //主任务 ID(由构造函数传入)     private String mainTaskId;      //主任务对象     private Task mainTask;      //流程实例 ID     private String instanceId;      //会签任务对象(Setter 方法传入)     private Task joinSignTask;      public void setJoinSignTask(Task joinSignTask) {         this.joinSignTask = joinSignTask;     }      public String getInstanceId() {         return instanceId;     }      public SubmitJoinSignTaskCmd(String mainTaskId, String passTransitionName, String noPassTransitionName) {         this.mainTaskId = mainTaskId;         this.passTransitionName = passTransitionName;         this.noPassTransitionName = noPassTransitionName;     }      @Override     public Boolean execute(Environment environment) throws Exception {         //获取任务服务         TaskService taskService = environment.get(TaskService.class);          //获取主任务与流程实例 ID         mainTask = taskService.getTask(mainTaskId);         instanceId = mainTask.getExecutionId();          //获取当前会签任务         String joinSignTaskId = joinSignTask.getId();          //从当前会签任务的任务变量中获取 ”会签意见“         String sign = (String) taskService.getVariable(joinSignTaskId, VAR_SIGN);          //规则如下:如果意见为“不同意”,则表示否决         if (sign != null && sign.equals("不同意")) {             //存在否决意见,则会签活动结束(一票否决制)             //结束会签任务             taskService.completeTask(joinSignTaskId);              //为主任务增加一条会签意见记录(注释)             taskService.addTaskComment(mainTaskId, "用户:" + joinSignTask.getAssignee()                     + ",会签意见:" + sign);              //结束主任务,流向【否决】转移             taskService.completeTask(mainTaskId, noPassTransitionName);              //会签结束             return true;         }          /**          * 通过会签          */         //完成会签任务         taskService.completeTask(joinSignTaskId);          //为主任务增加一条会签意见         taskService.addTaskComment(mainTaskId,"用户:"+joinSignTask.getAssignee()+                 ";会签意见为:"+sign);          //判定是否还有会签子任务         if(taskService.getSubTasks(mainTaskId).isEmpty()){//通过会签             //结束主任务,流向会签通过转移             taskService.completeTask(mainTaskId,passTransitionName);             return true;         }else{//会签活动还未结束             return false;         }     } }  

单元测试:

//发起流程实例 ProcessInstance processInstance=executionService.startProcessInstanceByKey("JointlySign"); instanceId=processInstance.getId();//实例 ID  //获取会签主任务 Task task=taskService.createTaskQuery().processInstanceId(instanceId)         .activityName(processInstance.findActiveActivityNames().iterator().next()         ).uniqueResult(); //断言当前活动为会签 assertTrue(processInstance.isActive("会签"));  List<Task> subTasks=taskService.getSubTasks(task.getId()); //断言主任务产生了 3 条子任务 assertEquals(3, subTasks.size());   //获取主任务 ID String taskId=task.getId(); //创建会签任务命令,指定会签通过转移以及会签否决转移 cmd=new SubmitJoinSignTaskCmd(taskId,"to execute","to end"); 

至此又分为两种情况:

否决会签:

//获取会签用户 Deniro 的任务 Task deniroTask=taskService.findPersonalTasks("Deniro").get(0);  //通过变量来模拟否决会签 Map<String,Object> vars=new HashMap<>(); vars.put(SubmitJoinSignTaskCmd.VAR_SIGN,"不同意"); taskService.setVariables(deniroTask.getId(), vars); cmd.setJoinSignTask(deniroTask);  //提交会签任务(执行自定义命令) boolean result= Configuration.getProcessEngine().execute(cmd);  //断言会签活动已完成 assertTrue(result);  //断言流程实例结束 assertProcessInstanceEnded(cmd.getInstanceId()); 

通过会签:

//获取会签用户 Deniro 的任务 Task deniroTask=taskService.findPersonalTasks("Deniro").get(0); cmd.setJoinSignTask(deniroTask);//不设置否决意见,即通过  //提交会签任务(执行自定义命令) boolean result= Configuration.getProcessEngine().execute(cmd);  //断言会签任务未完成 assertFalse(result);  //获取会签用户 Jack 的任务 Task jackTask=taskService.findPersonalTasks("Jack").get(0); cmd.setJoinSignTask(jackTask);//不设置否决意见,即通过  //提交会签任务(执行自定义命令) result= Configuration.getProcessEngine().execute(cmd);  //断言会签任务未完成 assertFalse(result);  //获取会签用户 Lucy 的任务 Task lucyTask=taskService.findPersonalTasks("Lucy").get(0); cmd.setJoinSignTask(lucyTask);//不设置否决意见,即通过  //提交会签任务(执行自定义命令) result= Configuration.getProcessEngine().execute(cmd);  //断言会签活动已完成 assertTrue(result);  //断言流程实例到达【执行】活动 ProcessInstance processInstance=executionService.findProcessInstanceById         (instanceId); assertTrue(processInstance.isActive("执行"));  //完成【执行】活动 String executionId=processInstance.findActiveExecutionIn("执行").getId(); executionService.signalExecutionById(executionId);  //断言流程实例结束 assertProcessInstanceEnded(instanceId); 

现在清楚了吧O(∩_∩)O哈哈~

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » 说说在 jBPM 工作流中如何实现【会签】功能

分享到:更多 ()