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)
|