提问和思考
如果你是使用Activiti系列框架做的审批系统,那么当用户提出需要【转交】功能的时候,你需要如何实现?
如果你们的审批系统是自己设计的,没用框架,那么为了满足【转交】这个功能,设计时需要考虑哪些?
功能介绍
Activiti框架支不支持转交功能,有没有API?
答案是:这个可以有。
你要知道,这些工作流框架都是外国人写的,是,你可以说也有中国人提交mr,但是架构和思想还是遵从着西方人那一套。
如果你是工作流相关业务的开发人员,你一定听说过一个名词:中国式工作流。
delegateTask感兴趣的可以搜一搜这个API,虽然翻译是:授(权);把(工作、权力等)委托(给下级);选派(某人做某事)
但是你细究他的底层实现,以及调用这一系列API后,数据库表的变化,你会发现,效果和你想的,和用户想的根本不是一回事。
/*** 将任务委托给另一个人,修改act_ru_task表记录* 如果没有任务所有者(owner_),则将该字段置为任务的当前受理人* 委托状态(delegation_)变更为PENDING* 当前审批人(assignee_)变更为被委托人*/void delegateTask(String taskId, String userId);/*** 被加签人处理完毕,节点处理人回转到委托人* 只有在委托状态(delegation_)为PENDING才能执行* 委托状态(delegation_)变更为RESOLVED* 当前审批人(assignee_)变更为 委托人*/void resolveTask(String taskId);
看明白没,这个委派功能,说白了 审批人还是我,只不过暂时转给别人帮我审一下。
什么是转交?委托?委派?
其实不必纠结于叫法,你只需要明确这个功能的效果。
审批人由于各种原因,当前审批单无法审批了,他需要把这笔交给别人审。
于是他点了【转交】按钮,选了张三,然后这笔单的这个节点的审批人就变成张三了,且流程图也跟着相应的变化了。
或者你参考钉钉【转交】的实现效果。
我转交了审批单,这个审批单就和我没关系了,出了问题我不负责,谁审的去找谁。
事实上市面上很多审批系统的转交功能都是这个效果。
Activiti如何实现上面所说的转交效果
核心思想:转交 == 替换审批人
既然是这样,我们可以用Activiti为我们提供的
taskService.setAssignee(String taskId, String userId);
来覆盖掉act_hi_taskinst表和act_ru_task表的 ASSIGNEE_ 字段。
看起来很简单啊!
但是,问题是,流程实例中所有关于审批人的信息你都改了么?
真正的替换就要把旧的审批人的所有数据信息全部替换。
那么还有哪有呢?
这就要追溯到到发起流程,创建流程实例时的时候了。
runtimeService.startProcessInstanceByKey(
String processDefinitionKey, String businessKey, Map<String, Object> variables);
首先,你不可能把人直接写死在xx.bpmn文件里吧,大概率是通过变量(variables)设置审批人的。
所以你还需要把这些流程变量里的审批人都替换掉,在act_hi_varinst表和act_ru_variable表中。
API为:
runtimeService.setVariable(String executionId, String variableName, Object value);
最后一点
还需要自己记录每一次转交的记录,就是[谁]转交给了[谁]?
实现细节
1、转交接口参数最少2个,一个task_id,一个接收人的user_id
2、通过task_id查询Task信息(顺便校验task_id是否存在)
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
3、确认当前task所属哪个流程模版,即 流程定义ID
String procInstId = task.getProcessInstanceId();
String procDefId = historyService.createHistoricProcessInstanceQuery().processInstanceId(procInstId).singleResult().getProcessDefinitionId();
4、根据流程定义ID,解析流程模板,把所有用户任务节点都找出来
public Map<String, UserTask> getTasks(String procDefId) {Map<String, UserTask> map = new HashMap<>(16);BpmnModel bpmnModel = repositoryService.getBpmnModel(procDefId);List<org.activiti.bpmn.model.Process> processList = bpmnModel.getProcesses();if (processList == null || processList.size() == 0) {return map;}for (org.activiti.bpmn.model.Process process : processList) {if (process == null) {continue;}Collection<FlowElement> flowElementCollection = process.getFlowElements();if (flowElementCollection == null || flowElementCollection.size() == 0) {continue;}for (FlowElement flowElement : flowElementCollection) {if (!(flowElement instanceof UserTask)) {continue;}UserTask userTask = (UserTask) flowElement;map.put(userTask.getId(), userTask);}}return map;}
5、找到当前节点
UserTask userTask = map.get(task.getTaskDefinitionKey());
6、判断这个节点是不是多实例,然后取出 流程变量名
String variableName;
if (userTask.hasMultiInstanceLoopCharacteristics()) {MultiInstanceLoopCharacteristics milc =userTask.getLoopCharacteristics();variableName = Utils.resolveUel(milc.getInputDataItem());
} else {variableName = Utils.resolveUel(userTask.getAssignee());
}public static String resolveUel(String uel) {return uel.replace("${", "").replace("}", "").trim();
}
7、查询数据库里这个流程变量的信息
HistoricVariableInstance hvi = historyService.createHistoricVariableInstanceQuery().processInstanceId(procInstId).variableName(variableName).singleResult();
8、更改流程变量信息里的值
Object newValue;
if ("serializable".equals(hvi.getVariableTypeName())) {ArrayList<String> list = (ArrayList<String>) hvi.getValue();if (!list.contains(oldUserID) {throw new MyException("人不对");}Collections.replaceAll(list, oldUserID, newUserID);newValue = list;
} else {String userId = (String) hvi.getValue();if (!userId.equals(oldUserID)) {throw new MyException("人不对");}newValue = newUserID
}
9、事务:重新设置新审批人 + 修改流程变量 + 转交过程
taskService.setAssignee(taskId, newUserID);
runtimeService.setVariable(task.getExecutionId(), variableName, newValue);
insertXXX();
总结
如果你是自己设计的审批系统,针对转交功能的设计,一定要预留一个可以直接替换的接口。
消除旧人在这笔审批单中的所有痕迹,并替换成新人,再把转交过程记录下来。
这样的话,保证了数据一致性,不会留下隐患。