liu
3 天以前 8ad99e5f72085d14fd56e8faa7aa36701823c8b5
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
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.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 java.util.List;
 
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 
/**
 * 数据权限工具类
 *
 * @author HUIHUI
 */
public class CrmPermissionUtils {
 
    /**
     * 校验用户是否是 CRM 管理员(CRM 管理员或系统超级管理员)
     *
     * 超级管理员(super_admin)不受 CRM 数据权限限制,可以查看全部数据
     *
     * @return 是/否
     */
    public static boolean isCrmAdmin() {
        PermissionCommonApi permissionApi = SpringUtil.getBean(PermissionCommonApi.class);
        return permissionApi.hasAnyRoles(getLoginUserId(),
                RoleCodeEnum.CRM_ADMIN.getCode(), RoleCodeEnum.SUPER_ADMIN.getCode());
    }
 
    /**
     * 校验用户是否有查看全部客户的权限
     *
     * @return 是/否
     */
    private static boolean hasCustomerViewAllPermission() {
        PermissionCommonApi permissionApi = SpringUtil.getBean(PermissionCommonApi.class);
        return permissionApi.hasAnyPermissions(getLoginUserId(), "crm:customer:view-all");
    }
 
    /**
     * 构造 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) {
        // 特殊逻辑:CRM 管理员 / 超级管理员直接查询所有,不过滤数据权限
        if (isCrmAdmin()) {
            return;
        }
        MybatisPlusJoinProperties mybatisPlusJoinProperties = SpringUtil.getBean(MybatisPlusJoinProperties.class);
        final String ownerUserIdField = mybatisPlusJoinProperties.getTableAlias() + ".owner_user_id";
        // 场景一:我负责的数据
        if (CrmSceneTypeEnum.isOwner(sceneType)) {
            query.eq(ownerUserIdField, userId);
        }
        // 场景二:我参与的数据(我有读或写权限,并且不是负责人)
        if (CrmSceneTypeEnum.isInvolved(sceneType)) {
            // 需要继承客户团队的模块:数据自身团队 ∪ 客户负责人/客户团队
            String tableName = getInheritCustomerTeamTable(bizType);
            if (tableName != null) {
                appendCustomerInheritedPermissionCondition(query, bizType, tableName, 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; // 跳过过滤,查看全部
            }
            // 无权限则降级为"我负责的"
            query.eq(ownerUserIdField, userId);
        }
    }
 
    /**
     * 获得「继承客户团队」的业务表名;不需要继承的返回 null
     *
     * 目前统一为:联系人、商机、合同、销售报价、回款、回款计划、开票
     */
    private static String getInheritCustomerTeamTable(Integer bizType) {
        if (CrmBizTypeEnum.CRM_CONTACT.getType().equals(bizType)) {
            return "crm_contact";
        }
        if (CrmBizTypeEnum.CRM_BUSINESS.getType().equals(bizType)) {
            return "crm_business";
        }
        if (CrmBizTypeEnum.CRM_CONTRACT.getType().equals(bizType)) {
            return "crm_contract";
        }
        if (CrmBizTypeEnum.CRM_SALE_QUOTATION.getType().equals(bizType)) {
            return "crm_sale_quotation";
        }
        if (CrmBizTypeEnum.CRM_RECEIVABLE.getType().equals(bizType)) {
            return "crm_receivable";
        }
        if (CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType().equals(bizType)) {
            return "crm_receivable_plan";
        }
        if (CrmBizTypeEnum.CRM_INVOICE.getType().equals(bizType)) {
            return "crm_invoice";
        }
        return null;
    }
 
    /**
     * 继承客户团队的数据权限:数据自身团队 ∪ 客户负责人/客户团队
     *
     * @param query            连表查询对象
     * @param bizType          数据类型
     * @param tableName        业务表名
     * @param userId           用户编号
     * @param ownerUserIdField 负责人字段
     */
    private static <T extends MPJLambdaWrapper<?>> void appendCustomerInheritedPermissionCondition(
            T query, Integer bizType, String tableName, Long userId, String ownerUserIdField) {
        // 子查询1:数据自身的团队成员权限
        String subQuery1 = "(SELECT p1.biz_id FROM crm_permission p1 " +
                "WHERE p1.biz_type = " + bizType + " " +
                "AND p1.level IN (" + CrmPermissionLevelEnum.OWNER.getLevel() + "," +
                CrmPermissionLevelEnum.READ.getLevel() + "," + CrmPermissionLevelEnum.WRITE.getLevel() + ") " +
                "AND p1.user_id = " + userId + " AND p1.deleted = 0)";
        // 子查询2:所属客户的负责人 + 团队成员
        // 注意:以 crm_customer.owner_user_id 为准,兼容权限表中负责人记录缺失/不一致的历史数据
        String subQuery2 = "(SELECT x.id FROM " + tableName + " x " +
                "INNER JOIN crm_customer cu ON cu.id = x.customer_id AND cu.deleted = 0 " +
                "WHERE x.deleted = 0 AND (cu.owner_user_id = " + userId +
                " OR EXISTS (SELECT 1 FROM crm_permission p2 " +
                "WHERE p2.biz_type = " + CrmBizTypeEnum.CRM_CUSTOMER.getType() + " " +
                "AND p2.biz_id = x.customer_id AND p2.user_id = " + userId + " AND p2.deleted = 0)))";
        // 使用 OR 连接两个子查询,并排除自己是负责人的情况
        query.apply("t.id IN (" + subQuery1 + " UNION " + subQuery2 + ")")
             .ne(ownerUserIdField, userId);
    }
 
}