247 lines
6.7 KiB
Markdown
247 lines
6.7 KiB
Markdown
|
|
# 菜单权限控制说明
|
|||
|
|
|
|||
|
|
## 📋 概述
|
|||
|
|
|
|||
|
|
系统通过 **权限编码(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`)
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// 获取用户的所有权限
|
|||
|
|
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`)
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
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`)
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
const route: RouteRecordRaw = {
|
|||
|
|
path: routePath,
|
|||
|
|
name: routeName,
|
|||
|
|
meta: {
|
|||
|
|
title: menu.name,
|
|||
|
|
requiresAuth: true,
|
|||
|
|
// 如果菜单有权限要求,添加到路由meta中
|
|||
|
|
...(menu.permission && { permissions: [menu.permission] }),
|
|||
|
|
},
|
|||
|
|
component: componentLoader,
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2. 路由守卫检查(`router.beforeEach`)
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// 检查权限
|
|||
|
|
const requiredPermissions = to.meta.permissions;
|
|||
|
|
if (requiredPermissions && requiredPermissions.length > 0) {
|
|||
|
|
if (!authStore.hasAnyPermission(requiredPermissions)) {
|
|||
|
|
// 没有所需权限,跳转到 403 页面
|
|||
|
|
next({ name: "Forbidden" });
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3. 权限检查方法(`authStore`)
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// 检查是否有指定权限
|
|||
|
|
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. 查看用户权限
|
|||
|
|
|
|||
|
|
在浏览器控制台执行:
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 查看当前用户权限
|
|||
|
|
console.log(useAuthStore().user?.permissions);
|
|||
|
|
|
|||
|
|
// 检查是否有特定权限
|
|||
|
|
console.log(useAuthStore().hasPermission("user:read"));
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 查看用户菜单
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 查看当前用户的菜单
|
|||
|
|
console.log(useAuthStore().menus);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 后端调试
|
|||
|
|
|
|||
|
|
在后端日志中查看:
|
|||
|
|
|
|||
|
|
- 用户权限列表
|
|||
|
|
- 菜单过滤结果
|
|||
|
|
|
|||
|
|
## 📚 相关文档
|
|||
|
|
|
|||
|
|
- [RBAC 权限控制详解](./RBAC_GUIDE.md)
|
|||
|
|
- [菜单管理使用说明](./MENU_MANAGEMENT.md)
|
|||
|
|
- [权限管理使用说明](./PERMISSION_MANAGEMENT.md)
|