3 天以前 111270df037596a04df97f787ca8b9199dd99866
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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
package cn.iocoder.yudao.module.crm.framework.permission.core.aop;
 
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
 
import java.util.*;
 
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CRM_PERMISSION_DENIED;
 
/**
 * Crm 数据权限校验 AOP 切面
 *
 * @author HUIHUI
 */
@Component
@Aspect
@Slf4j
public class CrmPermissionAspect {
 
    @Resource
    private CrmPermissionService crmPermissionService;
 
    @Resource
    private AdminUserApi adminUserApi;
 
    @Resource
    private CrmBusinessMapper businessMapper;
 
    @Before("@annotation(crmPermission)")
    public void doBefore(JoinPoint joinPoint, CrmPermission crmPermission) {
        // 1.1 获取相关属性值
        Map<String, Object> expressionValues = parseExpressions(joinPoint, crmPermission);
        Integer bizType = StrUtil.isEmpty(crmPermission.bizTypeValue()) ?
                crmPermission.bizType()[0].getType() : (Integer) expressionValues.get(crmPermission.bizTypeValue()); // 模块类型
        // 1.2 处理兼容多个 bizId 的情况
        Object object = expressionValues.get(crmPermission.bizId()); // 模块数据编号
        Set<Long> bizIds = new HashSet<>();
        if (object instanceof Collection<?>) {
            bizIds.addAll(convertSet((Collection<?>) object, item -> Long.parseLong(item.toString())));
        } else {
            bizIds.add(Long.parseLong(object.toString()));
        }
        Integer permissionLevel = crmPermission.level().getLevel(); // 需要的权限级别
 
        // 2. 逐个校验权限
        List<CrmPermissionDO> permissionList = crmPermissionService.getPermissionListByBiz(bizType, bizIds);
        Map<Long, List<CrmPermissionDO>> multiMap = convertMultiMap(permissionList, CrmPermissionDO::getBizId);
        bizIds.forEach(bizId -> validatePermission(bizType, bizId, multiMap.get(bizId), permissionLevel));
    }
 
    private void validatePermission(Integer bizType, Long bizId, List<CrmPermissionDO> bizPermissions, Integer permissionLevel) {
        // 1. 如果是超级管理员则直接通过
        if (CrmPermissionUtils.isCrmAdmin()) {
            return;
        }
        // 特殊:没有数据权限的情况,针对 READ 的特殊处理
        if (CollUtil.isEmpty(bizPermissions)) {
            // 1.1 公海数据,如果没有团队成员,大家也应该有 READ 权限才对
            if (CrmPermissionLevelEnum.isRead(permissionLevel)) {
                return;
            }
            // 没有数据权限的情况下超出了读权限直接报错,避免后面校验空指针
            throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(bizType));
        } else { // 1.2 有数据权限但是没有负责人的情况
            if (!anyMatch(bizPermissions, item -> CrmPermissionLevelEnum.isOwner(item.getLevel()))
                && CrmPermissionLevelEnum.isRead(permissionLevel)) {
                return;
            }
        }
 
        // 2. 只考虑自身的权限
        Long userId = getUserId();
        CrmPermissionDO userPermission = CollUtil.findOne(bizPermissions, permission -> ObjUtil.equal(permission.getUserId(), userId));
        if (userPermission != null) {
            if (isUserPermissionValid(userPermission, permissionLevel)) {
                return;
            }
        }
 
        // 3. 商机特殊处理:检查客户团队权限
        if (CrmBizTypeEnum.CRM_BUSINESS.getType().equals(bizType)) {
            if (validateBusinessCustomerPermission(bizId, userId, permissionLevel)) {
                return;
            }
        }
 
        // 4. 考虑下级的权限
        List<AdminUserRespDTO> subordinateUserIds = adminUserApi.getUserListBySubordinate(userId);
        for (Long subordinateUserId : convertSet(subordinateUserIds, AdminUserRespDTO::getId)) {
            CrmPermissionDO subordinatePermission = CollUtil.findOne(bizPermissions,
                    permission -> ObjUtil.equal(permission.getUserId(), subordinateUserId));
            if (subordinatePermission != null && isUserPermissionValid(subordinatePermission, permissionLevel)) {
                return;
            }
            // 商机特殊处理:检查客户团队权限(下属的)
            if (CrmBizTypeEnum.CRM_BUSINESS.getType().equals(bizType)) {
                if (validateBusinessCustomerPermission(bizId, subordinateUserId, permissionLevel)) {
                    return;
                }
            }
        }
 
        // 5. 没有权限,抛出异常
        log.info("[doBefore][userId({}) 要求权限({}) 实际权限({}) 数据校验错误]", // 打个 info 日志,方便后续排查问题、审计
                userId, permissionLevel, toJsonString(userPermission));
        throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(bizType));
    }
 
    /**
     * 商机特殊权限校验:检查用户是否有该商机所属客户的团队权限
     *
     * @param businessId     商机编号
     * @param userId         用户编号
     * @param permissionLevel 需要的权限级别
     * @return 是否有权限
     */
    private boolean validateBusinessCustomerPermission(Long businessId, Long userId, Integer permissionLevel) {
        // 获取商机的客户ID
        CrmBusinessDO business = businessMapper.selectById(businessId);
        if (business == null || business.getCustomerId() == null) {
            return false;
        }
        // 检查客户团队权限
        List<CrmPermissionDO> customerPermissions = crmPermissionService.getPermissionListByBiz(
                CrmBizTypeEnum.CRM_CUSTOMER.getType(), Collections.singleton(business.getCustomerId()));
        if (CollUtil.isEmpty(customerPermissions)) {
            // 客户公海数据,READ 权限放行
            return CrmPermissionLevelEnum.isRead(permissionLevel);
        }
        // 检查用户是否有客户团队的权限
        CrmPermissionDO userCustomerPermission = CollUtil.findOne(customerPermissions,
                permission -> ObjUtil.equal(permission.getUserId(), userId));
        if (userCustomerPermission != null && isUserPermissionValid(userCustomerPermission, permissionLevel)) {
            return true;
        }
        return false;
    }
 
    /**
     * 校验用户权限是否有效
     *
     * @param userPermission   用户拥有的权限
     * @param permissionLevel  需要的权限级别
     * @return 是否有效
     */
    @SuppressWarnings("RedundantIfStatement")
    private boolean isUserPermissionValid(CrmPermissionDO userPermission, Integer permissionLevel) {
        // 2.1 情况一:如果自己是负责人,则默认有所有权限
        if (CrmPermissionLevelEnum.isOwner(userPermission.getLevel())) {
            return true;
        }
        // 2.2 情况二:校验自己是否有读权限
        if (CrmPermissionLevelEnum.isRead(permissionLevel)) {
            if (CrmPermissionLevelEnum.isRead(userPermission.getLevel()) // 校验当前用户是否有读权限
                    || CrmPermissionLevelEnum.isWrite(userPermission.getLevel())) { // 校验当前用户是否有写权限
                return true;
            }
        }
        // 2.3 情况三:校验自己是否有写权限
        if (CrmPermissionLevelEnum.isWrite(permissionLevel)) {
            if (CrmPermissionLevelEnum.isWrite(userPermission.getLevel())) { // 校验当前用户是否有写权限
                return true;
            }
        }
        return false;
    }
 
    /**
     * 获得用户编号
     *
     * @return 用户编号
     */
    private static Long getUserId() {
        return WebFrameworkUtils.getLoginUserId();
    }
 
    private static Map<String, Object> parseExpressions(JoinPoint joinPoint, CrmPermission crmPermission) {
        // 1. 需要解析的表达式
        List<String> expressionStrings = new ArrayList<>(2);
        expressionStrings.add(crmPermission.bizId());
        if (StrUtil.isNotEmpty(crmPermission.bizTypeValue())) { // 为空则表示 bizType 有值
            expressionStrings.add(crmPermission.bizTypeValue());
        }
        // 2. 执行解析
        return SpringExpressionUtils.parseExpressions(joinPoint, expressionStrings);
    }
 
}