liu
3 天以前 8ad99e5f72085d14fd56e8faa7aa36701823c8b5
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/aop/CrmPermissionAspect.java
@@ -6,8 +6,22 @@
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.contact.CrmContactDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.invoice.CrmInvoiceDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.quotation.CrmSaleQuotationDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO;
import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.contact.CrmContactMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.invoice.CrmInvoiceMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.quotation.CrmSaleQuotationMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivableMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivablePlanMapper;
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;
@@ -48,6 +62,20 @@
    @Resource
    private CrmBusinessMapper businessMapper;
    @Resource
    private CrmContractMapper contractMapper;
    @Resource
    private CrmContactMapper contactMapper;
    @Resource
    private CrmCustomerMapper customerMapper;
    @Resource
    private CrmSaleQuotationMapper quotationMapper;
    @Resource
    private CrmReceivableMapper receivableMapper;
    @Resource
    private CrmReceivablePlanMapper receivablePlanMapper;
    @Resource
    private CrmInvoiceMapper invoiceMapper;
    @Before("@annotation(crmPermission)")
    public void doBefore(JoinPoint joinPoint, CrmPermission crmPermission) {
@@ -79,33 +107,37 @@
        if (CrmPermissionUtils.isCrmAdmin()) {
            return;
        }
        // 特殊:没有数据权限的情况,针对 READ 的特殊处理
        // 2. 数据本身的负责人:直接通过(兼容权限记录缺失的历史数据)
        Long userId = getUserId();
        if (isRecordOwner(bizType, bizId, userId)) {
            return;
        }
        // 3. 联系人/商机/合同/报价/回款/回款计划/开票:继承所属客户的权限
        //    放在「无数据权限」判断之前,避免权限记录缺失的历史数据导致负责人无法写操作
        if (validateCustomerPermission(bizType, bizId, userId, permissionLevel)) {
            return;
        }
        // 3. 特殊:没有数据权限的情况,针对 READ 的特殊处理
        if (CollUtil.isEmpty(bizPermissions)) {
            // 1.1 公海数据,如果没有团队成员,大家也应该有 READ 权限才对
            if (CrmPermissionLevelEnum.isRead(permissionLevel)) {
            // 3.1 真正的公海数据(父客户也没有团队),大家有 READ 权限;
            //     如果父客户有团队,则走上面继承逻辑,这里不能放行,避免越权读取
            if (CrmPermissionLevelEnum.isRead(permissionLevel) && !hasParentCustomerTeam(bizType, bizId)) {
                return;
            }
            // 没有数据权限的情况下超出了读权限直接报错,避免后面校验空指针
            throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(bizType));
        } else { // 1.2 有数据权限但是没有负责人的情况
        } else { // 3.2 有数据权限但是没有负责人的情况
            if (!anyMatch(bizPermissions, item -> CrmPermissionLevelEnum.isOwner(item.getLevel()))
                && CrmPermissionLevelEnum.isRead(permissionLevel)) {
                return;
            }
        }
        // 2. 只考虑自身的权限
        Long userId = getUserId();
        // 4. 只考虑自身的权限
        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;
            }
        }
@@ -118,11 +150,9 @@
            if (subordinatePermission != null && isUserPermissionValid(subordinatePermission, permissionLevel)) {
                return;
            }
            // 商机特殊处理:检查客户团队权限(下属的)
            if (CrmBizTypeEnum.CRM_BUSINESS.getType().equals(bizType)) {
                if (validateBusinessCustomerPermission(bizId, subordinateUserId, permissionLevel)) {
                    return;
                }
            // 继承所属客户的权限(下属的)
            if (validateCustomerPermission(bizType, bizId, subordinateUserId, permissionLevel)) {
                return;
            }
        }
@@ -133,33 +163,161 @@
    }
    /**
     * 商机特殊权限校验:检查用户是否有该商机所属客户的团队权限
     * 继承所属客户的权限校验:客户负责人、客户团队成员
     *
     * @param businessId     商机编号
     * @param userId         用户编号
     * 适用于:联系人、商机、合同、销售报价、回款、回款计划、开票
     *
     * @param bizType         数据类型
     * @param bizId           数据编号
     * @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) {
    private boolean validateCustomerPermission(Integer bizType, Long bizId, Long userId, Integer permissionLevel) {
        // 1. 获取所属客户
        Long customerId = getCustomerId(bizType, bizId);
        if (customerId == 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)) {
        CrmCustomerDO customer = customerMapper.selectById(customerId);
        // 2. 客户负责人:直接通过(兼容权限表中负责人记录缺失/不一致的历史数据)
        if (customer != null && ObjUtil.equal(customer.getOwnerUserId(), userId)) {
            return true;
        }
        return false;
        // 3. 检查客户团队权限
        List<CrmPermissionDO> customerPermissions = crmPermissionService.getPermissionListByBiz(
                CrmBizTypeEnum.CRM_CUSTOMER.getType(), Collections.singleton(customerId));
        if (CollUtil.isEmpty(customerPermissions)) {
            // 客户有负责人但不是当前用户:不是公海,不能放行
            if (customer != null && customer.getOwnerUserId() != null) {
                return false;
            }
            // 真正的公海数据,READ 权限放行
            return CrmPermissionLevelEnum.isRead(permissionLevel);
        }
        // 4. 检查用户是否有客户团队的权限
        CrmPermissionDO userCustomerPermission = CollUtil.findOne(customerPermissions,
                permission -> ObjUtil.equal(permission.getUserId(), userId));
        return userCustomerPermission != null && isUserPermissionValid(userCustomerPermission, permissionLevel);
    }
    /**
     * 获取数据所属的客户编号
     *
     * @param bizType 数据类型
     * @param bizId   数据编号
     * @return 客户编号,不存在时返回 null
     */
    private Long getCustomerId(Integer bizType, Long bizId) {
        if (CrmBizTypeEnum.CRM_CONTACT.getType().equals(bizType)) {
            CrmContactDO contact = contactMapper.selectById(bizId);
            return contact == null ? null : contact.getCustomerId();
        }
        if (CrmBizTypeEnum.CRM_BUSINESS.getType().equals(bizType)) {
            CrmBusinessDO business = businessMapper.selectById(bizId);
            return business == null ? null : business.getCustomerId();
        }
        if (CrmBizTypeEnum.CRM_CONTRACT.getType().equals(bizType)) {
            CrmContractDO contract = contractMapper.selectById(bizId);
            return contract == null ? null : contract.getCustomerId();
        }
        if (CrmBizTypeEnum.CRM_SALE_QUOTATION.getType().equals(bizType)) {
            CrmSaleQuotationDO quotation = quotationMapper.selectById(bizId);
            return quotation == null ? null : quotation.getCustomerId();
        }
        if (CrmBizTypeEnum.CRM_RECEIVABLE.getType().equals(bizType)) {
            CrmReceivableDO receivable = receivableMapper.selectById(bizId);
            return receivable == null ? null : receivable.getCustomerId();
        }
        if (CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType().equals(bizType)) {
            CrmReceivablePlanDO plan = receivablePlanMapper.selectById(bizId);
            return plan == null ? null : plan.getCustomerId();
        }
        if (CrmBizTypeEnum.CRM_INVOICE.getType().equals(bizType)) {
            CrmInvoiceDO invoice = invoiceMapper.selectById(bizId);
            return invoice == null ? null : invoice.getCustomerId();
        }
        return null;
    }
    /**
     * 获取数据本身的负责人编号
     *
     * @param bizType 数据类型
     * @param bizId   数据编号
     * @return 负责人编号,不存在时返回 null
     */
    private Long getOwnerUserId(Integer bizType, Long bizId) {
        if (CrmBizTypeEnum.CRM_CONTACT.getType().equals(bizType)) {
            CrmContactDO contact = contactMapper.selectById(bizId);
            return contact == null ? null : contact.getOwnerUserId();
        }
        if (CrmBizTypeEnum.CRM_BUSINESS.getType().equals(bizType)) {
            CrmBusinessDO business = businessMapper.selectById(bizId);
            return business == null ? null : business.getOwnerUserId();
        }
        if (CrmBizTypeEnum.CRM_CONTRACT.getType().equals(bizType)) {
            CrmContractDO contract = contractMapper.selectById(bizId);
            return contract == null ? null : contract.getOwnerUserId();
        }
        if (CrmBizTypeEnum.CRM_SALE_QUOTATION.getType().equals(bizType)) {
            CrmSaleQuotationDO quotation = quotationMapper.selectById(bizId);
            return quotation == null ? null : quotation.getOwnerUserId();
        }
        if (CrmBizTypeEnum.CRM_RECEIVABLE.getType().equals(bizType)) {
            CrmReceivableDO receivable = receivableMapper.selectById(bizId);
            return receivable == null ? null : receivable.getOwnerUserId();
        }
        if (CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType().equals(bizType)) {
            CrmReceivablePlanDO plan = receivablePlanMapper.selectById(bizId);
            return plan == null ? null : plan.getOwnerUserId();
        }
        if (CrmBizTypeEnum.CRM_INVOICE.getType().equals(bizType)) {
            CrmInvoiceDO invoice = invoiceMapper.selectById(bizId);
            return invoice == null ? null : invoice.getOwnerUserId();
        }
        return null;
    }
    /**
     * 判断用户是否是数据本身的负责人
     *
     * 不依赖 crm_permission 记录,兼容历史数据中权限记录缺失的情况
     *
     * @param bizType 数据类型
     * @param bizId   数据编号
     * @param userId  用户编号
     * @return 是否是负责人
     */
    private boolean isRecordOwner(Integer bizType, Long bizId, Long userId) {
        return userId != null && ObjUtil.equal(getOwnerUserId(bizType, bizId), userId);
    }
    /**
     * 判断子数据所属客户是否存在团队(负责人或团队成员)
     *
     * 用于区分「真正的公海数据」和「父客户有团队、但子数据本身没有权限记录」的历史数据
     *
     * @param bizType 数据类型
     * @param bizId   数据编号
     * @return 是否存在客户团队
     */
    private boolean hasParentCustomerTeam(Integer bizType, Long bizId) {
        Long customerId = getCustomerId(bizType, bizId);
        if (customerId == null) {
            return false;
        }
        // 客户有负责人,说明不是公海
        CrmCustomerDO customer = customerMapper.selectById(customerId);
        if (customer == null) {
            return false;
        }
        if (customer.getOwnerUserId() != null) {
            return true;
        }
        // 客户没有负责人时,检查是否有团队成员
        return CollUtil.isNotEmpty(crmPermissionService.getPermissionListByBiz(
                CrmBizTypeEnum.CRM_CUSTOMER.getType(), Collections.singleton(customerId)));
    }
    /**