云
6 天以前 a82d847bbb39e2417d4c9773ebc4fc51fdb0b8fc
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/util/CrmPermissionUtils.java
@@ -7,14 +7,19 @@
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.hrm.api.handover.HrmUserHandoverApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.github.yulichang.autoconfigure.MybatisPlusJoinProperties;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import lombok.extern.slf4j.Slf4j;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@@ -24,16 +29,77 @@
 *
 * @author HUIHUI
 */
@Slf4j
public class CrmPermissionUtils {
    /** 超级管理员查看客户时不受公海可见度开关影响。 */
    public static boolean isSuperAdmin() {
        Long userId = getLoginUserId();
        if (userId == null) {
            return false;
        }
        return SpringUtil.getBean(PermissionCommonApi.class)
                .hasAnyRoles(userId, RoleCodeEnum.SUPER_ADMIN.getCode());
    }
    /**
     * 校验用户是否是 CRM 管理员
     * 校验用户是否是 CRM 管理员(或超级管理员)
     *
     * @return 是/否
     */
    public static boolean isCrmAdmin() {
        PermissionCommonApi permissionApi = SpringUtil.getBean(PermissionCommonApi.class);
        return permissionApi.hasAnyRoles(getLoginUserId(), RoleCodeEnum.CRM_ADMIN.getCode());
        return permissionApi.hasAnyRoles(getLoginUserId(),
                RoleCodeEnum.SUPER_ADMIN.getCode(), RoleCodeEnum.CRM_ADMIN.getCode());
    }
    /**
     * 校验用户是否有查看全部客户的权限
     *
     * @return 是/否
     */
    private static boolean hasCustomerViewAllPermission() {
        PermissionCommonApi permissionApi = SpringUtil.getBean(PermissionCommonApi.class);
        return permissionApi.hasAnyPermissions(getLoginUserId(), "crm:customer:view-all");
    }
    /**
     * 获得"我负责的数据"对应的用户编号集合:本人 + 已将数据交接给我的离职用户
     *
     * 离职交接完成后,交接人需要能看到离职人名下的数据,因此这里通过 HrmUserHandoverApi 扩展。
     *
     * @param userId 当前用户编号
     * @return 用户编号集合,至少包含本人
     */
    public static Set<Long> getOwnerUserIds(Long userId) {
        Set<Long> ownerUserIds = new LinkedHashSet<>();
        if (userId == null) {
            return ownerUserIds;
        }
        ownerUserIds.add(userId);
        try {
            HrmUserHandoverApi handoverApi = SpringUtil.getBean(HrmUserHandoverApi.class);
            Set<Long> handoverUserIds = handoverApi.expandUserIds(new HashSet<>(ownerUserIds));
            if (CollUtil.isNotEmpty(handoverUserIds)) {
                ownerUserIds.addAll(handoverUserIds);
            }
        } catch (Exception ex) {
            // HRM 模块未部署时降级为只看本人数据,不影响 CRM 正常使用
            log.warn("[getOwnerUserIds][扩展交接用户失败,userIds({}) 仅按本人数据查询]", userId, ex);
        }
        return ownerUserIds;
    }
    /**
     * 追加"我负责的数据"的过滤条件,支持离职交接后的数据可见性扩展
     */
    private static <T extends MPJLambdaWrapper<?>> void appendOwnerUserCondition(T query, String ownerUserIdField, Long userId) {
        Set<Long> ownerUserIds = getOwnerUserIds(userId);
        if (ownerUserIds.size() <= 1) {
            query.eq(ownerUserIdField, userId);
        } else {
            query.in(ownerUserIdField, ownerUserIds);
        }
    }
    /**
@@ -49,20 +115,25 @@
                                                                                    Long userId, Integer sceneType) {
        MybatisPlusJoinProperties mybatisPlusJoinProperties = SpringUtil.getBean(MybatisPlusJoinProperties.class);
        final String ownerUserIdField = mybatisPlusJoinProperties.getTableAlias() + ".owner_user_id";
        // 场景一:我负责的数据
        // 场景一:我负责的数据(含离职交接给我的数据)
        if (CrmSceneTypeEnum.isOwner(sceneType)) {
            query.eq(ownerUserIdField, userId);
            appendOwnerUserCondition(query, ownerUserIdField, userId);
        }
        // 场景二:我参与的数据(我有读或写权限,并且不是负责人)
        if (CrmSceneTypeEnum.isInvolved(sceneType)) {
            if (CrmPermissionUtils.isCrmAdmin()) { // 特殊逻辑:如果是超管,直接查询所有,不过滤数据权限
                return;
            }
            query.innerJoin(CrmPermissionDO.class, on -> on.eq(CrmPermissionDO::getBizType, bizType)
                    .eq(CrmPermissionDO::getBizId, bizId)
                    .in(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.READ.getLevel(), CrmPermissionLevelEnum.WRITE.getLevel())
                    .eq(CrmPermissionDO::getUserId,userId));
            query.ne(ownerUserIdField, userId);
            // 商机特殊处理:同时关联商机团队和客户团队的权限
            if (CrmBizTypeEnum.CRM_BUSINESS.getType().equals(bizType)) {
                appendBusinessPermissionCondition(query, bizId, userId, ownerUserIdField);
            } else {
                query.innerJoin(CrmPermissionDO.class, on -> on.eq(CrmPermissionDO::getBizType, bizType)
                        .eq(CrmPermissionDO::getBizId, bizId)
                        .in(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.READ.getLevel(), CrmPermissionLevelEnum.WRITE.getLevel())
                        .eq(CrmPermissionDO::getUserId, userId));
                query.ne(ownerUserIdField, userId);
            }
        }
        // 场景三:下属负责的数据(下属是负责人)
        if (CrmSceneTypeEnum.isSubordinate(sceneType)) {
@@ -74,6 +145,37 @@
                query.in(ownerUserIdField, convertSet(subordinateUsers, AdminUserRespDTO::getId));
            }
        }
        // 场景四:全部数据(需要有 view-all 权限或 CRM 管理员)
        if (CrmSceneTypeEnum.isAll(sceneType)) {
            if (isCrmAdmin() || hasCustomerViewAllPermission()) {
                return; // 跳过过滤,查看全部
            }
            // 无权限则降级为"我负责的"(含离职交接给我的数据)
            appendOwnerUserCondition(query, ownerUserIdField, userId);
        }
    }
    /**
     * 商机特殊权限条件:同时关联商机团队和客户团队的权限
     * 商机继承客户团队的权限,即客户团队成员也能看到该客户下的商机
     */
    private static <T extends MPJLambdaWrapper<?>, S> void appendBusinessPermissionCondition(T query, SFunction<S, ?> bizId,
                                                                                              Long userId, String ownerUserIdField) {
        // 使用子查询方式:商机团队成员 OR 客户团队成员
        // 子查询1:商机团队成员权限
        String subQuery1 = "(SELECT p1.biz_id FROM crm_permission p1 " +
                "WHERE p1.biz_type = " + CrmBizTypeEnum.CRM_BUSINESS.getType() + " " +
                "AND p1.level IN (" + CrmPermissionLevelEnum.READ.getLevel() + "," + CrmPermissionLevelEnum.WRITE.getLevel() + ") " +
                "AND p1.user_id = " + userId + ")";
        // 子查询2:客户团队成员权限(通过商机的 customer_id 关联)
        String subQuery2 = "(SELECT b.id FROM crm_business b " +
                "INNER JOIN crm_permission p2 ON p2.biz_type = " + CrmBizTypeEnum.CRM_CUSTOMER.getType() + " " +
                "AND p2.biz_id = b.customer_id " +
                "AND p2.level IN (" + CrmPermissionLevelEnum.READ.getLevel() + "," + CrmPermissionLevelEnum.WRITE.getLevel() + ") " +
                "AND p2.user_id = " + userId + ")";
        // 使用 OR 连接两个子查询,并排除自己是负责人的情况
        query.apply("t.id IN (" + subQuery1 + " UNION " + subQuery2 + ")")
             .ne(ownerUserIdField, userId);
    }
}