RBAC权限管理系统设计:让权限控制如臂使指!🔐

93 阅读10分钟

标题: 权限管理太复杂?RBAC模型帮你搞定一切!
副标题: 从用户到权限,一套完整的权限管理解决方案


🎬 开篇:权限管理的噩梦

项目经理:小王,给张三开通查看财务报表的权限
开发:好的!(直接在代码里加if判断)

一周后:
项目经理:给李四也开通
开发:好的!(再加一个if)

一个月后:
项目经理:所有财务部的人都要有这个权限
开发:💀 我要改100个地方...

三个月后:
项目经理:张三离职了,回收所有权限
开发:😱 他有30个权限,我要查遍整个代码库...

六个月后:
CEO:系统被黑了,数据泄露!谁有查看用户隐私的权限?
开发:这...我也不知道啊...😰

教训:权限管理必须系统化、可配置、可追溯!

🤔 为什么需要RBAC?

想象公司的门禁系统:

  • ❌ 直接给权限: 每个人单独配置能进哪些门(管理噩梦)
  • ✅ RBAC模式: 员工→角色→权限(总经理能进所有门,前台只能进大厅)

📚 知识地图

RBAC权限管理体系
├── 👤 用户(User)
├── 🎭 角色(Role)
├── 🔑 权限(Permission)
├── 📊 数据权限(Data Permission)
└── 🔗 关系映射
    ├── 用户-角色(多对多)
    ├── 角色-权限(多对多)
    └── 角色-数据范围

🗄️ 第一章:数据库表设计

核心表结构

-- 1. 用户表
CREATE TABLE sys_user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL COMMENT '用户名',
    password VARCHAR(100) NOT NULL COMMENT '密码(加密)',
    real_name VARCHAR(50) COMMENT '真实姓名',
    email VARCHAR(100) COMMENT '邮箱',
    phone VARCHAR(20) COMMENT '手机号',
    dept_id BIGINT COMMENT '部门ID',
    status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1-正常,0-禁用',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_username (username),
    INDEX idx_dept_id (dept_id),
    INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

-- 2. 角色表
CREATE TABLE sys_role (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_code VARCHAR(50) NOT NULL COMMENT '角色编码',
    role_name VARCHAR(50) NOT NULL COMMENT '角色名称',
    description VARCHAR(200) COMMENT '描述',
    data_scope TINYINT NOT NULL DEFAULT 1 COMMENT '数据范围:1-全部,2-本部门及子部门,3-本部门,4-仅本人,5-自定义',
    status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1-正常,0-禁用',
    sort INT NOT NULL DEFAULT 0 COMMENT '排序',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_role_code (role_code),
    INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';

-- 3. 权限表(菜单+按钮)
CREATE TABLE sys_permission (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    parent_id BIGINT NOT NULL DEFAULT 0 COMMENT '父级ID',
    permission_code VARCHAR(100) NOT NULL COMMENT '权限编码',
    permission_name VARCHAR(50) NOT NULL COMMENT '权限名称',
    permission_type TINYINT NOT NULL COMMENT '类型:1-菜单,2-按钮',
    url VARCHAR(200) COMMENT '菜单URL',
    icon VARCHAR(50) COMMENT '图标',
    sort INT NOT NULL DEFAULT 0 COMMENT '排序',
    status TINYINT NOT NULL DEFAULT 1 COMMENT '状态',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_permission_code (permission_code),
    INDEX idx_parent_id (parent_id),
    INDEX idx_type (permission_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表';

-- 4. 用户-角色关联表
CREATE TABLE sys_user_role (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL COMMENT '用户ID',
    role_id BIGINT NOT NULL COMMENT '角色ID',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_user_role (user_id, role_id),
    INDEX idx_user_id (user_id),
    INDEX idx_role_id (role_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';

-- 5. 角色-权限关联表
CREATE TABLE sys_role_permission (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_id BIGINT NOT NULL COMMENT '角色ID',
    permission_id BIGINT NOT NULL COMMENT '权限ID',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_role_permission (role_id, permission_id),
    INDEX idx_role_id (role_id),
    INDEX idx_permission_id (permission_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色权限关联表';

-- 6. 部门表(数据权限用)
CREATE TABLE sys_dept (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    parent_id BIGINT NOT NULL DEFAULT 0 COMMENT '父部门ID',
    dept_name VARCHAR(50) NOT NULL COMMENT '部门名称',
    dept_code VARCHAR(50) NOT NULL COMMENT '部门编码',
    leader VARCHAR(50) COMMENT '负责人',
    phone VARCHAR(20) COMMENT '联系电话',
    sort INT NOT NULL DEFAULT 0 COMMENT '排序',
    status TINYINT NOT NULL DEFAULT 1 COMMENT '状态',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_dept_code (dept_code),
    INDEX idx_parent_id (parent_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='部门表';

-- 7. 角色-部门关联表(自定义数据权限)
CREATE TABLE sys_role_dept (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_id BIGINT NOT NULL COMMENT '角色ID',
    dept_id BIGINT NOT NULL COMMENT '部门ID',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_role_dept (role_id, dept_id),
    INDEX idx_role_id (role_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色部门关联表';

-- 初始化数据
-- 超级管理员
INSERT INTO sys_user (username, password, real_name, status) 
VALUES ('admin', '$2a$10$encrypted_password', '超级管理员', 1);

-- 角色
INSERT INTO sys_role (role_code, role_name, data_scope) VALUES
('ADMIN', '超级管理员', 1),
('MANAGER', '部门经理', 2),
('EMPLOYEE', '普通员工', 4);

-- 权限(示例)
INSERT INTO sys_permission (parent_id, permission_code, permission_name, permission_type, url) VALUES
(0, 'system', '系统管理', 1, '/system'),
(1, 'system:user', '用户管理', 1, '/system/user'),
(2, 'system:user:add', '新增用户', 2, NULL),
(2, 'system:user:edit', '编辑用户', 2, NULL),
(2, 'system:user:delete', '删除用户', 2, NULL);

💻 第二章:核心功能实现

功能1:权限校验

/**
 * 权限服务
 */
@Service
public class PermissionService {
    
    @Autowired
    private UserRoleMapper userRoleMapper;
    
    @Autowired
    private RolePermissionMapper rolePermissionMapper;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 检查用户是否有指定权限
     */
    public boolean hasPermission(Long userId, String permissionCode) {
        // 1. 从缓存获取用户权限
        String cacheKey = "user:permissions:" + userId;
        Set<String> permissions = (Set<String>) redisTemplate.opsForValue().get(cacheKey);
        
        if (permissions == null) {
            // 2. 查询用户的所有权限
            permissions = loadUserPermissions(userId);
            
            // 3. 缓存权限(30分钟)
            redisTemplate.opsForValue().set(
                cacheKey, 
                permissions, 
                Duration.ofMinutes(30)
            );
        }
        
        // 4. 检查权限
        boolean hasPermission = permissions.contains(permissionCode);
        
        log.debug("权限校验:userId={}, permission={}, result={}", 
            userId, permissionCode, hasPermission);
        
        return hasPermission;
    }
    
    /**
     * 加载用户的所有权限
     */
    private Set<String> loadUserPermissions(Long userId) {
        // 1. 查询用户的所有角色
        List<Long> roleIds = userRoleMapper.selectRoleIdsByUserId(userId);
        
        if (roleIds.isEmpty()) {
            return Collections.emptySet();
        }
        
        // 2. 查询角色的所有权限
        List<String> permissionCodes = rolePermissionMapper
            .selectPermissionCodesByRoleIds(roleIds);
        
        return new HashSet<>(permissionCodes);
    }
    
    /**
     * 清除用户权限缓存(角色变更时调用)
     */
    public void clearUserPermissionCache(Long userId) {
        String cacheKey = "user:permissions:" + userId;
        redisTemplate.delete(cacheKey);
        
        log.info("清除用户权限缓存:userId={}", userId);
    }
}

/**
 * 权限注解
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermission {
    String value();  // 权限编码
    String message() default "无权限访问";
}

/**
 * 权限拦截器(AOP)
 */
@Aspect
@Component
public class PermissionAspect {
    
    @Autowired
    private PermissionService permissionService;
    
    @Around("@annotation(requiresPermission)")
    public Object checkPermission(ProceedingJoinPoint pjp, 
                                 RequiresPermission requiresPermission) throws Throwable {
        
        // 1. 获取当前用户ID
        Long userId = UserContext.getCurrentUserId();
        if (userId == null) {
            throw new UnauthorizedException("未登录");
        }
        
        // 2. 检查权限
        String permissionCode = requiresPermission.value();
        boolean hasPermission = permissionService.hasPermission(userId, permissionCode);
        
        if (!hasPermission) {
            throw new ForbiddenException(requiresPermission.message());
        }
        
        // 3. 执行方法
        return pjp.proceed();
    }
}

/**
 * 使用示例
 */
@RestController
@RequestMapping("/user")
public class UserController {
    
    /**
     * 新增用户(需要权限)
     */
    @PostMapping
    @RequiresPermission("system:user:add")
    public Result addUser(@RequestBody User user) {
        // 业务逻辑
        return Result.success();
    }
    
    /**
     * 删除用户(需要权限)
     */
    @DeleteMapping("/{id}")
    @RequiresPermission("system:user:delete")
    public Result deleteUser(@PathVariable Long id) {
        // 业务逻辑
        return Result.success();
    }
}

功能2:数据权限

/**
 * 数据权限服务
 */
@Service
public class DataPermissionService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Autowired
    private RoleMapper roleMapper;
    
    @Autowired
    private DeptMapper deptMapper;
    
    /**
     * 获取用户的数据权限SQL
     */
    public String getDataPermissionSql(Long userId, String tableAlias) {
        // 1. 查询用户信息
        User user = userMapper.selectById(userId);
        if (user == null) {
            return " 1=0 ";  // 无权限
        }
        
        // 2. 查询用户的角色
        List<Role> roles = roleMapper.selectByUserId(userId);
        if (roles.isEmpty()) {
            return " 1=0 ";
        }
        
        // 3. 获取最大的数据权限范围
        DataScope maxScope = roles.stream()
            .map(Role::getDataScope)
            .max(Comparator.comparing(DataScope::getLevel))
            .orElse(DataScope.SELF);
        
        // 4. 根据数据范围生成SQL
        switch (maxScope) {
            case ALL:
                // 全部数据
                return " 1=1 ";
                
            case DEPT_AND_CHILD:
                // 本部门及子部门数据
                List<Long> deptIds = deptMapper.selectChildDeptIds(user.getDeptId());
                deptIds.add(user.getDeptId());
                return String.format(" %s.dept_id IN (%s) ", 
                    tableAlias, 
                    deptIds.stream()
                        .map(String::valueOf)
                        .collect(Collectors.joining(",")));
                
            case DEPT_ONLY:
                // 仅本部门数据
                return String.format(" %s.dept_id = %d ", tableAlias, user.getDeptId());
                
            case SELF:
                // 仅本人数据
                return String.format(" %s.create_user_id = %d ", tableAlias, userId);
                
            case CUSTOM:
                // 自定义数据权限
                List<Long> customDeptIds = roleMapper.selectCustomDeptIds(
                    roles.stream()
                        .map(Role::getId)
                        .collect(Collectors.toList())
                );
                
                if (customDeptIds.isEmpty()) {
                    return " 1=0 ";
                }
                
                return String.format(" %s.dept_id IN (%s) ",
                    tableAlias,
                    customDeptIds.stream()
                        .map(String::valueOf)
                        .collect(Collectors.joining(",")));
                
            default:
                return " 1=0 ";
        }
    }
}

/**
 * 数据权限注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataPermission {
    String tableAlias() default "t";  // 表别名
}

/**
 * MyBatis拦截器(实现数据权限过滤)
 */
@Intercepts({
    @Signature(
        type = StatementHandler.class,
        method = "prepare",
        args = {Connection.class, Integer.class}
    )
})
@Component
public class DataPermissionInterceptor implements Interceptor {
    
    @Autowired
    private DataPermissionService dataPermissionService;
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        
        // 1. 获取SQL
        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
        String originalSql = boundSql.getSql();
        
        // 2. 检查是否需要数据权限过滤
        MappedStatement mappedStatement = 
            (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        
        String methodName = mappedStatement.getId();
        Method method = getMethod(methodName);
        
        if (method == null || !method.isAnnotationPresent(DataPermission.class)) {
            // 不需要数据权限过滤
            return invocation.proceed();
        }
        
        // 3. 获取当前用户ID
        Long userId = UserContext.getCurrentUserId();
        if (userId == null) {
            throw new UnauthorizedException("未登录");
        }
        
        // 4. 获取表别名
        DataPermission annotation = method.getAnnotation(DataPermission.class);
        String tableAlias = annotation.tableAlias();
        
        // 5. 生成数据权限SQL
        String dataPermissionSql = dataPermissionService
            .getDataPermissionSql(userId, tableAlias);
        
        // 6. 修改SQL(在WHERE后面添加数据权限条件)
        String newSql = addDataPermissionToSql(originalSql, dataPermissionSql);
        
        // 7. 替换SQL
        metaObject.setValue("delegate.boundSql.sql", newSql);
        
        log.debug("数据权限SQL:{}", dataPermissionSql);
        
        return invocation.proceed();
    }
    
    /**
     * 在SQL中添加数据权限条件
     */
    private String addDataPermissionToSql(String originalSql, String dataPermissionSql) {
        // 简化实现:在WHERE后面添加AND条件
        // 实际应使用SQL解析器(如JSqlParser)
        
        if (originalSql.toUpperCase().contains("WHERE")) {
            return originalSql.replaceFirst(
                "(?i)WHERE", 
                "WHERE " + dataPermissionSql + " AND "
            );
        } else {
            return originalSql + " WHERE " + dataPermissionSql;
        }
    }
    
    private Method getMethod(String methodName) {
        try {
            String className = methodName.substring(0, methodName.lastIndexOf("."));
            String methodSimpleName = methodName.substring(methodName.lastIndexOf(".") + 1);
            
            Class<?> clazz = Class.forName(className);
            return Arrays.stream(clazz.getMethods())
                .filter(m -> m.getName().equals(methodSimpleName))
                .findFirst()
                .orElse(null);
        } catch (Exception e) {
            return null;
        }
    }
}

/**
 * 使用示例
 */
@Mapper
public interface UserMapper {
    
    /**
     * 查询用户列表(带数据权限)
     */
    @Select("SELECT * FROM sys_user t WHERE t.status = 1")
    @DataPermission(tableAlias = "t")
    List<User> selectList();
}

/**
 * SQL执行示例:
 * 
 * 原始SQL:
 * SELECT * FROM sys_user t WHERE t.status = 1
 * 
 * 添加数据权限后(假设是部门经理):
 * SELECT * FROM sys_user t 
 * WHERE t.dept_id IN (100, 101, 102) AND t.status = 1
 * 
 * 效果:只能查询本部门及子部门的用户 ✅
 */

功能3:角色管理

/**
 * 角色服务
 */
@Service
public class RoleService {
    
    @Autowired
    private RoleMapper roleMapper;
    
    @Autowired
    private RolePermissionMapper rolePermissionMapper;
    
    @Autowired
    private UserRoleMapper userRoleMapper;
    
    @Autowired
    private PermissionService permissionService;
    
    /**
     * 创建角色
     */
    @Transactional(rollbackFor = Exception.class)
    public Long createRole(RoleDTO dto) {
        // 1. 检查角色编码是否重复
        Role existing = roleMapper.selectByRoleCode(dto.getRoleCode());
        if (existing != null) {
            throw new BusinessException("角色编码已存在");
        }
        
        // 2. 创建角色
        Role role = Role.builder()
            .roleCode(dto.getRoleCode())
            .roleName(dto.getRoleName())
            .description(dto.getDescription())
            .dataScope(dto.getDataScope())
            .status(1)
            .build();
        
        roleMapper.insert(role);
        
        // 3. 分配权限
        if (dto.getPermissionIds() != null && !dto.getPermissionIds().isEmpty()) {
            assignPermissions(role.getId(), dto.getPermissionIds());
        }
        
        log.info("创建角色成功:roleId={}, roleCode={}", role.getId(), role.getRoleCode());
        return role.getId();
    }
    
    /**
     * 分配权限
     */
    @Transactional(rollbackFor = Exception.class)
    public void assignPermissions(Long roleId, List<Long> permissionIds) {
        // 1. 删除旧的权限关联
        rolePermissionMapper.deleteByRoleId(roleId);
        
        // 2. 插入新的权限关联
        if (permissionIds != null && !permissionIds.isEmpty()) {
            List<RolePermission> list = permissionIds.stream()
                .map(permissionId -> RolePermission.builder()
                    .roleId(roleId)
                    .permissionId(permissionId)
                    .build())
                .collect(Collectors.toList());
            
            rolePermissionMapper.batchInsert(list);
        }
        
        // 3. 清除该角色下所有用户的权限缓存
        List<Long> userIds = userRoleMapper.selectUserIdsByRoleId(roleId);
        for (Long userId : userIds) {
            permissionService.clearUserPermissionCache(userId);
        }
        
        log.info("分配权限成功:roleId={}, permissionCount={}", 
            roleId, permissionIds.size());
    }
    
    /**
     * 分配用户
     */
    @Transactional(rollbackFor = Exception.class)
    public void assignUsers(Long roleId, List<Long> userIds) {
        // 1. 删除旧的用户关联
        userRoleMapper.deleteByRoleId(roleId);
        
        // 2. 插入新的用户关联
        if (userIds != null && !userIds.isEmpty()) {
            List<UserRole> list = userIds.stream()
                .map(userId -> UserRole.builder()
                    .userId(userId)
                    .roleId(roleId)
                    .build())
                .collect(Collectors.toList());
            
            userRoleMapper.batchInsert(list);
            
            // 3. 清除用户权限缓存
            for (Long userId : userIds) {
                permissionService.clearUserPermissionCache(userId);
            }
        }
        
        log.info("分配用户成功:roleId={}, userCount={}", roleId, userIds.size());
    }
    
    /**
     * 删除角色
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteRole(Long roleId) {
        // 1. 检查是否有用户关联
        long userCount = userRoleMapper.countByRoleId(roleId);
        if (userCount > 0) {
            throw new BusinessException("该角色下还有用户,无法删除");
        }
        
        // 2. 删除角色-权限关联
        rolePermissionMapper.deleteByRoleId(roleId);
        
        // 3. 删除角色
        roleMapper.deleteById(roleId);
        
        log.info("删除角色成功:roleId={}", roleId);
    }
}

🎯 第三章:前端集成

菜单权限

<!-- MenuTree.vue -->
<template>
  <el-menu>
    <template v-for="menu in menuTree">
      <!-- 一级菜单 -->
      <el-submenu 
        v-if="menu.children && menu.children.length > 0"
        :key="menu.id"
        :index="menu.id.toString()">
        
        <template #title>
          <i :class="menu.icon"></i>
          <span>{{ menu.name }}</span>
        </template>
        
        <!-- 子菜单 -->
        <el-menu-item
          v-for="child in menu.children"
          :key="child.id"
          :index="child.url">
          {{ child.name }}
        </el-menu-item>
      </el-submenu>
      
      <!-- 无子菜单 -->
      <el-menu-item
        v-else
        :key="menu.id"
        :index="menu.url">
        <i :class="menu.icon"></i>
        <span>{{ menu.name }}</span>
      </el-menu-item>
    </template>
  </el-menu>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { getUserMenuTree } from '@/api/permission'

const menuTree = ref([])

onMounted(async () => {
  // 获取当前用户的菜单树
  const response = await getUserMenuTree()
  menuTree.value = response.data
})
</script>

按钮权限

<!-- UserList.vue -->
<template>
  <div>
    <el-table :data="userList">
      <el-table-column prop="username" label="用户名" />
      <el-table-column prop="realName" label="姓名" />
      
      <el-table-column label="操作">
        <template #default="{ row }">
          <!-- v-permission指令控制按钮显示 -->
          <el-button
            v-permission="'system:user:edit'"
            type="primary"
            @click="handleEdit(row)">
            编辑
          </el-button>
          
          <el-button
            v-permission="'system:user:delete'"
            type="danger"
            @click="handleDelete(row)">
            删除
          </el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script setup>
// permission指令
import { useUserStore } from '@/stores/user'

// 自定义指令
app.directive('permission', {
  mounted(el, binding) {
    const userStore = useUserStore()
    const permissionCode = binding.value
    
    // 检查是否有权限
    if (!userStore.permissions.includes(permissionCode)) {
      // 没有权限,移除元素
      el.parentNode?.removeChild(el)
    }
  }
})
</script>

✅ 最佳实践

设计原则:
□ 最小权限原则(用户只有必需的权限)
□ 职责分离原则(权限不能过度集中)
□ 权限可追溯(记录权限变更日志)
□ 默认拒绝原则(无权限默认拒绝访问)

性能优化:
□ 权限信息缓存到Redis(减少数据库查询)
□ 用户登录时加载权限(避免每次查询)
□ 菜单树缓存(前端缓存菜单结构)
□ 数据权限SQL优化(建立合适的索引)

安全加固:
□ 权限变更需要审批流程
□ 记录所有权限操作日志
□ 定期审计用户权限
□ 敏感操作二次验证

用户体验:
□ 无权限时友好提示
□ 权限不足时不显示按钮
□ 提供权限申请流程
□ 管理员可以临时授权

🎉 总结

核心要点

RBAC权限管理核心:

1️⃣ 五张核心表
   - 用户表
   - 角色表
   - 权限表
   - 用户角色关联表
   - 角色权限关联表

2️⃣ 两种权限
   - 功能权限(菜单+按钮)
   - 数据权限(部门、本人等)

3️⃣ 三个关键点
   - 权限缓存(Redis)
   - 数据权限(MyBatis拦截器)
   - 前后端联动(菜单树+按钮权限)

4️⃣ 四大原则
   - 最小权限
   - 职责分离
   - 默认拒绝
   - 可追溯

📚 延伸阅读


记住:好的权限系统是"灵活、安全、高效"的完美结合! 🔐


文档编写时间:2025年10月24日
作者:热爱安全的权限工程师
版本:v1.0
愿你的系统固若金汤! 🔐✨