library-picturebook-activity/docs/MENU_PERMISSION_CONTROL.md
2025-11-23 14:04:20 +08:00

6.7 KiB
Raw Blame History

菜单权限控制说明

📋 概述

系统通过 权限编码Permission Code 来控制用户对菜单的访问。菜单权限控制分为两个层面:

  1. 菜单显示控制:根据用户权限过滤菜单,只显示用户有权限访问的菜单
  2. 路由访问控制:通过路由守卫检查用户是否有权限访问某个页面

🔄 权限控制流程

用户登录
  ↓
获取用户角色和权限
  ↓
调用 /api/menus/user-menus 获取用户菜单
  ↓
后端根据用户权限过滤菜单
  ↓
前端动态生成路由和菜单
  ↓
路由守卫检查页面访问权限

🎯 如何配置菜单权限

1. 创建权限

首先需要在权限管理中创建权限,例如:

  • menu:read - 查看菜单权限
  • user:read - 查看用户权限
  • role:read - 查看角色权限

2. 将权限分配给角色

在角色管理中,为角色分配相应的权限。例如:

  • 管理员角色:拥有所有权限
  • 普通用户角色:只拥有 user:read 权限

3. 为用户分配角色

在用户管理中,为用户分配角色。用户会继承角色的所有权限。

4. 为菜单设置权限编码

在菜单管理中,为菜单设置 权限编码 字段:

示例配置

菜单名称 路径 权限编码 说明
用户管理 /system/users user:read 只有拥有 user:read 权限的用户才能看到此菜单
角色管理 /system/roles role:read 只有拥有 role:read 权限的用户才能看到此菜单
权限管理 /system/permissions - 不设置权限编码,所有用户都可以看到
仪表盘 /dashboard - 不设置权限编码,所有用户都可以看到

5. 权限编码规则

权限编码格式:资源:操作

常见示例:

  • user:read - 查看用户
  • user:create - 创建用户
  • user:update - 更新用户
  • user:delete - 删除用户
  • role:read - 查看角色
  • menu:read - 查看菜单

💻 技术实现

后端实现

1. 菜单权限过滤(MenusService.findUserMenus

// 获取用户的所有权限
const userPermissions = await this.authService.getUserPermissions(userId);

// 过滤菜单如果菜单有permission字段检查用户是否有该权限
const filterMenus = (menus: any[]): any[] => {
  return menus
    .filter((menu) => {
      // 如果菜单没有设置权限要求,则显示
      if (!menu.permission) {
        return true;
      }
      // 如果设置了权限要求,检查用户是否有该权限
      return userPermissions.includes(menu.permission);
    })
    .map((menu) => {
      // 递归过滤子菜单
      if (menu.children && menu.children.length > 0) {
        menu.children = filterMenus(menu.children);
      }
      return menu;
    });
};

2. 用户权限获取(AuthService.getUserPermissions

async getUserPermissions(userId: number): Promise<string[]> {
  const user = await this.usersService.findOne(userId);
  const permissions = new Set<string>();

  // 遍历用户的所有角色
  user.roles?.forEach((ur: any) => {
    // 遍历角色的所有权限
    ur.role.permissions?.forEach((rp: any) => {
      permissions.add(rp.permission.code);
    });
  });

  return Array.from(permissions);
}

前端实现

1. 菜单转换为路由(convertMenusToRoutes

const route: RouteRecordRaw = {
  path: routePath,
  name: routeName,
  meta: {
    title: menu.name,
    requiresAuth: true,
    // 如果菜单有权限要求添加到路由meta中
    ...(menu.permission && { permissions: [menu.permission] }),
  },
  component: componentLoader,
};

2. 路由守卫检查(router.beforeEach

// 检查权限
const requiredPermissions = to.meta.permissions;
if (requiredPermissions && requiredPermissions.length > 0) {
  if (!authStore.hasAnyPermission(requiredPermissions)) {
    // 没有所需权限,跳转到 403 页面
    next({ name: "Forbidden" });
    return;
  }
}

3. 权限检查方法(authStore

// 检查是否有指定权限
const hasPermission = (permission: string): boolean => {
  return user.value?.permissions?.includes(permission) ?? false;
};

// 检查是否有任一权限
const hasAnyPermission = (permissions: string[]): boolean => {
  if (!permissions || permissions.length === 0) return true;
  return permissions.some((perm) => hasPermission(perm));
};

📝 使用示例

示例 1创建需要权限的菜单

  1. 登录系统,进入 菜单管理
  2. 点击 新增菜单
  3. 填写菜单信息:
    • 菜单名称:用户管理
    • 路由路径:/system/users
    • 组件路径:system/users/Index
    • 权限编码user:read
    • 父菜单:选择 系统管理
  4. 保存菜单

示例 2创建公开菜单所有用户可见

  1. 在菜单管理中新增菜单
  2. 权限编码字段留空
  3. 这样所有用户都可以看到此菜单

示例 3为用户分配权限

  1. 进入 权限管理,创建权限:

    • 权限名称:查看用户
    • 权限编码:user:read
    • 资源:user
    • 操作:read
  2. 进入 角色管理,编辑角色:

    • 为角色分配 user:read 权限
  3. 进入 用户管理,编辑用户:

    • 为用户分配该角色

⚠️ 注意事项

  1. 权限编码必须唯一:每个权限编码在系统中是唯一的
  2. 菜单权限为空则公开:如果菜单的 权限编码 字段为空,所有用户都可以看到
  3. 子菜单继承父菜单权限:子菜单会独立检查权限,不会自动继承父菜单权限
  4. 路由和菜单双重控制
    • 菜单显示控制:控制菜单是否在侧边栏显示
    • 路由访问控制:控制用户是否可以直接访问页面(通过 URL
  5. 权限变更后需重新登录:权限变更后,用户需要重新登录才能看到新的菜单

🔍 调试技巧

1. 查看用户权限

在浏览器控制台执行:

// 查看当前用户权限
console.log(useAuthStore().user?.permissions);

// 检查是否有特定权限
console.log(useAuthStore().hasPermission("user:read"));

2. 查看用户菜单

// 查看当前用户的菜单
console.log(useAuthStore().menus);

3. 后端调试

在后端日志中查看:

  • 用户权限列表
  • 菜单过滤结果

📚 相关文档