From ba19342fa9b0c03db0b4c7265670a8c4c0829bab Mon Sep 17 00:00:00 2001 From: yang chen Date: Mon, 17 Nov 2025 10:30:19 +0800 Subject: [PATCH] feat(bpm): add admin cancel process instance functionality - Added admin-cancel endpoint in BpmProcessInstanceController - Implemented adminCancelProcessInstance method in service layer - Added permission check for admin cancel operation - Handled both active and historic process instance cancellation - Updated bill approval status after cancellation - Added corresponding service method declaration in interface --- .../BpmProcessInstanceController.java | 8 + .../service/IBpmProcessInstanceService.java | 8 + .../impl/BpmProcessInstanceServiceImpl.java | 485 +++++++++++++++++- 3 files changed, 500 insertions(+), 1 deletion(-) diff --git a/jeelowcode-service/jeelowcode-service-bpm-biz/src/main/java/com/jeelowcode/service/bpm/controller/BpmProcessInstanceController.java b/jeelowcode-service/jeelowcode-service-bpm-biz/src/main/java/com/jeelowcode/service/bpm/controller/BpmProcessInstanceController.java index 3548826..056effe 100644 --- a/jeelowcode-service/jeelowcode-service-bpm-biz/src/main/java/com/jeelowcode/service/bpm/controller/BpmProcessInstanceController.java +++ b/jeelowcode-service/jeelowcode-service-bpm-biz/src/main/java/com/jeelowcode/service/bpm/controller/BpmProcessInstanceController.java @@ -84,4 +84,12 @@ public class BpmProcessInstanceController extends BaseController { return success(true); } + @DeleteMapping("/admin-cancel") + @Operation(tags = "流程管理", summary = "管理员取消流程实例", description = "管理员强制取消任意流程实例") + @PreAuthorize("@ss.hasPermission('bpm:process-instance:admin-cancel')") + public CommonResult adminCancelProcessInstance(@Valid @RequestBody BpmProcessInstanceCancelReqVO cancelReqVO) { + processInstanceService.adminCancelProcessInstance(getLoginUserId(), cancelReqVO); + return success(true); + } + } diff --git a/jeelowcode-service/jeelowcode-service-bpm-biz/src/main/java/com/jeelowcode/service/bpm/service/IBpmProcessInstanceService.java b/jeelowcode-service/jeelowcode-service-bpm-biz/src/main/java/com/jeelowcode/service/bpm/service/IBpmProcessInstanceService.java index 327ecdf..8aa4f0a 100644 --- a/jeelowcode-service/jeelowcode-service-bpm-biz/src/main/java/com/jeelowcode/service/bpm/service/IBpmProcessInstanceService.java +++ b/jeelowcode-service/jeelowcode-service-bpm-biz/src/main/java/com/jeelowcode/service/bpm/service/IBpmProcessInstanceService.java @@ -111,6 +111,14 @@ public interface IBpmProcessInstanceService { */ void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO); + /** + * 管理员取消流程实例 + * + * @param adminUserId 管理员用户编号 + * @param cancelReqVO 取消信息 + */ + void adminCancelProcessInstance(Long adminUserId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO); + /** * 获得历史的流程实例 * diff --git a/jeelowcode-service/jeelowcode-service-bpm-biz/src/main/java/com/jeelowcode/service/bpm/service/impl/BpmProcessInstanceServiceImpl.java b/jeelowcode-service/jeelowcode-service-bpm-biz/src/main/java/com/jeelowcode/service/bpm/service/impl/BpmProcessInstanceServiceImpl.java index 592a602..2bc650f 100644 --- a/jeelowcode-service/jeelowcode-service-bpm-biz/src/main/java/com/jeelowcode/service/bpm/service/impl/BpmProcessInstanceServiceImpl.java +++ b/jeelowcode-service/jeelowcode-service-bpm-biz/src/main/java/com/jeelowcode/service/bpm/service/impl/BpmProcessInstanceServiceImpl.java @@ -1 +1,484 @@ -package com.jeelowcode.service.bpm.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; import com.baomidou.dynamic.datasource.annotation.DSTransactional; import com.jeelowcode.service.bpm.config.convert.task.BpmProcessInstanceConvert; import com.jeelowcode.service.bpm.config.framework.bpm.core.event.BpmProcessInstanceResultEventPublisher; import com.jeelowcode.service.bpm.controller.vo.enu.BpmFormType; import com.jeelowcode.service.bpm.controller.vo.instance.*; import com.jeelowcode.service.bpm.dto.BpmProcessInstanceCreateReqDTO; import com.jeelowcode.service.bpm.entity.BpmProcessDefinitionExtDO; import com.jeelowcode.service.bpm.entity.BpmProcessInstanceExtDO; import com.jeelowcode.service.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum; import com.jeelowcode.service.bpm.enums.task.BpmProcessInstanceResultEnum; import com.jeelowcode.service.bpm.enums.task.BpmProcessInstanceStatusEnum; import com.jeelowcode.service.bpm.mapper.BpmProcessInstanceExtMapper; import com.jeelowcode.service.bpm.service.IBpmMessageService; import com.jeelowcode.service.bpm.service.IBpmProcessDefinitionService; import com.jeelowcode.service.bpm.service.IBpmProcessInstanceService; import com.jeelowcode.service.bpm.service.IBpmTaskService; import com.jeelowcode.service.system.api.IApiAdminUserApi; import com.jeelowcode.service.system.api.IApiDeptApi; import com.jeelowcode.service.system.dto.AdminUserRespDTO; import com.jeelowcode.service.system.dto.DeptRespDTO; import com.jeelowcode.tool.framework.common.pojo.PageResult; import com.jeelowcode.tool.framework.common.util.number.NumberUtils; import com.jeelowcode.tool.framework.flowable.core.context.FlowableContextHolder; import lombok.extern.slf4j.Slf4j; import org.flowable.common.engine.api.delegate.event.FlowableEventType; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import javax.validation.Valid; import java.time.LocalDateTime; import java.util.*; import static com.jeelowcode.service.bpm.enums.ErrorCodeConstants.*; import static com.jeelowcode.tool.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.jeelowcode.tool.framework.common.util.collection.CollectionUtils.convertList; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements IBpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private BpmProcessInstanceExtMapper processInstanceExtMapper; @Resource @Lazy // 解决循环依赖 private IBpmTaskService taskService; @Resource private IBpmProcessDefinitionService processDefinitionService; @Resource private HistoryService historyService; @Resource private IApiAdminUserApi apiAdminUserApi; @Resource private IApiDeptApi apiDeptApi; @Resource private BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher; @Resource private IBpmMessageService messageService; @Resource private BillApproveStatusService billApproveStatusService; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getMyProcessInstancePage(Long userId, BpmProcessInstanceMyPageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 PageResult pageResult = processInstanceExtMapper.selectPage(userId, pageReqVO); if (CollUtil.isEmpty(pageResult.getList())) { return new PageResult<>(pageResult.getTotal()); } // 获得流程 Task Map List processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId); Map> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds); // 转换返回 return BpmProcessInstanceConvert.INSTANCE.convertPage(pageResult, taskMap); } @Override @DSTransactional(rollbackFor = Exception.class) public String createProcessInstanceV2(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(BpmFormType.ZUOYE.getBpmId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getAssignee()); } @Override @DSTransactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getAssignee()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getAssignee()); } @Override public BpmProcessInstanceRespVO getProcessInstanceVO(String id) { // 获得流程实例 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } BpmProcessInstanceExtDO processInstanceExt = processInstanceExtMapper.selectByProcessInstanceId(id); Assert.notNull(processInstanceExt, "流程实例拓展({}) 不存在", id); // 获得流程定义 ProcessDefinition processDefinition = processDefinitionService .getProcessDefinition(processInstance.getProcessDefinitionId()); Assert.notNull(processDefinition, "流程定义({}) 不存在", processInstance.getProcessDefinitionId()); BpmProcessDefinitionExtDO processDefinitionExt = processDefinitionService.getProcessDefinitionExt( processInstance.getProcessDefinitionId()); Assert.notNull(processDefinitionExt, "流程定义拓展({}) 不存在", id); String bpmnXml = processDefinitionService.getProcessDefinitionBpmnXML(processInstance.getProcessDefinitionId()); // 获得 User AdminUserRespDTO startUser = apiAdminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())); DeptRespDTO dept = null; if (startUser != null) { dept = apiDeptApi.getDept(startUser.getDeptId()); } // 拼接结果 return BpmProcessInstanceConvert.INSTANCE.convert2(processInstance, processInstanceExt, processDefinition, processDefinitionExt, bpmnXml, startUser, dept); } @Override public void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { // 如果找不到,则通过历史表查询 HistoricProcessInstance historicInstance = getHistoricProcessInstance(cancelReqVO.getId()); if (historicInstance != null) { // 只能取消自己的 if (!Objects.equals(historicInstance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 流程实例已经删除了,需要更新流程的状态 updateProcessInstanceExtCancel(new FlowableCancelledEvent() { @Override public Object getCause() { return BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason()); } @Override public String getExecutionId() { return ""; } @Override public String getProcessInstanceId() { return cancelReqVO.getId(); } @Override public String getProcessDefinitionId() { return ""; } @Override public String getScopeType() { return ""; } @Override public String getScopeId() { return ""; } @Override public String getSubScopeId() { return ""; } @Override public String getScopeDefinitionId() { return ""; } @Override public FlowableEventType getType() { return null; } }); // 修改单据审批状态 billApproveStatusService.afterCancel(historicInstance.getId()); } else { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } } else { // 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。通过历史表查询 deleteProcessInstance(cancelReqVO.getId(), BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason())); // 修改单据审批状态 billApproveStatusService.afterCancel(instance.getProcessInstanceId()); } } /** * 获得历史的流程实例 * * @param id 流程实例的编号 * @return 历史的流程实例 */ @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public void createProcessInstanceExt(ProcessInstance instance) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition2(instance.getProcessDefinitionId()); // 插入 BpmProcessInstanceExtDO 对象 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getId()) .setProcessDefinitionId(definition.getId()) .setName(instance.getProcessDefinitionName()) .setStartUserId(Long.valueOf(instance.getStartUserId())) .setCategory(definition.getCategory()) .setStatus(BpmProcessInstanceStatusEnum.RUNNING.getStatus()) .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); processInstanceExtMapper.insert(instanceExtDO); } @Override public void updateProcessInstanceExtCancel(FlowableCancelledEvent event) { // 判断是否为 Reject 不通过。如果是,则不进行更新. // 因为,updateProcessInstanceExtReject 方法,已经进行更新了 if (BpmProcessInstanceDeleteReasonEnum.isRejectReason((String) event.getCause())) { return; } // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(event.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.CANCEL.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override public void updateProcessInstanceExtComplete(ProcessInstance instance) { // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); // 如果正常完全,说明审批通过 processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被通过的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.convert2ApprovedReq(instance)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override @DSTransactional(rollbackFor = Exception.class) public void updateProcessInstanceExtReject(String id, String reason) { // 需要主动查询,因为 instance 只有 id 属性 ProcessInstance processInstance = getProcessInstance(id); // 删除流程实例,以实现驳回任务时,取消整个审批流程 deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(reason))); // 更新 status + result // 注意,不能和上面的逻辑更换位置。因为 deleteProcessInstance 会触发流程的取消,进而调用 updateProcessInstanceExtCancel 方法, // 设置 result 为 BpmProcessInstanceStatusEnum.CANCEL,显然和 result 不一定是一致的 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO().setProcessInstanceId(id) .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.REJECT.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被不通过的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert2RejectReq(processInstance, reason)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> assignee) { // 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 设置上下文信息 // TODO @hai:要不往 variables 存到一个全局固定 key 里,减少对上下文的依赖 FlowableContextHolder.setAssignee(assignee); // 创建流程实例 ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); // 设置流程名字 runtimeService.setProcessInstanceName(instance.getId(), definition.getName()); // 补全流程实例的拓展表 processInstanceExtMapper.updateByProcessInstanceId(new BpmProcessInstanceExtDO().setProcessInstanceId(instance.getId()) .setFormVariables(variables).setAssignee(assignee)); billApproveStatusService.afterSubmit(instance.getProcessInstanceId()); return instance.getId(); } @Override public List getAssigneeByProcessInstanceIdAndTaskDefinitionKey(String processInstanceId, String taskDefinitionKey) { // 1. 先从上下文获取,首次提交数据库中查询不到 List result = FlowableContextHolder.getAssigneeByTaskDefinitionKey(taskDefinitionKey); if (CollUtil.isNotEmpty(result)) { return result; } // 2. 从数据库中获取 BpmProcessInstanceExtDO instance = processInstanceExtMapper.selectByProcessInstanceId(processInstanceId); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } if (CollUtil.isNotEmpty(instance.getAssignee())) { return instance.getAssignee().get(taskDefinitionKey); } return Collections.emptyList(); } } \ No newline at end of file +package com.jeelowcode.service.bpm.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; +import com.baomidou.dynamic.datasource.annotation.DSTransactional; +import com.jeelowcode.service.bpm.config.convert.task.BpmProcessInstanceConvert; +import com.jeelowcode.service.bpm.config.framework.bpm.core.event.BpmProcessInstanceResultEventPublisher; +import com.jeelowcode.service.bpm.controller.vo.enu.BpmFormType; +import com.jeelowcode.service.bpm.controller.vo.instance.*; +import com.jeelowcode.service.bpm.dto.BpmProcessInstanceCreateReqDTO; +import com.jeelowcode.service.bpm.entity.BpmProcessDefinitionExtDO; +import com.jeelowcode.service.bpm.entity.BpmProcessInstanceExtDO; +import com.jeelowcode.service.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum; +import com.jeelowcode.service.bpm.enums.task.BpmProcessInstanceResultEnum; +import com.jeelowcode.service.bpm.enums.task.BpmProcessInstanceStatusEnum; +import com.jeelowcode.service.bpm.mapper.BpmProcessInstanceExtMapper; +import com.jeelowcode.service.bpm.service.IBpmMessageService; +import com.jeelowcode.service.bpm.service.IBpmProcessDefinitionService; +import com.jeelowcode.service.bpm.service.IBpmProcessInstanceService; +import com.jeelowcode.service.bpm.service.IBpmTaskService; +import com.jeelowcode.service.system.api.IApiAdminUserApi; +import com.jeelowcode.service.system.api.IApiDeptApi; +import com.jeelowcode.service.system.dto.AdminUserRespDTO; +import com.jeelowcode.service.system.dto.DeptRespDTO; +import com.jeelowcode.tool.framework.common.pojo.PageResult; +import com.jeelowcode.tool.framework.common.util.number.NumberUtils; +import com.jeelowcode.tool.framework.flowable.core.context.FlowableContextHolder; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.api.delegate.event.FlowableEventType; +import org.flowable.engine.HistoryService; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.delegate.event.FlowableCancelledEvent; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.Task; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.time.LocalDateTime; +import java.util.*; + +import static com.jeelowcode.service.bpm.enums.ErrorCodeConstants.*; +import static com.jeelowcode.tool.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.jeelowcode.tool.framework.common.util.collection.CollectionUtils.convertList; + +/** + * 流程实例 Service 实现类 + *

+ * ProcessDefinition & ProcessInstance & Execution & Task 的关系: + * 1. + *

+ * HistoricProcessInstance & ProcessInstance 的关系: + * 1. + *

+ * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class BpmProcessInstanceServiceImpl implements IBpmProcessInstanceService { + + @Resource + private RuntimeService runtimeService; + @Resource + private BpmProcessInstanceExtMapper processInstanceExtMapper; + @Resource + @Lazy // 解决循环依赖 + private IBpmTaskService taskService; + @Resource + private IBpmProcessDefinitionService processDefinitionService; + @Resource + private HistoryService historyService; + @Resource + private IApiAdminUserApi apiAdminUserApi; + @Resource + private IApiDeptApi apiDeptApi; + @Resource + private BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher; + @Resource + private IBpmMessageService messageService; + @Resource + private BillApproveStatusService billApproveStatusService; + + @Override + public ProcessInstance getProcessInstance(String id) { + return runtimeService.createProcessInstanceQuery() + .includeProcessVariables() + .processInstanceId(id) + .singleResult(); + } + + @Override + public List getProcessInstances(Set ids) { + return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); + } + + @Override + public PageResult getMyProcessInstancePage(Long userId, + BpmProcessInstanceMyPageReqVO pageReqVO) { + // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 + PageResult pageResult = processInstanceExtMapper.selectPage(userId, pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return new PageResult<>(pageResult.getTotal()); + } + + // 获得流程 Task Map + List processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId); + Map> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds); + // 转换返回 + return BpmProcessInstanceConvert.INSTANCE.convertPage(pageResult, taskMap); + } + + @Override + @DSTransactional(rollbackFor = Exception.class) + public String createProcessInstanceV2(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { + // 获得流程定义 + ProcessDefinition definition = processDefinitionService.getProcessDefinition(BpmFormType.ZUOYE.getBpmId()); + // 发起流程 + return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getAssignee()); + } + + + @Override + @DSTransactional(rollbackFor = Exception.class) + public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { + // 获得流程定义 + ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); + // 发起流程 + return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getAssignee()); + } + + @Override + public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { + // 获得流程定义 + ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); + // 发起流程 + return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getAssignee()); + } + + @Override + public BpmProcessInstanceRespVO getProcessInstanceVO(String id) { + // 获得流程实例 + HistoricProcessInstance processInstance = getHistoricProcessInstance(id); + if (processInstance == null) { + return null; + } + BpmProcessInstanceExtDO processInstanceExt = processInstanceExtMapper.selectByProcessInstanceId(id); + Assert.notNull(processInstanceExt, "流程实例拓展({}) 不存在", id); + + // 获得流程定义 + ProcessDefinition processDefinition = processDefinitionService + .getProcessDefinition(processInstance.getProcessDefinitionId()); + Assert.notNull(processDefinition, "流程定义({}) 不存在", processInstance.getProcessDefinitionId()); + BpmProcessDefinitionExtDO processDefinitionExt = processDefinitionService.getProcessDefinitionExt( + processInstance.getProcessDefinitionId()); + Assert.notNull(processDefinitionExt, "流程定义拓展({}) 不存在", id); + String bpmnXml = processDefinitionService.getProcessDefinitionBpmnXML(processInstance.getProcessDefinitionId()); + + // 获得 User + AdminUserRespDTO startUser = apiAdminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())); + DeptRespDTO dept = null; + if (startUser != null) { + dept = apiDeptApi.getDept(startUser.getDeptId()); + } + + // 拼接结果 + return BpmProcessInstanceConvert.INSTANCE.convert2(processInstance, processInstanceExt, + processDefinition, processDefinitionExt, bpmnXml, startUser, dept); + } + + @Override + public void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { + // 校验流程实例存在 + ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); + if (instance == null) { + // 如果找不到,则通过历史表查询 + HistoricProcessInstance historicInstance = getHistoricProcessInstance(cancelReqVO.getId()); + if (historicInstance != null) { + // 只能取消自己的 + if (!Objects.equals(historicInstance.getStartUserId(), String.valueOf(userId))) { + throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); + } + // 流程实例已经删除了,需要更新流程的状态 + updateProcessInstanceExtCancel(new FlowableCancelledEvent() { + @Override + public Object getCause() { + return BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason()); + } + + @Override + public String getExecutionId() { + return ""; + } + + @Override + public String getProcessInstanceId() { + return cancelReqVO.getId(); + } + + @Override + public String getProcessDefinitionId() { + return ""; + } + + @Override + public String getScopeType() { + return ""; + } + + @Override + public String getScopeId() { + return ""; + } + + @Override + public String getSubScopeId() { + return ""; + } + + @Override + public String getScopeDefinitionId() { + return ""; + } + + @Override + public FlowableEventType getType() { + return null; + } + }); + // 修改单据审批状态 + billApproveStatusService.afterCancel(historicInstance.getId()); + } else { + throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); + } + } else { + // 只能取消自己的 + if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { + throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); + } + + // 通过删除流程实例,实现流程实例的取消, + // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。通过历史表查询 + deleteProcessInstance(cancelReqVO.getId(), + BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason())); + // 修改单据审批状态 + billApproveStatusService.afterCancel(instance.getProcessInstanceId()); + } + } + + @Override + public void adminCancelProcessInstance(Long adminUserId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { + // 校验流程实例存在 + ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); + if (instance == null) { + // 如果找不到,则通过历史表查询 + HistoricProcessInstance historicInstance = getHistoricProcessInstance(cancelReqVO.getId()); + if (historicInstance != null) { + // 流程实例已经删除了,需要更新流程的状态 + updateProcessInstanceExtCancel(new FlowableCancelledEvent() { + @Override + public Object getCause() { + return BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason()); + } + + @Override + public String getExecutionId() { + return ""; + } + + @Override + public String getProcessInstanceId() { + return cancelReqVO.getId(); + } + + @Override + public String getProcessDefinitionId() { + return ""; + } + + @Override + public String getScopeType() { + return ""; + } + + @Override + public String getScopeId() { + return ""; + } + + @Override + public String getSubScopeId() { + return ""; + } + + @Override + public String getScopeDefinitionId() { + return ""; + } + + @Override + public FlowableEventType getType() { + return null; + } + }); + // 修改单据审批状态 + billApproveStatusService.afterCancel(historicInstance.getId()); + } else { + throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); + } + } else { + // 管理员可以取消任意流程实例,不需要检查是否为发起人 + // 通过删除流程实例,实现流程实例的取消, + // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。通过历史表查询 + deleteProcessInstance(cancelReqVO.getId(), + BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason())); + // 修改单据审批状态 + billApproveStatusService.afterCancel(instance.getProcessInstanceId()); + } + } + + /** + * 获得历史的流程实例 + * + * @param id 流程实例的编号 + * @return 历史的流程实例 + */ + @Override + public HistoricProcessInstance getHistoricProcessInstance(String id) { + return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult(); + } + + @Override + public List getHistoricProcessInstances(Set ids) { + return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); + } + + @Override + public void createProcessInstanceExt(ProcessInstance instance) { + // 获得流程定义 + ProcessDefinition definition = processDefinitionService.getProcessDefinition2(instance.getProcessDefinitionId()); + // 插入 BpmProcessInstanceExtDO 对象 + BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() + .setProcessInstanceId(instance.getId()) + .setProcessDefinitionId(definition.getId()) + .setName(instance.getProcessDefinitionName()) + .setStartUserId(Long.valueOf(instance.getStartUserId())) + .setCategory(definition.getCategory()) + .setStatus(BpmProcessInstanceStatusEnum.RUNNING.getStatus()) + .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); + + processInstanceExtMapper.insert(instanceExtDO); + } + + @Override + public void updateProcessInstanceExtCancel(FlowableCancelledEvent event) { + // 判断是否为 Reject 不通过。如果是,则不进行更新. + // 因为,updateProcessInstanceExtReject 方法,已经进行更新了 + if (BpmProcessInstanceDeleteReasonEnum.isRejectReason((String) event.getCause())) { + return; + } + + // 需要主动查询,因为 instance 只有 id 属性 + // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance + HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); + // 更新拓展表 + BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() + .setProcessInstanceId(event.getProcessInstanceId()) + .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 + .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) + .setResult(BpmProcessInstanceResultEnum.CANCEL.getResult()); + processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); + + // 发送流程实例的状态事件 + processInstanceResultEventPublisher.sendProcessInstanceResultEvent( + BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); + } + + @Override + public void updateProcessInstanceExtComplete(ProcessInstance instance) { + // 需要主动查询,因为 instance 只有 id 属性 + // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance + HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); + // 更新拓展表 + BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() + .setProcessInstanceId(instance.getProcessInstanceId()) + .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 + .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) + .setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); // 如果正常完全,说明审批通过 + processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); + + // 发送流程被通过的消息 + messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.convert2ApprovedReq(instance)); + + // 发送流程实例的状态事件 + processInstanceResultEventPublisher.sendProcessInstanceResultEvent( + BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); + } + + @Override + @DSTransactional(rollbackFor = Exception.class) + public void updateProcessInstanceExtReject(String id, String reason) { + // 需要主动查询,因为 instance 只有 id 属性 + ProcessInstance processInstance = getProcessInstance(id); + // 删除流程实例,以实现驳回任务时,取消整个审批流程 + deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(reason))); + + // 更新 status + result + // 注意,不能和上面的逻辑更换位置。因为 deleteProcessInstance 会触发流程的取消,进而调用 updateProcessInstanceExtCancel 方法, + // 设置 result 为 BpmProcessInstanceStatusEnum.CANCEL,显然和 result 不一定是一致的 + BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO().setProcessInstanceId(id) + .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) + .setResult(BpmProcessInstanceResultEnum.REJECT.getResult()); + processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); + + // 发送流程被不通过的消息 + messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert2RejectReq(processInstance, reason)); + + // 发送流程实例的状态事件 + processInstanceResultEventPublisher.sendProcessInstanceResultEvent( + BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); + } + + private void deleteProcessInstance(String id, String reason) { + runtimeService.deleteProcessInstance(id, reason); + } + + private String createProcessInstance0(Long userId, ProcessDefinition definition, + Map variables, String businessKey, + Map> assignee) { + // 校验流程定义 + if (definition == null) { + throw exception(PROCESS_DEFINITION_NOT_EXISTS); + } + if (definition.isSuspended()) { + throw exception(PROCESS_DEFINITION_IS_SUSPENDED); + } + // 设置上下文信息 + // TODO @hai:要不往 variables 存到一个全局固定 key 里,减少对上下文的依赖 + FlowableContextHolder.setAssignee(assignee); + + // 创建流程实例 + ProcessInstance instance = runtimeService.createProcessInstanceBuilder() + .processDefinitionId(definition.getId()) + .businessKey(businessKey) + .name(definition.getName().trim()) + .variables(variables) + .start(); + // 设置流程名字 + runtimeService.setProcessInstanceName(instance.getId(), definition.getName()); + + // 补全流程实例的拓展表 + processInstanceExtMapper.updateByProcessInstanceId(new BpmProcessInstanceExtDO().setProcessInstanceId(instance.getId()) + .setFormVariables(variables).setAssignee(assignee)); + + billApproveStatusService.afterSubmit(instance.getProcessInstanceId()); + return instance.getId(); + } + + @Override + public List getAssigneeByProcessInstanceIdAndTaskDefinitionKey(String processInstanceId, String taskDefinitionKey) { + // 1. 先从上下文获取,首次提交数据库中查询不到 + List result = FlowableContextHolder.getAssigneeByTaskDefinitionKey(taskDefinitionKey); + if (CollUtil.isNotEmpty(result)) { + return result; + } + // 2. 从数据库中获取 + BpmProcessInstanceExtDO instance = processInstanceExtMapper.selectByProcessInstanceId(processInstanceId); + if (instance == null) { + throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); + } + if (CollUtil.isNotEmpty(instance.getAssignee())) { + return instance.getAssignee().get(taskDefinitionKey); + } + return Collections.emptyList(); + } + +} \ No newline at end of file