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

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

会签可以分为两种:

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

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

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

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

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

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

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

会签流程定义

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哈哈~

猜你喜欢

转载自blog.csdn.net/deniro_li/article/details/80634179
今日推荐