在发请流程之后经常会遇到,内容有误,申请撤销的操作。下面就聊一聊撤销操作。
我们定义一个接口,核心代码只有一行,就是 runtimeService.deleteProcessInstance(procInstId, "canceled-"+reason);
第一个参数是流程实例id 5001
这个ID是在启动实例的时候的工作流生成的,这里要区分的一点就是,这里的ID和流程定义ID不一定。流程ID是由三部分组成key:version:random number
忘记的可以看前面的文章。 第二个参数是撤销理由。 是的! 工作流引擎就是这么人性画, 撤销的时候居然还给一个参数用来设置理由 !!!! 真是牛逼!
其他的代码就是本项目中业务代码了,纯属存起来为了查询显示方便而已。
@Autowired
private RuntimeService runtimeService;
@RequestMapping(value = "/cancel", method = RequestMethod.POST)
@ApiOperation(value = "撤回申请")
public Result<Object> cancel(@RequestParam String id,
@RequestParam String procInstId,
@RequestParam(required = false) String reason){
if(StrUtil.isBlank(reason)){
reason = "";
}
runtimeService.deleteProcessInstance(procInstId, "canceled-"+reason);
ActBusiness actBusiness = actBusinessService.getById(id);
actBusiness.setStatus(ActivitiConstant.STATUS_CANCELED);
actBusiness.setResult(ActivitiConstant.RESULT_TO_SUBMIT);
actBusinessService.updateById(actBusiness);
return ResultUtil.success("操作成功");
}
撤销之后仍然可以在次发起流程,方法和第一次一样。
审批通过总共要分成两部分,第一步分查询当前节点的一个节点。第二步,审批通过。
完成这个任务我们需要两个查询条件第一个条件就是当前节点的ID( sid-5C4F7308-E8EC-476E-84D8-63D17C2B08D5
,请注意这里和当前流程的当前任务ID不是同一个。),第二个查询条件就是当前节点所在的流程定义的ID
(leave:1:7
) ,请注意这些ID的区分。
业务流程如下:
@RequestMapping(value = "/getNextNode/{procDefId}/{currActId}", method = RequestMethod.GET)
@ApiOperation(value = "通过当前节点定义id获取下一个节点")
public Result<ProcessNodeVo> getNextNode(@ApiParam("当前节点定义id") @PathVariable String procDefId,
@ApiParam("当前节点定义id") @PathVariable String currActId){
ProcessNodeVo node = new ProcessNodeVo();
// 当前执行节点id
ProcessDefinitionEntity dfe = (ProcessDefinitionEntity) ((RepositoryServiceImpl)repositoryService).getDeployedProcessDefinition(procDefId);
// 获取所有节点
List<ActivityImpl> activitiList = dfe.getActivities();
// 判断出当前流程所处节点,根据路径获得下一个节点实例
for(ActivityImpl activityImpl : activitiList){
if (activityImpl.getId().equals(currActId)) {
// 获取下一个节点
List<PvmTransition> pvmTransitions = activityImpl.getOutgoingTransitions();
PvmActivity pvmActivity = pvmTransitions.get(0).getDestination();
String type = pvmActivity.getProperty("type").toString();
if("userTask".equals(type)){
// 用户任务节点
Boolean customUser = actNodeService.hasCustomUser(pvmActivity.getId());
if(customUser){
// 是否为自选用户节点
node.setType(ActivitiConstant.NODE_TYPE_CUSTOM);
}else {
node.setType(ActivitiConstant.NODE_TYPE_TASK);
node.setTitle(pvmActivity.getProperty("name").toString());
// 设置关联用户
List<User> users = getNodetUsers(pvmActivity.getId());
node.setUsers(removeDuplicate(users));
}
}else if("exclusiveGateway".equals(type)){
// 排他网关
node.setType(ActivitiConstant.NODE_TYPE_EG);
}else if("parallelGateway".equals(type)){
// 平行网关
node.setType(ActivitiConstant.NODE_TYPE_PG);
}else if("endEvent".equals(type)){
// 结束
node.setType(ActivitiConstant.NODE_TYPE_END);
}else{
throw new TbootException("流程设计错误,包含无法处理的节点");
}
break;
}
}
return new ResultUtil<ProcessNodeVo>().setData(node);
}
我们一起来看看下面几个核心的API
taskService.addComment(id, procInstId, comment);
第一个是传的当前任务的ID(例如:5032) 第二个参数是当前流程实例的ID (例如:5021),第三个参数是审批意见(例如:同意)Task task = taskService.createTaskQuery().taskId(id).singleResult(); taskService.complete(id);
List tasks = taskService.createTaskQuery().processInstanceId(procInstId).list();
iHistoryIdentityService.insert(SnowFlakeUtil.nextId().toString(), ActivitiConstant.EXECUTOR_TYPE, securityUtil.getCurrUser().getId(), id, procInstId);
这个接口也是业务中自己写的接口不过是操作的工作流的表。ACT_HI_IDENTITYLINK
大家可以看看这个表的信息
@Autowired
private TaskService taskService;
@RequestMapping(value = "/pass", method = RequestMethod.POST)
@ApiOperation(value = "任务节点审批通过")
public Result<Object> pass(@ApiParam("任务id") @RequestParam String id,
@ApiParam("流程实例id") @RequestParam String procInstId,
@ApiParam("下个节点审批人") @RequestParam(required = false) String[] assignees,
@ApiParam("优先级") @RequestParam(required = false) Integer priority,
@ApiParam("意见评论") @RequestParam(required = false) String comment,
@ApiParam("是否发送站内消息") @RequestParam(defaultValue = "false") Boolean sendMessage,
@ApiParam("是否发送短信通知") @RequestParam(defaultValue = "false") Boolean sendSms,
@ApiParam("是否发送邮件通知") @RequestParam(defaultValue = "false") Boolean sendEmail){
if(StrUtil.isBlank(comment)){
comment = "";
}
taskService.addComment(id, procInstId, comment);
ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(procInstId).singleResult();
Task task = taskService.createTaskQuery().taskId(id).singleResult();
taskService.complete(id);
List<Task> tasks = taskService.createTaskQuery().processInstanceId(procInstId).list();
// 判断下一个节点
if(tasks!=null&&tasks.size()>0){
for(Task t : tasks){
if(assignees==null||assignees.length<1){
// 如果下个节点未分配审批人为空 取消结束流程
List<User> users = actProcessService.getNode(t.getTaskDefinitionKey()).getUsers();
if(users==null||users.size()==0){
runtimeService.deleteProcessInstance(procInstId, "canceled-审批节点未分配审批人,流程自动中断取消");
ActBusiness actBusiness = actBusinessService.getById(pi.getBusinessKey());
actBusiness.setStatus(ActivitiConstant.STATUS_CANCELED);
actBusiness.setResult(ActivitiConstant.RESULT_TO_SUBMIT);
actBusinessService.updateById(actBusiness);
break;
}else{
// 避免重复添加
List<String> list = iRunIdentityService.selectByConditions(t.getId(), "candidate");
if(list==null||list.size()==0) {
// 分配了节点负责人分发给全部
for (User user : users) {
taskService.addCandidateUser(t.getId(), user.getId());
// 异步发消息
messageUtil.sendActMessage(user.getId(), ActivitiConstant.MESSAGE_TODO_CONTENT, sendMessage, sendSms, sendEmail);
}
taskService.setPriority(t.getId(), task.getPriority());
}
}
}else{
// 避免重复添加
List<String> list = iRunIdentityService.selectByConditions(t.getId(), "candidate");
if(list==null||list.size()==0) {
for(String assignee : assignees){
taskService.addCandidateUser(t.getId(), assignee);
// 异步发消息
messageUtil.sendActMessage(assignee, ActivitiConstant.MESSAGE_TODO_CONTENT, sendMessage, sendSms, sendEmail);
taskService.setPriority(t.getId(), priority);
}
}
}
}
} else {
ActBusiness actBusiness = actBusinessService.getById(pi.getBusinessKey());
actBusiness.setStatus(ActivitiConstant.STATUS_FINISH);
actBusiness.setResult(ActivitiConstant.RESULT_PASS);
actBusinessService.updateById(actBusiness);
// 异步发消息
messageUtil.sendActMessage(actBusiness.getUserId(), ActivitiConstant.MESSAGE_PASS_CONTENT, sendMessage, sendSms, sendEmail);
}
// 记录实际审批人员
iHistoryIdentityService.insert(SnowFlakeUtil.nextId().toString(),
ActivitiConstant.EXECUTOR_TYPE, securityUtil.getCurrUser().getId(), id, procInstId);
return ResultUtil.success("操作成功");
}
我们这里还是要操作两步,首先获取可以返回的节点,获取到节点信息之后,然后在执行驳回操作。
这里最核心的API是 historyService查询历史任务节点中结束的任务节点
。然后将结点拼装成业务中自己定义的bean对象返回。 返回结果如下图。 id 为任务ID, key为流程定义中的节点ID。 name为任务名称。
这是一个数组,因为我们只有一个人审批过了,所以这里只有一条记录。 然后我们在真正驳回的时候选择是驳回到发起人,还是哪一个已经审批的人那里。
@Autowired
private HistoryService historyService;
@RequestMapping(value = "/getBackList/{procInstId}", method = RequestMethod.GET)
@ApiOperation(value = "获取可返回的节点")
public Result<Object> getBackList(@PathVariable String procInstId){
List<HistoricTaskVo> list = new ArrayList<>();
List<HistoricTaskInstance> taskInstanceList = historyService.createHistoricTaskInstanceQuery().processInstanceId(procInstId)
.finished().list();
taskInstanceList.forEach(e -> {
HistoricTaskVo htv = new HistoricTaskVo(e);
list.add(htv);
});
// 去重
LinkedHashSet<String> set = new LinkedHashSet<String>(list.size());
List<HistoricTaskVo> newList = new ArrayList<>();
list.forEach(e->{
if(set.add(e.getName())){
newList.add(e);
}
});
return ResultUtil.data(newList);
}
我们通过上面一步获取到节点信息之后就可以开始真证的驳回了。 我们还是来分析一下核心代码逻辑。
taskService.addComment(id, procInstId, comment);
@Autowired
private ManagementService managementService;
//取得流程定义
ProcessDefinitionEntity definition = (ProcessDefinitionEntity) repositoryService.getProcessDefinition(procDefId);
// 获取历史任务的Activity
ActivityImpl hisActivity = definition.findActivity(backTaskKey);
// 实现跳转
managementService.executeCommand(new JumpTask(procInstId, hisActivity.getId()));
// 重新分配原节点审批人
List<Task> tasks = taskService.createTaskQuery().processInstanceId(procInstId).list();
if(tasks!=null&&tasks.size()>0){
tasks.forEach(e->{
for(String assignee:assignees){
taskService.addCandidateUser(e.getId(), assignee);
// 异步发消息
messageUtil.sendActMessage(assignee, ActivitiConstant.MESSAGE_TODO_CONTENT, sendMessage, sendSms, sendEmail);
}
if(priority!=null){
taskService.setPriority(e.getId(), priority);
}
});
}
// 记录实际审批人员
iHistoryIdentityService.insert(SnowFlakeUtil.nextId().toString(),
ActivitiConstant.EXECUTOR_TYPE, securityUtil.getCurrUser().getId(), id, procInstId);
到此驳回就已经完成。
@RequestMapping(value = "/backToTask", method = RequestMethod.POST)
@ApiOperation(value = "任务节点审批驳回至指定历史节点")
public Result<Object> backToTask(@ApiParam("任务id") @RequestParam String id,
@ApiParam("驳回指定节点key") @RequestParam String backTaskKey,
@ApiParam("流程实例id") @RequestParam String procInstId,
@ApiParam("流程定义id") @RequestParam String procDefId,
@ApiParam("原节点审批人") @RequestParam(required = false) String[] assignees,
@ApiParam("优先级") @RequestParam(required = false) Integer priority,
@ApiParam("意见评论") @RequestParam(required = false) String comment,
@ApiParam("是否发送站内消息") @RequestParam(defaultValue = "false") Boolean sendMessage,
@ApiParam("是否发送短信通知") @RequestParam(defaultValue = "false") Boolean sendSms,
@ApiParam("是否发送邮件通知") @RequestParam(defaultValue = "false") Boolean sendEmail){
if(StrUtil.isBlank(comment)){
comment = "";
}
taskService.addComment(id, procInstId, comment);
// 取得流程定义
ProcessDefinitionEntity definition = (ProcessDefinitionEntity) repositoryService.getProcessDefinition(procDefId);
// 获取历史任务的Activity
ActivityImpl hisActivity = definition.findActivity(backTaskKey);
// 实现跳转
managementService.executeCommand(new JumpTask(procInstId, hisActivity.getId()));
// 重新分配原节点审批人
List<Task> tasks = taskService.createTaskQuery().processInstanceId(procInstId).list();
if(tasks!=null&&tasks.size()>0){
tasks.forEach(e->{
for(String assignee:assignees){
taskService.addCandidateUser(e.getId(), assignee);
// 异步发消息
messageUtil.sendActMessage(assignee, ActivitiConstant.MESSAGE_TODO_CONTENT, sendMessage, sendSms, sendEmail);
}
if(priority!=null){
taskService.setPriority(e.getId(), priority);
}
});
}
// 记录实际审批人员
iHistoryIdentityService.insert(SnowFlakeUtil.nextId().toString(),
ActivitiConstant.EXECUTOR_TYPE, securityUtil.getCurrUser().getId(), id, procInstId);
return ResultUtil.success("操作成功");
}
工作流的会签的流程我们就介绍完了, 下面对定点进行总结。