3、RBAC1 权限系统详细设计与实现指南(含审计功能)

【投稿赢 iPhone 17】「我的第一个开源项目」故事征集:用代码换C位出道! 10w+人浏览 1.6k人参与

目录

  1. RBAC1 模型核心概念
  2. 数据库表结构设计(含审计)
  3. 工作流程详解
  4. Java 实现示例(含详细中文注释)
  5. API 使用示例
  6. 审计功能实现
  7. 测试用例验证

一、RBAC1 模型核心概念

1.1 什么是 RBAC1?

RBAC1(Role-Based Access Control Level 1)在 RBAC0 的基础上增加了角色继承特性,允许角色之间形成层次结构:

  • 子角色自动继承父角色的所有权限
  • 支持多级继承(树形或森林结构)
  • 解决了权限重复分配的问题

1.2 核心增强点

特性RBAC0RBAC1
角色关系平面结构层次结构
权限继承子角色继承父角色权限
管理复杂度高(重复分配)低(一次定义,多处继承)

1.3 形象类比

公司组织架构升级版

  • 员工角色:拥有基础权限(查看工资单、提交请假申请)
  • 经理角色:继承员工所有权限 + 额外权限(审批请假、查看团队工资)
  • 总监角色:继承经理所有权限 + 额外权限(团队管理、预算审批)

当给经理分配权限时,只需分配"审批请假"权限,自动获得员工的所有基础权限

1.4 继承规则

  1. 传递性:如果 A 继承 B,B 继承 C,则 A 继承 C
  2. 累积性:子角色拥有自身权限 + 所有祖先角色权限
  3. 无环性:不能形成循环继承(A→B→A)

二、数据库表结构设计(含审计)

2.1 表结构总览

表名说明关系
sys_user用户表-
sys_role角色表-
sys_role_hierarchy角色继承关系表父角色 ↔ 子角色
sys_permission权限表-
sys_user_role用户-角色关联表用户 ↔ 角色
sys_role_permission角色-权限关联表角色 ↔ 权限
sys_audit_log审计日志表记录所有权限变更

2.2 新增/修改的表结构

2.2.1 角色表 (sys_role) - 新增字段
CREATE TABLE `sys_role` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `role_code` VARCHAR(50) NOT NULL COMMENT '角色编码(唯一,用于程序识别)',
  `role_name` VARCHAR(50) NOT NULL COMMENT '角色名称(用于显示)',
  `description` VARCHAR(200) DEFAULT NULL COMMENT '角色描述',
  `status` TINYINT DEFAULT 1 COMMENT '状态:1-正常,0-禁用',
  `level` INT DEFAULT 0 COMMENT '角色层级(0-根角色,1-一级子角色...)',
  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_role_code` (`role_code`),
  KEY `idx_level` (`level`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表(支持继承)';
2.2.2 角色继承关系表 (sys_role_hierarchy) - 新增
CREATE TABLE `sys_role_hierarchy` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '关系ID',
  `parent_role_id` BIGINT NOT NULL COMMENT '父角色ID',
  `child_role_id` BIGINT NOT NULL COMMENT '子角色ID',
  `hierarchy_level` INT NOT NULL COMMENT '继承层级(1-直接子角色,2-孙子角色...)',
  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_parent_child` (`parent_role_id`, `child_role_id`),
  KEY `idx_parent_role_id` (`parent_role_id`),
  KEY `idx_child_role_id` (`child_role_id`),
  -- 防止循环继承的约束(应用层实现)
  CONSTRAINT `chk_no_self_reference` CHECK (`parent_role_id` != `child_role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色继承关系表';
2.2.3 审计日志表 (sys_audit_log) - 新增
CREATE TABLE `sys_audit_log` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '审计日志ID',
  `operation_type` VARCHAR(20) NOT NULL COMMENT '操作类型:CREATE/UPDATE/DELETE/ASSIGN/REVOKE',
  `target_type` VARCHAR(20) NOT NULL COMMENT '目标类型:USER/ROLE/PERMISSION/HIERARCHY',
  `target_id` BIGINT NOT NULL COMMENT '目标ID',
  `operator_id` BIGINT NOT NULL COMMENT '操作人ID',
  `operator_name` VARCHAR(50) NOT NULL COMMENT '操作人姓名',
  `operation_details` JSON DEFAULT NULL COMMENT '操作详情(JSON格式)',
  `ip_address` VARCHAR(45) DEFAULT NULL COMMENT '操作IP地址',
  `user_agent` VARCHAR(500) DEFAULT NULL COMMENT '用户代理',
  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
  PRIMARY KEY (`id`),
  KEY `idx_operator_id` (`operator_id`),
  KEY `idx_target_type_id` (`target_type`, `target_id`),
  KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限系统审计日志表';

2.3 示例数据

角色数据
-- 基础角色
INSERT INTO sys_role (role_code, role_name, description, level) 
VALUES 
('EMPLOYEE', '普通员工', '基础员工角色', 0),
('MANAGER', '部门经理', '部门管理角色', 1),
('DIRECTOR', '部门总监', '部门总监角色', 2);

-- 角色继承关系
-- 经理继承员工权限
INSERT INTO sys_role_hierarchy (parent_role_id, child_role_id, hierarchy_level) 
VALUES (1, 2, 1);

-- 总监继承经理权限(间接继承员工权限)
INSERT INTO sys_role_hierarchy (parent_role_id, child_role_id, hierarchy_level) 
VALUES (2, 3, 1);
权限数据
-- 员工权限
INSERT INTO sys_permission (permission_code, permission_name, resource_type, resource_path) 
VALUES 
('SALARY_VIEW', '查看工资', 'API', '/api/salary/view'),
('LEAVE_APPLY', '申请请假', 'API', '/api/leave/apply');

-- 经理额外权限
INSERT INTO sys_permission (permission_code, permission_name, resource_type, resource_path) 
VALUES 
('LEAVE_APPROVE', '审批请假', 'API', '/api/leave/approve'),
('TEAM_SALARY_VIEW', '查看团队工资', 'API', '/api/salary/team');

-- 总监额外权限
INSERT INTO sys_permission (permission_code, permission_name, resource_type, resource_path) 
VALUES 
('TEAM_MANAGE', '团队管理', 'API', '/api/team/manage'),
('BUDGET_APPROVE', '预算审批', 'API', '/api/budget/approve');
角色-权限关联
-- 员工角色拥有基础权限
INSERT INTO sys_role_permission (role_id, permission_id) 
VALUES (1, 1), (1, 2);

-- 经理角色拥有额外权限(自动继承员工权限)
INSERT INTO sys_role_permission (role_id, permission_id) 
VALUES (2, 3), (2, 4);

-- 总监角色拥有额外权限(自动继承经理和员工权限)
INSERT INTO sys_role_permission (role_id, permission_id) 
VALUES (3, 5), (3, 6);

三、RBAC1 工作流程详解

3.1 角色继承构建流程

管理员系统数据库创建父角色(员工)插入sys_role表创建子角色(经理)插入sys_role表设置继承关系(经理继承员工)插入sys_role_hierarchy表记录审计日志为父角色分配权限插入sys_role_permission表记录审计日志管理员系统数据库

3.2 权限验证流程(关键增强)

用户API接口权限服务数据库请求 /api/leave/approve检查用户是否有 LEAVE_APPROVE 权限查询用户直接关联的角色返回直接角色列表查询角色的所有祖先角色(递归查询)返回完整角色树查询所有角色(直接+继承)的权限返回完整权限列表返回权限检查结果返回审批结果返回403错误alt[有权限][无权限]用户API接口权限服务数据库

3.3 核心验证逻辑(与RBAC0的区别)

  1. 获取当前用户直接关联的角色
  2. 递归查询这些角色的所有祖先角色
  3. 合并直接角色 + 所有祖先角色
  4. 查询所有角色关联的权限
  5. 检查目标权限是否在权限列表中

3.4 继承关系查询策略

  • 方案1:递归查询(简单但性能较差)
  • 方案2:路径枚举(存储完整路径,查询快)
  • 方案3:闭包表(预计算所有继承关系,推荐)

本方案采用闭包表策略

  • sys_role_hierarchy 表中预存储所有直接和间接继承关系
  • 通过 hierarchy_level 字段区分直接继承(1)和间接继承(>1)

四、Java 实现示例(含详细中文注释)

4.1 新增实体类

角色继承关系实体
/**
 * 角色继承关系实体类
 * 对应数据库表:sys_role_hierarchy
 * 用于表示角色之间的继承关系
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RoleHierarchy {
    /**
     * 关系ID,主键
     */
    private Long id;
    
    /**
     * 父角色ID(被继承的角色)
     * 例如:员工角色ID
     */
    private Long parentRoleId;
    
    /**
     * 子角色ID(继承的角色)
     * 例如:经理角色ID
     */
    private Long childRoleId;
    
    /**
     * 继承层级:
     * 1 - 直接子角色(经理直接继承员工)
     * 2 - 间接子角色(总监间接继承员工)
     * 数值越大,继承距离越远
     */
    private Integer hierarchyLevel;
    
    /**
     * 创建时间
     */
    private LocalDateTime createTime;
}
审计日志实体
/**
 * 审计日志实体类
 * 对应数据库表:sys_audit_log
 * 记录权限系统的所有关键操作
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AuditLog {
    /**
     * 审计日志ID,主键
     */
    private Long id;
    
    /**
     * 操作类型:
     * CREATE - 创建
     * UPDATE - 更新  
     * DELETE - 删除
     * ASSIGN - 分配(如分配角色给用户)
     * REVOKE - 撤销(如撤销角色)
     */
    private String operationType;
    
    /**
     * 目标类型:
     * USER - 用户
     * ROLE - 角色
     * PERMISSION - 权限
     * HIERARCHY - 继承关系
     */
    private String targetType;
    
    /**
     * 目标ID(被操作对象的ID)
     */
    private Long targetId;
    
    /**
     * 操作人ID
     */
    private Long operatorId;
    
    /**
     * 操作人姓名(冗余存储,便于审计查询)
     */
    private String operatorName;
    
    /**
     * 操作详情(JSON格式)
     * 包含操作前后的数据对比等详细信息
     */
    private String operationDetails;
    
    /**
     * 操作IP地址
     */
    private String ipAddress;
    
    /**
     * 用户代理(浏览器信息)
     */
    private String userAgent;
    
    /**
     * 操作时间
     */
    private LocalDateTime createTime;
}

4.2 增强的Mapper接口

角色继承关系Mapper
/**
 * 角色继承关系Mapper
 * 负责角色继承关系的数据库操作
 */
@Mapper
public interface RoleHierarchyMapper {
    
    /**
     * 插入角色继承关系
     * @param hierarchy 角色继承关系对象
     * @return 影响行数
     */
    int insert(RoleHierarchy hierarchy);
    
    /**
     * 根据子角色ID查询所有祖先角色ID(包括间接祖先)
     * 这是RBAC1的核心查询方法
     * 
     * 例如:查询总监的所有祖先角色,返回[经理, 员工]
     * 
     * @param childRoleId 子角色ID
     * @return 祖先角色ID列表
     */
    List<Long> selectAllAncestorRoleIdsByChildId(Long childRoleId);
    
    /**
     * 根据父角色ID查询所有后代角色ID
     * 用于级联操作(如删除角色时检查是否有子角色)
     * 
     * @param parentRoleId 父角色ID
     * @return 后代角色ID列表
     */
    List<Long> selectAllDescendantRoleIdsByParentId(Long parentRoleId);
    
    /**
     * 检查是否存在循环继承
     * 在添加继承关系前调用,防止A→B→A的情况
     * 
     * @param parentRoleId 潜在父角色ID
     * @param childRoleId 潜在子角色ID
     * @return true-存在循环,false-无循环
     */
    boolean existsCircularReference(@Param("parentRoleId") Long parentRoleId, 
                                   @Param("childRoleId") Long childRoleId);
    
    /**
     * 删除角色的所有继承关系(级联删除)
     * @param roleId 角色ID
     * @return 影响行数
     */
    int deleteByRoleId(Long roleId);
}
审计日志Mapper
/**
 * 审计日志Mapper
 * 负责审计日志的数据库操作
 */
@Mapper
public interface AuditLogMapper {
    
    /**
     * 插入审计日志
     * @param auditLog 审计日志对象
     * @return 影响行数
     */
    int insert(AuditLog auditLog);
    
    /**
     * 根据条件查询审计日志(分页)
     * @param query 审计日志查询条件
     * @return 审计日志列表
     */
    List<AuditLog> selectByCondition(AuditLogQuery query);
    
    /**
     * 根据目标类型和ID查询相关审计日志
     * 用于查看某个对象的操作历史
     * 
     * @param targetType 目标类型
     * @param targetId 目标ID
     * @return 审计日志列表
     */
    List<AuditLog> selectByTarget(String targetType, Long targetId);
}

4.3 RBAC1权限服务核心实现

/**
 * RBAC1权限服务实现类(继承RBAC0,增强角色继承功能)
 * 负责权限的核心业务逻辑处理,支持角色继承
 */
@Service
@Slf4j
public class Rbac1PermissionService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Autowired
    private RoleMapper roleMapper;
    
    @Autowired
    private RoleHierarchyMapper roleHierarchyMapper;
    
    @Autowired
    private PermissionMapper permissionMapper;
    
    @Autowired
    private AuditLogService auditLogService;
    
    /**
     * 检查用户是否拥有指定权限(支持角色继承)
     * RBAC1的核心验证方法,相比RBAC0增加了继承关系处理
     * 
     * 验证流程:
     * 1. 根据用户ID获取用户直接关联的角色ID列表
     * 2. 对每个直接角色,查询其所有祖先角色ID
     * 3. 合并直接角色ID + 所有祖先角色ID,去重
     * 4. 查询所有角色(直接+继承)关联的权限ID列表
     * 5. 检查目标权限编码是否在权限列表中
     * 
     * @param userId 用户ID
     * @param permissionCode 目标权限编码(如:LEAVE_APPROVE)
     * @return true-拥有权限,false-无权限
     */
    public boolean hasPermission(Long userId, String permissionCode) {
        log.info("开始检查用户权限(RBAC1): userId={}, permissionCode={}", userId, permissionCode);
        
        try {
            // 步骤1: 获取用户直接关联的角色ID列表
            List<Long> directRoleIds = userMapper.selectRoleIdsByUserId(userId);
            log.debug("用户直接关联的角色ID列表: {}", directRoleIds);
            
            // 如果用户没有任何直接角色,直接返回无权限
            if (CollectionUtils.isEmpty(directRoleIds)) {
                log.warn("用户没有分配任何直接角色,userId={}", userId);
                return false;
            }
            
            // 步骤2: 获取所有祖先角色ID(包括间接祖先)
            Set<Long> allRoleIds = new HashSet<>(directRoleIds); // 包含直接角色
            for (Long directRoleId : directRoleIds) {
                List<Long> ancestorRoleIds = roleHierarchyMapper.selectAllAncestorRoleIdsByChildId(directRoleId);
                allRoleIds.addAll(ancestorRoleIds);
                log.debug("角色 {} 的祖先角色ID列表: {}", directRoleId, ancestorRoleIds);
            }
            log.debug("用户拥有的完整角色ID集合(直接+继承): {}", allRoleIds);
            
            // 步骤3: 获取所有角色关联的权限ID列表
            List<Long> permissionIds = roleMapper.selectPermissionIdsByRoleIds(new ArrayList<>(allRoleIds));
            log.debug("角色关联的权限ID列表: {}", permissionIds);
            
            // 如果没有任何权限,直接返回无权限
            if (CollectionUtils.isEmpty(permissionIds)) {
                log.warn("角色没有分配任何权限,roleIds={}", allRoleIds);
                return false;
            }
            
            // 步骤4: 获取权限编码列表并检查
            List<Permission> permissions = permissionMapper.selectByIds(permissionIds);
            Set<String> permissionCodes = permissions.stream()
                    .map(Permission::getPermissionCode)
                    .collect(Collectors.toSet());
            log.debug("用户拥有的权限编码列表: {}", permissionCodes);
            
            boolean hasPermission = permissionCodes.contains(permissionCode);
            log.info("RBAC1权限检查结果: userId={}, permissionCode={}, result={}", 
                    userId, permissionCode, hasPermission);
            
            return hasPermission;
            
        } catch (Exception e) {
            log.error("RBAC1权限检查发生异常", e);
            return false;
        }
    }
    
    /**
     * 获取用户拥有的所有权限编码列表(支持角色继承)
     * 
     * @param userId 用户ID
     * @return 权限编码集合
     */
    public Set<String> getUserPermissionCodes(Long userId) {
        log.info("获取用户权限列表(RBAC1): userId={}", userId);
        
        try {
            // 获取直接角色ID
            List<Long> directRoleIds = userMapper.selectRoleIdsByUserId(userId);
            if (CollectionUtils.isEmpty(directRoleIds)) {
                return Collections.emptySet();
            }
            
            // 获取所有角色ID(直接+祖先)
            Set<Long> allRoleIds = new HashSet<>(directRoleIds);
            for (Long directRoleId : directRoleIds) {
                List<Long> ancestorRoleIds = roleHierarchyMapper.selectAllAncestorRoleIdsByChildId(directRoleId);
                allRoleIds.addAll(ancestorRoleIds);
            }
            
            // 获取权限
            List<Long> permissionIds = roleMapper.selectPermissionIdsByRoleIds(new ArrayList<>(allRoleIds));
            if (CollectionUtils.isEmpty(permissionIds)) {
                return Collections.emptySet();
            }
            
            List<Permission> permissions = permissionMapper.selectByIds(permissionIds);
            return permissions.stream()
                    .map(Permission::getPermissionCode)
                    .collect(Collectors.toSet());
                    
        } catch (Exception e) {
            log.error("获取用户权限列表发生异常", e);
            return Collections.emptySet();
        }
    }
    
    /**
     * 设置角色继承关系
     * 建立父角色和子角色之间的继承关系
     * 
     * @param parentRoleId 父角色ID
     * @param childRoleId 子角色ID
     * @param operatorId 操作人ID
     * @param request 审计信息
     * @throws BusinessException 业务异常
     */
    @Transactional(rollbackFor = Exception.class)
    public void setRoleInheritance(Long parentRoleId, Long childRoleId, 
                                 Long operatorId, AuditRequest request) {
        log.info("设置角色继承关系: parentRoleId={}, childRoleId={}, operatorId={}", 
                parentRoleId, childRoleId, operatorId);
        
        // 1. 参数校验
        if (parentRoleId.equals(childRoleId)) {
            throw new BusinessException("不能设置角色自继承");
        }
        
        // 2. 检查循环继承
        if (roleHierarchyMapper.existsCircularReference(parentRoleId, childRoleId)) {
            throw new BusinessException("存在循环继承风险,无法设置继承关系");
        }
        
        // 3. 检查是否已存在继承关系
        RoleHierarchy existing = roleHierarchyMapper.selectByParentAndChild(parentRoleId, childRoleId);
        if (existing != null) {
            throw new BusinessException("继承关系已存在");
        }
        
        // 4. 创建继承关系
        RoleHierarchy hierarchy = RoleHierarchy.builder()
                .parentRoleId(parentRoleId)
                .childRoleId(childRoleId)
                .hierarchyLevel(1) // 直接继承
                .build();
        roleHierarchyMapper.insert(hierarchy);
        
        // 5. 递归更新间接继承关系(闭包表维护)
        updateIndirectInheritance(parentRoleId, childRoleId);
        
        // 6. 记录审计日志
        auditLogService.logRoleInheritance(
            operatorId, 
            parentRoleId, 
            childRoleId, 
            request.getIpAddress(), 
            request.getUserAgent()
        );
        
        log.info("角色继承关系设置成功");
    }
    
    /**
     * 递归更新间接继承关系
     * 当添加新的直接继承关系时,需要更新所有相关的间接继承关系
     * 
     * 例如:添加 A→B,而 B→C 已存在,则需要添加 A→C(间接)
     * 
     * @param parentRoleId 新的父角色ID
     * @param childRoleId 新的子角色ID
     */
    private void updateIndirectInheritance(Long parentRoleId, Long childRoleId) {
        // 1. 获取子角色的所有后代角色(B的所有后代:C, D...)
        List<Long> descendants = roleHierarchyMapper.selectAllDescendantRoleIdsByParentId(childRoleId);
        
        // 2. 为每个后代角色添加间接继承关系(A→C, A→D...)
        for (Long descendantId : descendants) {
            // 计算继承层级:A→B(1) + B→C(1) = A→C(2)
            Integer level = getInheritanceLevel(parentRoleId, descendantId);
            if (level != null) {
                RoleHierarchy indirect = RoleHierarchy.builder()
                        .parentRoleId(parentRoleId)
                        .childRoleId(descendantId)
                        .hierarchyLevel(level)
                        .build();
                // 忽略重复插入异常
                try {
                    roleHierarchyMapper.insert(indirect);
                } catch (Exception e) {
                    log.debug("间接继承关系已存在,跳过: {}→{}", parentRoleId, descendantId);
                }
            }
        }
        
        // 3. 获取父角色的所有祖先角色(A的所有祖先:X, Y...)
        List<Long> ancestors = roleHierarchyMapper.selectAllAncestorRoleIdsByChildId(parentRoleId);
        
        // 4. 为每个祖先角色添加到子角色及其后代的间接继承关系
        for (Long ancestorId : ancestors) {
            // 祖先→子角色
            Integer level1 = getInheritanceLevel(ancestorId, childRoleId);
            if (level1 != null) {
                RoleHierarchy indirect1 = RoleHierarchy.builder()
                        .parentRoleId(ancestorId)
                        .childRoleId(childRoleId)
                        .hierarchyLevel(level1)
                        .build();
                try {
                    roleHierarchyMapper.insert(indirect1);
                } catch (Exception e) {
                    log.debug("间接继承关系已存在,跳过: {}→{}", ancestorId, childRoleId);
                }
            }
            
            // 祖先→后代角色
            for (Long descendantId : descendants) {
                Integer level2 = getInheritanceLevel(ancestorId, descendantId);
                if (level2 != null) {
                    RoleHierarchy indirect2 = RoleHierarchy.builder()
                            .parentRoleId(ancestorId)
                            .childRoleId(descendantId)
                            .hierarchyLevel(level2)
                            .build();
                    try {
                        roleHierarchyMapper.insert(indirect2);
                    } catch (Exception e) {
                        log.debug("间接继承关系已存在,跳过: {}→{}", ancestorId, descendantId);
                    }
                }
            }
        }
    }
    
    /**
     * 计算两个角色之间的继承层级
     * 通过现有继承关系计算最短路径
     */
    private Integer getInheritanceLevel(Long ancestorId, Long descendantId) {
        // 简化实现:通过BFS计算最短路径
        // 实际项目中可以优化为存储路径或使用图算法
        Queue<RolePath> queue = new LinkedList<>();
        Set<Long> visited = new HashSet<>();
        
        queue.offer(new RolePath(descendantId, 0));
        visited.add(descendantId);
        
        while (!queue.isEmpty()) {
            RolePath current = queue.poll();
            if (current.roleId.equals(ancestorId)) {
                return current.level;
            }
            
            // 获取当前角色的直接父角色
            List<Long> directParents = roleHierarchyMapper.selectDirectParentRoleIds(current.roleId);
            for (Long parentId : directParents) {
                if (!visited.contains(parentId)) {
                    visited.add(parentId);
                    queue.offer(new RolePath(parentId, current.level + 1));
                }
            }
        }
        
        return null; // 无继承关系
    }
    
    /**
     * 内部类:用于BFS路径计算
     */
    @Data
    @AllArgsConstructor
    private static class RolePath {
        private Long roleId;
        private Integer level;
    }
}

4.4 审计服务实现

/**
 * 审计日志服务
 * 负责记录权限系统的所有关键操作
 */
@Service
@Slf4j
public class AuditLogService {
    
    @Autowired
    private AuditLogMapper auditLogMapper;
    
    @Autowired
    private UserMapper userMapper;
    
    /**
     * 记录角色继承关系设置的审计日志
     * 
     * @param operatorId 操作人ID
     * @param parentRoleId 父角色ID
     * @param childRoleId 子角色ID
     * @param ipAddress 操作IP
     * @param userAgent 用户代理
     */
    public void logRoleInheritance(Long operatorId, Long parentRoleId, 
                                 Long childRoleId, String ipAddress, String userAgent) {
        try {
            // 获取操作人信息
            User operator = userMapper.selectById(operatorId);
            String operatorName = operator != null ? operator.getRealName() : "未知用户";
            
            // 构建操作详情
            Map<String, Object> details = new HashMap<>();
            details.put("parentRoleId", parentRoleId);
            details.put("childRoleId", childRoleId);
            details.put("operation", "SET_INHERITANCE");
            
            // 创建审计日志
            AuditLog auditLog = AuditLog.builder()
                    .operationType("ASSIGN")
                    .targetType("HIERARCHY")
                    .targetId(parentRoleId) // 以父角色作为目标
                    .operatorId(operatorId)
                    .operatorName(operatorName)
                    .operationDetails(JsonUtils.toJson(details))
                    .ipAddress(ipAddress)
                    .userAgent(userAgent)
                    .build();
            
            auditLogMapper.insert(auditLog);
            log.info("审计日志记录成功: operator={}, operation=SET_INHERITANCE", operatorName);
            
        } catch (Exception e) {
            log.error("记录审计日志失败", e);
            // 审计日志失败不应影响主业务流程
        }
    }
    
    /**
     * 记录通用的权限操作审计日志
     */
    public void logPermissionOperation(String operationType, String targetType, 
                                     Long targetId, Long operatorId, 
                                     String ipAddress, String userAgent, 
                                     Object operationDetails) {
        try {
            User operator = userMapper.selectById(operatorId);
            String operatorName = operator != null ? operator.getRealName() : "未知用户";
            
            AuditLog auditLog = AuditLog.builder()
                    .operationType(operationType)
                    .targetType(targetType)
                    .targetId(targetId)
                    .operatorId(operatorId)
                    .operatorName(operatorName)
                    .operationDetails(operationDetails != null ? JsonUtils.toJson(operationDetails) : null)
                    .ipAddress(ipAddress)
                    .userAgent(userAgent)
                    .build();
            
            auditLogMapper.insert(auditLog);
            
        } catch (Exception e) {
            log.error("记录审计日志失败", e);
        }
    }
}

五、API 使用示例

5.1 角色继承管理API

/**
 * 角色管理Controller(支持继承)
 */
@RestController
@RequestMapping("/api/role")
public class RoleController {
    
    @Autowired
    private Rbac1PermissionService permissionService;
    
    /**
     * 设置角色继承关系
     * 建立父角色和子角色的继承关系
     * 
     * 请求示例:
     * POST /api/role/inheritance
     * {
     *   "parentRoleId": 1,
     *   "childRoleId": 2
     * }
     * 
     * 说明:设置角色2(经理)继承角色1(员工)的权限
     */
    @PostMapping("/inheritance")
    public Result<Void> setRoleInheritance(@RequestBody @Valid SetInheritanceRequest request,
                                         HttpServletRequest httpRequest) {
        // 获取当前操作人
        Long currentUserId = getCurrentUserId(httpRequest);
        
        // 构建审计信息
        AuditRequest auditRequest = AuditRequest.builder()
                .ipAddress(getClientIpAddress(httpRequest))
                .userAgent(httpRequest.getHeader("User-Agent"))
                .build();
        
        // 设置继承关系
        permissionService.setRoleInheritance(
            request.getParentRoleId(), 
            request.getChildRoleId(), 
            currentUserId, 
            auditRequest
        );
        
        return Result.success();
    }
    
    /**
     * 获取角色的完整权限树(用于展示)
     * 显示角色自身的权限 + 继承的权限
     */
    @GetMapping("/{roleId}/permission-tree")
    public Result<RolePermissionTree> getRolePermissionTree(@PathVariable Long roleId) {
        RolePermissionTree tree = buildPermissionTree(roleId);
        return Result.success(tree);
    }
    
    /**
     * 构建角色权限树
     * 包含直接权限和继承权限的详细信息
     */
    private RolePermissionTree buildPermissionTree(Long roleId) {
        // 获取角色基本信息
        Role role = roleMapper.selectById(roleId);
        
        // 获取直接权限
        List<Permission> directPermissions = getDirectPermissions(roleId);
        
        // 获取继承权限
        List<RoleInheritanceInfo> inheritanceChain = getInheritanceChain(roleId);
        
        return RolePermissionTree.builder()
                .role(role)
                .directPermissions(directPermissions)
                .inheritanceChain(inheritanceChain)
                .build();
    }
}

5.2 请求DTO定义

/**
 * 设置角色继承关系请求DTO
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SetInheritanceRequest {
    
    /**
     * 父角色ID(被继承的角色)
     * 例如:员工角色ID
     */
    @NotNull(message = "父角色ID不能为空")
    private Long parentRoleId;
    
    /**
     * 子角色ID(继承的角色)
     * 例如:经理角色ID
     */
    @NotNull(message = "子角色ID不能为空")
    private Long childRoleId;
}

/**
 * 审计请求信息
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AuditRequest {
    /**
     * 操作IP地址
     */
    private String ipAddress;
    
    /**
     * 用户代理信息
     */
    private String userAgent;
}

/**
 * 角色权限树响应DTO
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RolePermissionTree {
    /**
     * 当前角色信息
     */
    private Role role;
    
    /**
     * 直接权限列表(角色自身拥有的权限)
     */
    private List<Permission> directPermissions;
    
    /**
     * 继承链信息(从哪些角色继承了什么权限)
     */
    private List<RoleInheritanceInfo> inheritanceChain;
}

/**
 * 角色继承信息
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RoleInheritanceInfo {
    /**
     * 祖先角色信息
     */
    private Role ancestorRole;
    
    /**
     * 从该祖先角色继承的权限列表
     */
    private List<Permission> inheritedPermissions;
    
    /**
     * 继承层级(1-直接父角色,2-祖父角色...)
     */
    private Integer inheritanceLevel;
}

六、审计功能详细说明

6.1 审计覆盖范围

操作类型审计内容审计级别
角色管理创建/更新/删除角色
权限分配角色分配权限、用户分配角色
继承关系设置/删除角色继承最高
权限验证敏感权限访问(可选)

6.2 审计日志查询API

/**
 * 审计日志查询Controller
 */
@RestController
@RequestMapping("/api/audit")
public class AuditLogController {
    
    @Autowired
    private AuditLogService auditLogService;
    
    /**
     * 分页查询审计日志
     * 支持多种查询条件
     */
    @GetMapping("/logs")
    public Result<PageResult<AuditLog>> getAuditLogs(AuditLogQuery query) {
        PageResult<AuditLog> result = auditLogService.getAuditLogs(query);
        return Result.success(result);
    }
    
    /**
     * 查询特定对象的操作历史
     * 例如:查看某个角色的所有操作记录
     */
    @GetMapping("/target/{targetType}/{targetId}")
    public Result<List<AuditLog>> getTargetAuditLogs(@PathVariable String targetType,
                                                   @PathVariable Long targetId) {
        List<AuditLog> logs = auditLogService.getTargetAuditLogs(targetType, targetId);
        return Result.success(logs);
    }
}

6.3 审计日志示例

角色继承设置审计日志

{
  "id": 1001,
  "operationType": "ASSIGN",
  "targetType": "HIERARCHY",
  "targetId": 1,
  "operatorId": 100,
  "operatorName": "系统管理员",
  "operationDetails": {
    "parentRoleId": 1,
    "childRoleId": 2,
    "operation": "SET_INHERITANCE"
  },
  "ipAddress": "192.168.1.100",
  "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
  "createTime": "2024-01-15T10:30:00"
}

权限分配审计日志

{
  "id": 1002,
  "operationType": "ASSIGN",
  "targetType": "PERMISSION",
  "targetId": 5,
  "operatorId": 100,
  "operatorName": "系统管理员",
  "operationDetails": {
    "roleId": 2,
    "permissionId": 5,
    "permissionCode": "TEAM_MANAGE",
    "operation": "ASSIGN_PERMISSION"
  },
  "ipAddress": "192.168.1.100",
  "createTime": "2024-01-15T10:35:00"
}

七、测试用例验证

7.1 角色继承测试

/**
 * RBAC1权限服务测试类
 */
@SpringBootTest
class Rbac1PermissionServiceTest {
    
    @Autowired
    private Rbac1PermissionService permissionService;
    
    @Test
    void testHasPermission_WithRoleInheritance_ShouldIncludeInheritedPermissions() {
        // 准备数据:
        // - 用户ID=1 分配了经理角色(ID=2)
        // - 经理角色继承员工角色(ID=1)
        // - 员工角色拥有 SALARY_VIEW 权限
        // - 经理角色拥有 LEAVE_APPROVE 权限
        
        // 测试1:经理应该拥有员工的权限
        boolean canViewSalary = permissionService.hasPermission(1L, "SALARY_VIEW");
        assertTrue(canViewSalary, "经理应该继承员工的查看工资权限");
        
        // 测试2:经理应该拥有自己的权限
        boolean canApproveLeave = permissionService.hasPermission(1L, "LEAVE_APPROVE");
        assertTrue(canApproveLeave, "经理应该拥有审批请假权限");
        
        // 测试3:经理不应该拥有总监的权限
        boolean canManageTeam = permissionService.hasPermission(1L, "TEAM_MANAGE");
        assertFalse(canManageTeam, "经理不应该拥有团队管理权限");
    }
    
    @Test
    void testGetUserPermissionCodes_ShouldIncludeAllInheritedPermissions() {
        Set<String> permissions = permissionService.getUserPermissionCodes(1L);
        
        // 应该包含员工权限 + 经理权限
        assertTrue(permissions.contains("SALARY_VIEW"), "应该包含继承的员工权限");
        assertTrue(permissions.contains("LEAVE_APPLY"), "应该包含继承的员工权限");
        assertTrue(permissions.contains("LEAVE_APPROVE"), "应该包含经理自身权限");
        assertTrue(permissions.contains("TEAM_SALARY_VIEW"), "应该包含经理自身权限");
        
        // 不应该包含总监权限
        assertFalse(permissions.contains("TEAM_MANAGE"), "不应该包含总监权限");
        assertFalse(permissions.contains("BUDGET_APPROVE"), "不应该包含总监权限");
        
        assertEquals(4, permissions.size(), "应该有4个权限");
    }
    
    @Test
    void testSetRoleInheritance_WithCircularReference_ShouldThrowException() {
        // 准备数据:A→B, B→C 已存在
        
        // 尝试设置 C→A(形成循环)
        assertThrows(BusinessException.class, () -> {
            permissionService.setRoleInheritance(3L, 1L, 100L, createAuditRequest());
        });
    }
}

7.2 审计功能测试

@Test
void testAuditLog_WhenSetRoleInheritance_ShouldRecordAuditLog() {
    // 执行设置继承关系操作
    permissionService.setRoleInheritance(1L, 2L, 100L, createAuditRequest());
    
    // 查询审计日志
    List<AuditLog> logs = auditLogService.getTargetAuditLogs("HIERARCHY", 1L);
    
    // 验证审计日志
    assertEquals(1, logs.size(), "应该记录1条审计日志");
    AuditLog log = logs.get(0);
    assertEquals("ASSIGN", log.getOperationType());
    assertEquals("HIERARCHY", log.getTargetType());
    assertEquals(100L, log.getOperatorId());
    assertTrue(log.getOperationDetails().contains("parentRoleId"));
    assertTrue(log.getOperationDetails().contains("childRoleId"));
}

总结

RBAC1 模型通过引入角色继承机制,大大简化了权限管理的复杂度。结合完善的审计功能,可以构建一个既灵活又安全的权限控制系统。

关键优势

  1. 减少权限重复分配:通过继承避免重复配置
  2. 层次化管理:符合组织架构的自然层次
  3. 完整审计追踪:所有关键操作都有迹可循
  4. 安全防护:防止循环继承等异常情况

实施建议

  1. 继承层级控制:建议不超过5级,避免过度复杂
  2. 审计日志归档:定期归档历史审计日志,保证查询性能
  3. 缓存优化:对角色继承关系进行缓存,避免频繁数据库查询
  4. 权限变更通知:重要权限变更时发送通知给相关人员

这个实现方案适用于中大型企业应用,能够有效支撑复杂的组织架构和权限需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙茶清欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值