package com.iailab.module.bpm.framework.flowable.core.candidate; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.spring.SpringUtil; import com.iailab.framework.common.enums.CommonStatusEnum; import com.iailab.framework.common.util.object.ObjectUtils; import com.iailab.framework.datapermission.core.annotation.DataPermission; import com.iailab.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum; import com.iailab.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum; import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; import com.iailab.module.bpm.service.task.BpmProcessInstanceService; import com.iailab.module.system.api.user.AdminUserApi; import com.iailab.module.system.api.user.dto.AdminUserRespDTO; import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.FlowElement; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.runtime.ProcessInstance; import java.util.*; import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.iailab.module.bpm.enums.ErrorCodeConstants.MODEL_DEPLOY_FAIL_TASK_CANDIDATE_NOT_CONFIG; import static com.iailab.module.bpm.enums.ErrorCodeConstants.TASK_CREATE_FAIL_NO_CANDIDATE_USER; /** * {@link BpmTaskCandidateStrategy} 的调用者,用于调用对应的策略,实现任务的候选人的计算 * * @author iailab */ @Slf4j public class BpmTaskCandidateInvoker { private final Map strategyMap = new HashMap<>(); private final AdminUserApi adminUserApi; public BpmTaskCandidateInvoker(List strategyList, AdminUserApi adminUserApi) { strategyList.forEach(strategy -> { BpmTaskCandidateStrategy oldStrategy = strategyMap.put(strategy.getStrategy(), strategy); Assert.isNull(oldStrategy, "策略(%s) 重复", strategy.getStrategy()); }); this.adminUserApi = adminUserApi; } /** * 校验流程模型的任务分配规则全部都配置了 * 目的:如果有规则未配置,会导致流程任务找不到负责人,进而流程无法进行下去! * * @param bpmnBytes BPMN XML */ public void validateBpmnConfig(byte[] bpmnBytes) { BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes); assert bpmnModel != null; List userTaskList = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class); // 遍历所有的 UserTask,校验审批人配置 userTaskList.forEach(userTask -> { // 1.1 非人工审批,无需校验审批人配置 Integer approveType = BpmnModelUtils.parseApproveType(userTask); if (ObjectUtils.equalsAny(approveType, BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(), BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { return; } // 1.2 非空校验 Integer strategy = BpmnModelUtils.parseCandidateStrategy(userTask); String param = BpmnModelUtils.parseCandidateParam(userTask); if (strategy == null) { throw exception(MODEL_DEPLOY_FAIL_TASK_CANDIDATE_NOT_CONFIG, userTask.getName()); } BpmTaskCandidateStrategy candidateStrategy = getCandidateStrategy(strategy); if (candidateStrategy.isParamRequired() && StrUtil.isBlank(param)) { throw exception(MODEL_DEPLOY_FAIL_TASK_CANDIDATE_NOT_CONFIG, userTask.getName()); } // 2. 具体策略校验 getCandidateStrategy(strategy).validateParam(param); }); } /** * 计算任务的候选人 * * @param execution 执行任务 * @return 用户编号集合 */ @DataPermission(enable = false) // 忽略数据权限,避免因为过滤,导致找不到候选人 public Set calculateUsersByTask(DelegateExecution execution) { // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过 FlowElement flowElement = execution.getCurrentFlowElement(); Integer approveType = BpmnModelUtils.parseApproveType(flowElement); if (ObjectUtils.equalsAny(approveType, BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(), BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { return new HashSet<>(); } // 1.1 计算任务的候选人 Integer strategy = BpmnModelUtils.parseCandidateStrategy(flowElement); String param = BpmnModelUtils.parseCandidateParam(flowElement); Set userIds = getCandidateStrategy(strategy).calculateUsersByTask(execution, param); // 1.2 移除被禁用的用户 removeDisableUsers(userIds); // 2. 候选人为空时,根据“审批人为空”的配置补充 if (CollUtil.isEmpty(userIds)) { userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy()) .calculateUsersByTask(execution, param); // ASSIGN_EMPTY 策略,不需要移除被禁用的用户。原因是,再移除,可能会出现更没审批人了!!! } // 3. 移除发起人的用户 ProcessInstance processInstance = SpringUtil.getBean(BpmProcessInstanceService.class) .getProcessInstance(execution.getProcessInstanceId()); Assert.notNull(processInstance, "流程实例({}) 不存在", execution.getProcessInstanceId()); removeStartUserIfSkip(userIds, flowElement, Long.valueOf(processInstance.getStartUserId())); return userIds; } public Set calculateUsersByActivity(BpmnModel bpmnModel, String activityId, Long startUserId, String processDefinitionId, Map processVariables) { // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过 FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId); Integer approveType = BpmnModelUtils.parseApproveType(flowElement); if (ObjectUtils.equalsAny(approveType, BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(), BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { return new HashSet<>(); } // 1.1 计算任务的候选人 Integer strategy = BpmnModelUtils.parseCandidateStrategy(flowElement); String param = BpmnModelUtils.parseCandidateParam(flowElement); Set userIds = getCandidateStrategy(strategy).calculateUsersByActivity(bpmnModel, activityId, param, startUserId, processDefinitionId, processVariables); // 1.2 移除被禁用的用户 removeDisableUsers(userIds); // 2. 候选人为空时,根据“审批人为空”的配置补充 if (CollUtil.isEmpty(userIds)) { userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy()) .calculateUsersByActivity(bpmnModel, activityId, param, startUserId, processDefinitionId, processVariables); // ASSIGN_EMPTY 策略,不需要移除被禁用的用户。原因是,再移除,可能会出现更没审批人了!!! } // 3. 移除发起人的用户 removeStartUserIfSkip(userIds, flowElement, startUserId); return userIds; } @VisibleForTesting void removeDisableUsers(Set assigneeUserIds) { if (CollUtil.isEmpty(assigneeUserIds)) { return; } Map userMap = adminUserApi.getUserMap(assigneeUserIds); assigneeUserIds.removeIf(id -> { AdminUserRespDTO user = userMap.get(id); return user == null || CommonStatusEnum.isDisable(user.getStatus()); }); } /** * 如果“审批人与发起人相同时”,配置了 SKIP 跳过,则移除发起人 * * 注意:如果只有一个候选人,则不处理,避免无法审批 * * @param assigneeUserIds 当前分配的候选人 * @param flowElement 当前节点 * @param startUserId 发起人 */ @VisibleForTesting void removeStartUserIfSkip(Set assigneeUserIds, FlowElement flowElement, Long startUserId) { if (CollUtil.size(assigneeUserIds) <= 1) { return; } Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(flowElement); if (ObjectUtil.notEqual(assignStartUserHandlerType, BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { return; } assigneeUserIds.remove(startUserId); } private BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) { BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy); Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy); BpmTaskCandidateStrategy strategyObj = strategyMap.get(strategyEnum); Assert.notNull(strategyObj, "策略(%s) 不存在", strategy); return strategyObj; } }