Quellcode durchsuchen

feat: 只能看到同流程的审批人

wuwenyi vor 23 Stunden
Ursprung
Commit
cd577ea00d

+ 83 - 4
src/main/java/com/qqflow/engine/domain/flow/service/impl/ApprovalTaskServiceImpl.java

@@ -57,9 +57,11 @@ import java.io.File;
 import java.time.LocalDateTime;
 import java.time.temporal.ChronoUnit;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 import static com.qqflow.engine.common.constant.SecurityConstants.ASSIGNEE_TYPE_ROLE;
@@ -269,7 +271,7 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
         ProcessInstance instance = this.getActiveInstance(task.getInstanceId());
         Long operatorId = SecurityUtils.getUserId();
         Long transferToUserId = dto.getTransferToUserId() != null ? dto.getTransferToUserId() : dto.getTransferTo();
-        this.validateTransferTarget(transferToUserId, operatorId);
+        this.validateTransferTarget(transferToUserId, operatorId, instance);
         this.saveRecord(task, instance, operatorId, ApprovalAction.TRANSFER, ApprovalResult.TRANSFER.getCode(), dto.getComment(), null);
         this.updateTaskToTransferred(task);
         this.createTransferTask(task, instance, transferToUserId);
@@ -284,20 +286,20 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
         if (assigneeId == null) {
             throw new BusinessException("加签人不能为空");
         }
-        this.validateTransferTarget(assigneeId, null);
+        ProcessInstance instance = this.processInstanceMapper.selectById(task.getInstanceId());
+        this.validateTransferTarget(assigneeId, null, instance);
         ApprovalTask newTask = this.approvalTaskAssembler.buildNew(
                 task.getInstanceId(), task.getNodeId(), task.getNodeName(),
                 task.getNodeType(), assigneeId, ASSIGNEE_TYPE_USER, TaskStatus.PENDING.getCode()
         );
         this.approvalTaskMapper.insert(newTask);
-        ProcessInstance instance = this.processInstanceMapper.selectById(task.getInstanceId());
         Long operatorId = SecurityUtils.getUserId();
         this.saveRecord(task, instance, operatorId, ApprovalAction.ADD_SIGN, ApprovalResult.PASS.getCode(), null, null);
         this.publishTaskCompletedEvent(task, instance, operatorId,
                 ApprovalAction.ADD_SIGN.getCode(), ApprovalResult.PASS.getCode());
     }
 
-    private void validateTransferTarget(Long targetUserId, Long operatorId) {
+    private void validateTransferTarget(Long targetUserId, Long operatorId, ProcessInstance instance) {
         if (targetUserId == null) {
             throw new BusinessException("转办/加签人不能为空");
         }
@@ -311,6 +313,12 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
         if (targetUser.getStatus() != null && targetUser.getStatus() != 0) {
             throw new BusinessException("转办/加签人已被禁用");
         }
+        // 外部系统发起的流程(如优惠券审批)只允许在本流程审批人范围内转办/加签,
+        // 防止绕开选人接口直接指定 QQFlow 内部或其他商户人员
+        if (instance != null && this.isExternalInstance(instance)
+                && !this.resolveInstanceApproverIds(instance).contains(targetUserId)) {
+            throw new BusinessException("只能转办/加签给本流程的审批人");
+        }
     }
 
     @Override
@@ -683,6 +691,11 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
         if (instance == null) {
             throw new BusinessException("流程实例不存在");
         }
+        // 外部系统发起的流程(如优惠券审批):候选池收敛到本实例的审批人,
+        // 避免把 QQFlow 内部人员、其他商户审批人暴露给外部审批方
+        if (this.isExternalInstance(instance)) {
+            return this.listInstanceApprovers(instance, operatorId);
+        }
         ProcessDefinition definition = this.processDefinitionMapper.selectById(instance.getProcessDefinitionId());
         if (definition == null || definition.getModelJson() == null) {
             return this.listAllActiveSystemUsers(operatorId);
@@ -763,4 +776,70 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
                 .map(UserDTO::of)
                 .collect(Collectors.toList());
     }
+
+    /**
+     * 是否外部系统发起的流程:发起时由外部系统指定了各节点审批人(nodeAssignees)。
+     * <p>目前仅 wl-scrm 优惠券审批使用该字段,用于对这类流程做人员范围收敛。</p>
+     */
+    private boolean isExternalInstance(ProcessInstance instance) {
+        return StringUtils.hasText(instance.getNodeAssignees());
+    }
+
+    /**
+     * 流程实例内的审批人集合:发起时配置的各节点审批人 + 本实例已产生任务的接收人。
+     * <p>带上任务接收人是为了保证被转办/加签的人仍能在流程内继续流转。</p>
+     */
+    private Set<Long> resolveInstanceApproverIds(ProcessInstance instance) {
+        Set<Long> userIds = new LinkedHashSet<>();
+        if (StringUtils.hasText(instance.getNodeAssignees())) {
+            try {
+                Map<String, List<Long>> assignees = this.objectMapper.readValue(
+                        instance.getNodeAssignees(), new TypeReference<Map<String, List<Long>>>() {
+                        });
+                assignees.values().stream()
+                        .filter(Objects::nonNull)
+                        .forEach(userIds::addAll);
+            } catch (Exception e) {
+                throw new BusinessException("解析外部指定审批人失败:" + e.getMessage());
+            }
+        }
+        List<ApprovalTask> tasks = this.approvalTaskMapper.selectList(
+                new LambdaQueryWrapper<ApprovalTask>()
+                        .eq(ApprovalTask::getInstanceId, instance.getId()));
+        for (ApprovalTask task : tasks) {
+            // ROLE 任务的 assigneeId 是角色ID,不能当作用户ID
+            if (ASSIGNEE_TYPE_USER.equals(task.getAssigneeType()) && task.getAssigneeId() != null) {
+                userIds.add(task.getAssigneeId());
+            }
+        }
+        return userIds;
+    }
+
+    /**
+     * 本流程审批人列表:仅返回选人必要字段,不暴露手机号、邮箱等敏感信息。
+     */
+    private List<UserDTO> listInstanceApprovers(ProcessInstance instance, Long excludeUserId) {
+        Set<Long> userIds = this.resolveInstanceApproverIds(instance);
+        if (userIds.isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<SysUser> users = this.sysUserMapper.selectList(
+                new LambdaQueryWrapper<SysUser>()
+                        .in(SysUser::getId, userIds)
+                        .eq(SysUser::getStatus, 0)
+                        .orderByAsc(SysUser::getRealName, SysUser::getId));
+        return users.stream()
+                .filter(u -> excludeUserId == null || !u.getId().equals(excludeUserId))
+                .map(this::buildPickerUser)
+                .collect(Collectors.toList());
+    }
+
+    private UserDTO buildPickerUser(SysUser user) {
+        UserDTO dto = new UserDTO();
+        dto.setId(user.getId());
+        dto.setUsername(user.getUsername());
+        dto.setRealName(user.getRealName());
+        dto.setDeptId(user.getDeptId());
+        return dto;
+    }
 }