gaoluyang
3 天以前 d39e4f7d839167c16425086dfa2f8747bccfdff9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package cn.iocoder.yudao.module.crm.util;
 
import cn.hutool.core.collection.CollUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi;
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
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;
 
/**
 * 数据权限工具类
 *
 * @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 管理员(或超级管理员)
     *
     * @return 是/否
     */
    public static boolean isCrmAdmin() {
        PermissionCommonApi permissionApi = SpringUtil.getBean(PermissionCommonApi.class);
        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);
        }
    }
 
    /**
     * 构造 CRM 数据类型数据【分页】查询条件
     *
     * @param query     连表查询对象
     * @param bizType   数据类型 {@link CrmBizTypeEnum}
     * @param bizId     数据编号
     * @param userId    用户编号
     * @param sceneType 场景类型
     */
    public static <T extends MPJLambdaWrapper<?>, S> void appendPermissionCondition(T query, Integer bizType, SFunction<S, ?> bizId,
                                                                                    Long userId, Integer sceneType) {
        MybatisPlusJoinProperties mybatisPlusJoinProperties = SpringUtil.getBean(MybatisPlusJoinProperties.class);
        final String ownerUserIdField = mybatisPlusJoinProperties.getTableAlias() + ".owner_user_id";
        // 场景一:我负责的数据(含离职交接给我的数据)
        if (CrmSceneTypeEnum.isOwner(sceneType)) {
            appendOwnerUserCondition(query, ownerUserIdField, userId);
        }
        // 场景二:我参与的数据(我有读或写权限,并且不是负责人)
        if (CrmSceneTypeEnum.isInvolved(sceneType)) {
            if (CrmPermissionUtils.isCrmAdmin()) { // 特殊逻辑:如果是超管,直接查询所有,不过滤数据权限
                return;
            }
            // 商机特殊处理:同时关联商机团队和客户团队的权限
            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)) {
            AdminUserApi adminUserApi = SpringUtil.getBean(AdminUserApi.class);
            List<AdminUserRespDTO> subordinateUsers = adminUserApi.getUserListBySubordinate(userId);
            if (CollUtil.isEmpty(subordinateUsers)) {
                query.eq(ownerUserIdField, -1); // 不返回任何结果
            } else {
                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);
    }
 
}