✨ 揭秘 Spring Boot 代码中的“身份转换”:getVipIdByStock 深度解析 ✨

✨ 揭秘 Spring Boot 代码中的“身份转换”:getVipIdByStock 深度解析 ✨

大家好!👋 在日常的 Spring Boot 项目开发中,我们经常会遇到一些看似简单却蕴含深意的代码行。今天,我们就来深入探讨这样一行代码:

adminId = adminCommonService.getVipIdByStock(adminId);

这行代码通常出现在 Controller 层处理请求的方法中,尤其是在涉及多用户、多角色、分层权限管理的系统中。它到底做了什么?为什么需要它?不加行不行?🤔 带着这些疑问,让我们一起层层剥茧,结合具体的代码实例,彻底搞懂它的来龙去脉!

❓ 问题背景:共享资源下的身份难题

想象一个场景:你正在开发一个库存管理系统。系统中有两种主要用户:

  1. 公司老板 (Company Super Admin): 拥有整个公司的库存数据,可能是系统服务的购买者 (VIP)。
  2. 仓库管理员 (Store Manager): 由老板指派,负责具体管理库存,但他本身并不拥有这些库存数据的所有权。

当仓库管理员登录系统想要查看“我的库存”列表时,我们应该展示谁的库存呢?显然,应该展示他所属公司的库存,也就是老板账户下的库存。如果直接用仓库管理员的 ID 去查询,很可能查不到数据,或者逻辑上就是错误的。

这时,adminId = adminCommonService.getVipIdByStock(adminId); 这行代码就应运而生了,它的核心目的就是解决在处理共享资源(如库存)时,如何将操作者的身份 标准化 为资源所有者或权限主体的身份。

📊 核心功能概览

功能项描述涉及组件 (主要)输入输出
身份标准化检查当前用户角色,如果是特定操作者(如库存管理员),则查找并返回其上级(公司超管)ID;否则返回自身 ID。 🎯AdminCommonService, AdminService, RoleCacheService, AdminCacheService当前登录用户 adminId用于后续操作的 adminId
核心判断是否为 ROLE_STORE (库存管理人) 角色?AdminCommonService.hasRole, RoleCacheService用户 adminId布尔值 (True/False)
上级查找若是 ROLE_STORE,则递归向上查找 pid (父级ID),直到找到 ROLE_COMPANY_SUPER (公司超管) 的用户。 🧗‍♂️AdminService.findCompanySuperId, AdminCacheService用户 adminId公司超管 adminId
性能优化大量使用缓存 (@Cacheable) 提高用户、角色信息的查询效率。 🚀AdminCacheService, RoleCacheService查询参数缓存的数据或数据库数据
数据基础依赖数据库中的 admin (含 pid), role, admin_role_mapping 表结构。 💾AdminRepository, RoleRepository, AdminRoleMappingRepository-数据库记录

🗺️ 决策流程:Flowchart (Mermaid)

让我们用流程图来看看 getVipIdByStock 方法内部的决策逻辑:

辅助方法
是 (仓库管理员)
否 (非仓库管理员,如老板)
递归调用
hasRole(userId, roleName)
findCompanySuperId(userId)
findCompanySuperIdNext(user)
adminService.findCompanySuperId(userId)
roleCacheService.findRolesByAdminId(userId)
adminCacheService.findById(userId)
getVipIdByStock(用户ID nowId)
检查用户 nowId 是否拥有 ROLE_STORE 角色?
调用 findCompanySuperId(nowId)
递归查找公司超管ID (通过 pid 向上)
返回 公司超管ID
返回 用户自身ID (nowId)

流程解读:

  1. 输入当前用户 ID (nowId)。
  2. 检查该用户是否具有 ROLE_STORE(库存管理人)角色。
  3. 如果是,则调用 findCompanySuperId 方法,该方法会递归地通过用户的 pid(父级 ID)向上查找,直到找到拥有 ROLE_COMPANY_SUPER(公司超管)角色的用户,并返回其 ID。
  4. 如果不是,则直接返回用户自身的 ID (nowId)。

⏱️ 时序交互:Sequence Diagram (Mermaid)

当一个 仓库管理员 调用 list 方法查看库存列表时,系统内部的交互过程大致如下:

客户端 Controller CommonSvc AdminSvc RoleCache AdminCache RoleRepo AdminRepo Controller CommonSvc RoleCache RoleRepo AdminSvc AdminCache AdminRepo StockInventoryApiService GET /admin/list (携带 adminId = 仓库管理员ID) list(..., adminId) getVipIdByStock(仓库管理员ID) hasRole(仓库管理员ID, "ROLE_STORE") findRolesByAdminId(仓库管理员ID) 返回角色列表 (含 ROLE_STORE) 返回 true findCompanySuperId(仓库管理员ID) findById(仓库管理员ID) findById(仓库管理员ID) 返回 仓库管理员Admin对象 (含 pid = 上级ID) 返回 仓库管理员Admin对象 findRolesByAdminId(仓库管理员ID) 返回角色列表 (不含 ROLE_COMPANY_SUPER) findCompanySuperIdNext(仓库管理员对象) findById(上级ID) findById(上级ID) 返回 上级Admin对象 (假设是老板, 含 pid=null) 返回 上级Admin对象 findRolesByAdminId(老板ID) findRolesByAdminId(老板ID) 返回角色列表 (含 ROLE_COMPANY_SUPER) 返回角色列表 (含 ROLE_COMPANY_SUPER) 返回 老板ID (公司超管ID) 返回 老板ID adminId = 老板ID findPage(老板ID, pageInfo) 返回库存分页数据 返回 BaseResult.success(库存数据) 客户端 Controller CommonSvc AdminSvc RoleCache AdminCache RoleRepo AdminRepo Controller CommonSvc RoleCache RoleRepo AdminSvc AdminCache AdminRepo StockInventoryApiService

时序图解读:

这个图清晰地展示了从 Controller 接收请求开始,如何调用 AdminCommonService,再通过 AdminService 进行递归查找(涉及到用户缓存、角色缓存以及最终的数据库查询),最终获得公司超管 ID 并用其进行后续数据查询的完整过程。

🔬 代码层层解析:从 Controller 到 Repository

让我们深入代码,看看这个机制是如何在不同层级实现的:

1. Controller 层 (StockInventoryController)

@GetMapping("admin/list")
@ApiOperation("我的库存分页列表")
public BaseResult list(PageWithSearch basePage, @ApiIgnore @SessionAttribute(Constants.ADMIN_ID) Integer adminId) {
    checkParam(basePage.getField(), basePage.getValue(), Arrays.asList(searchParamValidArray));
    // ===> 就是这里!在调用核心业务逻辑前进行身份标准化 <===
    adminId = adminCommonService.getVipIdByStock(adminId);
    return BaseResult.success(stockInventoryApiService.findPage(adminId, basePage));
}

在 Controller 的 list 方法中,获取到 Session 中的 adminId 后,立即调用 adminCommonService.getVipIdByStock 进行处理,然后再将可能被转换后adminId 传递给 stockInventoryApiService 进行实际的数据查询。

2. Service 层 (AdminCommonService)

@Service
public class AdminCommonService {
    // ... 注入 AdminService, AdminCacheService, RoleCacheService ...

    /**
     * 获取商品管理人的上级vip id
     * 当操作者为库存管理人时获取上级的vip id
     * 否则返回自身
     */
    public Integer getVipIdByStock(Integer nowId) {
        // 核心判断:是否为库存管理人?
        return hasRole(nowId, Admin.ROLE_STORE) 
               // 是:查找公司超管 ID
               ? findCompanySuperId(nowId) 
               // 否:返回自身 ID
               : nowId;
    }

    // 判断用户是否有指定角色 (利用 RoleCacheService)
    public boolean hasRole(Integer adminId, String role) {
        List<String> roleStrList = roleCacheService.findRolesByAdminId(adminId).stream().map(Role::getName).collect(Collectors.toList());
        return roleStrList.contains(role);
    }

    // 查询公司超管 ID (委托给 AdminService)
    public Integer findCompanySuperId(Integer adminId) {
        return adminService.findCompanySuperId(adminId);
    }
    // ... 其他辅助方法 ...
}

AdminCommonService 负责编排逻辑:调用 hasRole 判断角色,如果需要,再调用 findCompanySuperId 获取上级 ID。

3. Service 层 (AdminService) - 递归查找核心

@Service
public class AdminService {
    // ... 注入 AdminRepository, AdminCacheService, RoleCacheService ...

    // 入口方法:根据 ID 查找超管
    public Integer findCompanySuperId(Integer adminId) {
        // 使用缓存获取 Admin 对象
        Admin admin = adminCacheService.findById(adminId).orElseThrow(() -> new RuntimeException("未查到管理员信息"));
        return findCompanySuperId(admin); // 调用重载方法
    }

    // 重载方法:处理 Admin 对象
    public Integer findCompanySuperId(Admin admin) {
        // 获取当前用户的角色
        List<Role> roles = roleCacheService.findRolesByAdminId(admin.getId());
        for (Role role : roles) {
            // 如果当前用户就是公司超管,直接返回其 ID
            if (Admin.ROLE_COMPANY_SUPER.equals(role.getName())) {
                return admin.getId();
            }
        }
        // 如果不是超管,调用递归方法查找上级
        return findCompanySuperIdNext(admin);
    }

    // 递归查找方法
    private Integer findCompanySuperIdNext(Admin admin) {
        // 获取父级 ID (pid)
        Integer parentId = admin.getPid();
        if (parentId == null) { // 如果没有父级了还没找到,可能数据有问题或已到顶层非超管
            throw new RuntimeException("未找到公司超级管理员"); // 或者根据业务返回特定值
        }
        // 获取父级 Admin 对象 (使用缓存)
        Admin parentAdmin = adminCacheService.findById(parentId).orElseThrow(() -> new RuntimeException("未找到上级用户信息"));
        // 获取父级的角色
        List<Role> parentRoles = roleCacheService.findRolesByAdminId(parentAdmin.getId());
        for (Role role : parentRoles) {
            // 如果父级是公司超管,返回父级 ID
            if (Admin.ROLE_COMPANY_SUPER.equals(role.getName())) {
                return parentAdmin.getId();
            }
        }
        // 如果父级还不是超管,继续向上递归查找
        return findCompanySuperIdNext(parentAdmin);
    }
    // ... 其他方法 ...
}

AdminService 是查找公司超管的核心。它首先检查当前用户是否为超管,如果不是,则通过 admin.getPid() 获取父级 ID,然后递归调用自身,直到找到具有 ROLE_COMPANY_SUPER 角色的用户为止。

4. Caching 层 (AdminCacheService, RoleCacheService)

// AdminCacheService
@Service
public class AdminCacheService {
    @Autowired private AdminRepository adminRepository;

    @Cacheable(value = "findAdminById", key = "#id") // 对 findById 结果进行缓存
    public Optional<Admin> findById(Integer id) {
        return adminRepository.findById(id);
    }
    // ... 其他缓存相关方法 (save, delete 时需要 @CacheEvict 清除缓存) ...
}

// RoleCacheService
@Service
public class RoleCacheService {
    @Autowired private RoleRepository roleRepository;
    // ... 其他注入 ...

    @Cacheable(value = "findRoleByAdminId", key = "#adminId") // 对用户角色列表进行缓存
    public List<Role> findRolesByAdminId(Integer adminId) {
        return roleRepository.findRolesByAdminId(adminId);
    }
    // ... 其他缓存相关方法 ...
}

缓存层通过 Spring Cache 的注解 (@Cacheable, @CacheEvict) 极大地提高了性能。频繁调用的 findByIdfindRolesByAdminId 的结果会被缓存起来,避免了每次都查询数据库。

5. Repository 层 (AdminRepository, RoleRepository)

// AdminRepository
public interface AdminRepository extends JpaRepository<Admin, Integer> {
    // JpaRepository 自动提供了 findById 方法
    // 实体类 Admin 中应该包含 pid 字段
}

// RoleRepository
public interface RoleRepository extends JpaRepository<Role, Integer> {
    @Query(value = "select r.* from role r join admin_role_mapping arm on r.id = arm.role_id and arm.admin_id = ?1", nativeQuery = true)
    List<Role> findRolesByAdminId(Integer adminId); // 查询用户角色
}

// AdminRoleMappingRepository (虽然没直接用在查找逻辑,但支撑了 admin_role_mapping 表)
public interface AdminRoleMappingRepository extends JpaRepository<AdminRoleMapping, Integer> {
    // ...
}

Repository 层定义了与数据库交互的接口。AdminRepository 提供了基础的 findById (继承自 JpaRepository) 来获取 Admin 对象(包含 pid),RoleRepository 则通过原生 SQL 查询关联 admin_role_mapping 表来获取用户的角色列表。

🤔 为什么这行代码如此重要?(The “Aha!” Moment)💡

现在,我们回到最初的问题:为什么要有 adminId = adminCommonService.getVipIdByStock(adminId); 这行代码?

  1. 确保数据归属正确性: 库存数据是公司的,不是仓库管理员个人的。通过将 ID 转换为公司超管 ID,保证了后续查询 (findPage) 是基于正确的数据主体进行的。
  2. 统一权限和订阅校验: 系统的某些功能(比如增删改库存、查看某些高级报表)可能与公司超管账户的订阅状态 (VIP) 或权限绑定。使用转换后的 ID 可以确保后续的权限检查(如代码中其他地方可能存在的 subscribeService.isVIP(adminId))是基于公司超管账户进行的。
  3. 简化下游服务逻辑: StockInventoryApiService 等下游服务不需要再关心传入的 adminId 到底是哪个角色的 ID,它们只需要假设这个 ID 就是拥有数据和权限的那个 ID,从而简化了它们的内部逻辑。Controller 层承担了身份标准化的职责。
  4. 支持组织架构和代理操作: 这种机制天然地支持了“员工代理老板操作”的业务场景,使得系统的组织架构设计更加灵活。

🎯 一言以蔽之:这行代码扮演了一个“守门员”和“翻译官”的角色,确保了在处理公司级资源时,系统始终使用代表公司的“法人” ID,屏蔽了底层员工身份的差异,保证了业务逻辑的一致性和正确性。

✅ 总结

通过对 StockInventoryController, AdminCommonService, AdminService, *CacheService, *Repository 等多个层级代码的深入分析,我们彻底理解了 adminId = adminCommonService.getVipIdByStock(adminId); 这行代码的精妙之处。它不仅仅是一次简单的赋值,而是:

  • 一个基于 角色 的条件判断。
  • 一个利用 pid 实现的 递归向上查找 机制。
  • 一个结合了 缓存 优化的实践。
  • 一个保证 数据归属权限统一 的关键设计。

希望这篇博客能帮助你理解这类代码设计的意图和实现方式。下次在你的项目中遇到类似场景时,就能更加得心应手了!💪


🧠 思维导图 (Markdown)

在这里插入图片描述


希望这篇博客对你有帮助!😊

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值