委派,又称为代理,是一种很常见的任务再分配模式。
委派的业务场景是这样的:任务已经分配给小李,但由于某种原因现在不方便办理,这时就需要委派给小王,代为办理。
实现委派功能两种思路:
- 不创建新的任务,而是直接修改原始任务的分配人(assignee 属性)。这样做会彻底断绝任务与原始分配人之间的关联关系,所以我们需要调用 TaskService 的
addTaskParticipatingUser ()
方法将任务的原始分配人作为一种特定的参与者与此任务建立关联;同时添加代理备注(TaskService 的addTaskComment()
),以供历史追溯。客户端需要实现 “我委派的任务” 列表,以供收回委派之用。这种方式可以方便地实现一个任务被委派多次的业务场景。 - 新创建委派任务,在原始任务的基础上创建委派子任务,以供代理人办理,这里我们需要注意同步原始任务及其子任务的业务数据,另外需要单独为办理委派任务,定义一个任务办理命令,在此命令中同时完成原始任务及其子任务,这种方式可以方便地实现一个任务被委派给多人办理的业务场景。
在此,我们按照思路一实现委派功能。
示例流程假设如下:
这个任务在定义中被分配给 Deniro,然后在后面的单元测试中,我们把这个任务委派给另外一个用户 Jack。
委派任务命令:
public class TaskDelegateCmd implements Command {
//任务 ID
private String taskId;
//受委托人 ID
private String delegateUserId;
public TaskDelegateCmd(String taskId, String delegateUserId) {
this.taskId = taskId;
this.delegateUserId = delegateUserId;
}
@Override
public Void execute(Environment environment) throws Exception {
//获取需要委派的任务
TaskService taskService = environment.get(TaskService.class);
Task task = taskService.getTask(taskId);
//把任务的当前处理者设置为 REPLACED_ASSIGNEE,方便以后【撤销委派】
taskService.addTaskParticipatingUser(taskId, task.getAssignee(), Participation.REPLACED_ASSIGNEE);
//记录【委派】日志
taskService.addTaskComment(taskId, task.getAssignee() + " 把任务委派给了 " + delegateUserId);
//把任务委派给受委托人
task.setAssignee(delegateUserId);
//持久化
taskService.saveTask(task);
return null;
}
}
单元测试:
//发起流程实例
ProcessInstance processInstance = executionService.startProcessInstanceByKey("Delegate");
instanceId = processInstance.getId();//实例 ID
//获取需要被委派的任务
Task task = taskService.findPersonalTasks("Deniro").get(0);
//委派给 Jack
Configuration.getProcessEngine().execute(new TaskDelegateCmd(task.getId(), "Jack"));
//获取 Jack 的任务
Task delegateTask = taskService.findPersonalTasks("Jack").get(0);
//断言该任务只有一个参与者(即委派人 Deniro)
List participations = taskService.getTaskParticipations(delegateTask
.getId());
assertEquals(1,participations.size());
assertEquals(Participation.REPLACED_ASSIGNEE, participations.get(0).getType());
//完成委派的任务
taskService.completeTask(delegateTask.getId());
//断言流程实例结束
assertProcessInstanceEnded(instanceId);
/**
* 以下验证流程历史
*/
//获取历史任务查询接口
HistoryTaskQuery historyTaskQuery=historyService.createHistoryTaskQuery();
//因为任务被委派给了 Jack 处理,所以历史中应该只有 Jack 的历史任务
assertEquals(0,historyTaskQuery.assignee("Deniro").list().size());
assertEquals(1,historyTaskQuery.assignee("Jack").list().size());
//获取任务注释对象
HistoryTask historyTask=historyTaskQuery.assignee("Jack").uniqueResult();
final HistoryDetailQuery historyDetailQuery = historyService.createHistoryDetailQuery()
.taskId(historyTask.getId());
List details=historyDetailQuery.list();
HistoryComment comment=(HistoryComment) details.get(0);
//断言任务注释
assertEquals("Deniro 把任务委派给了 Jack",comment.getMessage());
//获取任务参与者
List historyParticipations=taskService.getTaskParticipations
(historyTask.getId());
//断言任务提交后,对应的参与者信息被删除
assertEquals(0,historyParticipations.size());
关于第二种思路:即新创建委派任务,在原始任务的基础上创建委派子任务,以供代理人办理,我们可以自定义两条命令:
1、 委派任务命令
在此命令中根据原始任务,新创建一个子任务,把它委派给受委委托人:
public class TaskDelegateCmd2 implements Command {
//任务 ID
private String taskId;
//受委托人 ID
private String delegateUserId;
public TaskDelegateCmd2(String taskId, String delegateUserId) {
this.taskId = taskId;
this.delegateUserId = delegateUserId;
}
@Override
public TaskImpl execute(Environment environment) throws Exception {
//获取需要委派的任务
TaskService taskService = environment.get(TaskService.class);
Task task = taskService.getTask(taskId);
//定义【委派】日志
final String delegateMessage = task.getAssignee() + " 把任务委派给了 " + delegateUserId;
//记录【委派】日志
taskService.addTaskComment(taskId, delegateMessage);
//创建委派子任务
TaskImpl delegateTask = ((TaskImpl) task).createSubTask();
//把任务委派给受委托人
delegateTask.setAssignee(delegateUserId);
delegateTask.setDescription(delegateMessage);
//将原始任务的关键数据复制给委派任务
delegateTask.setName(task.getName());//任务名称
delegateTask.setFormResourceName(task.getFormResourceName());//任务表单
//...
//持久化原始任务与委派任务
taskService.saveTask(task);
taskService.saveTask(delegateTask);
return delegateTask;
}
}
2、提交委派任务命令
在此,同时完成委派任务和原始任务:
public class DelegateTaskCommitCmd implements Command {
//委派任务 ID
private String delegateTaskId;
public DelegateTaskCommitCmd(String delegateTaskId) {
this.delegateTaskId = delegateTaskId;
}
@Override
public Void execute(Environment environment) throws Exception {
TaskService taskService = environment.get(TaskService.class);
//根据委派任务获取原始任务
TaskImpl delegateTask = (TaskImpl) taskService.getTask(delegateTaskId);
Task originalTask = delegateTask.getSuperTask();
//完成委派任务与原始任务
taskService.completeTask(delegateTask.getId());
taskService.completeTask(originalTask.getId());
return null;
}
}
在实践中,【委派】还有一些业务场景需要考虑:
- 如何收回委派任务(可用自定义命令实现)。
- 如何支持给多个委托人(可能是多个人或者多个组)。
其实只要真正了解了 jBPM 工作流的原理以及它的命令模式,我们就可以扩展出上述这些实际业务逻辑所需要的功能啦O(∩_∩)O哈哈~