# 权限系统增强设计文档 **日期**: 2026-04-08 **作者**: 张翔 **版本**: 1.0 **状态**: 待审查 ## 1. 概述 ### 1.1 背景 当前系统已完成基础的路由权限控制,但存在以下优化空间: 1. **菜单硬编码** - 菜单在前端硬编码,无法根据用户角色动态显示 2. **权限数据分散** - 角色和权限信息存储在 localStorage,缺乏统一管理 3. **缺少按钮级权限控制** - 无法控制按钮级别的权限 4. **缺少 API 权限检查** - 前端调用 API 前未检查权限,可能发送无效请求 ### 1.2 目标 实现完整的权限系统增强,包括: 1. **动态菜单渲染** - 从后端获取菜单数据,根据用户权限动态渲染 2. **权限缓存优化** - 使用 Pinia 统一管理权限数据,localStorage 持久化 3. **权限指令** - 提供 `v-permission` 指令实现按钮级权限控制 4. **API 权限检查** - 前端调用 API 前检查权限,减少无效请求 ### 1.3 范围 **包含:** - Permission Store (Pinia) - v-permission 指令 - 动态菜单渲染 - API 权限检查工具 - 相关单元测试 **不包含:** - 后端权限系统修改(仅需新增 API) - 数据库权限表结构调整 - 其他业务功能开发 ## 2. 架构设计 ### 2.1 整体架构 ``` ┌─────────────────────────────────────────────────────────────┐ │ 前端应用 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ 路由守卫 │ │ 权限指令 │ │ 动态菜单 │ │ │ │ (已完成) │ │ v-permission │ │ 渲染 │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ │ └──────────────────┼──────────────────┘ │ │ │ │ │ ┌────────▼────────┐ │ │ │ Permission │ │ │ │ Store (Pinia) │ │ │ └────────┬────────┘ │ │ │ │ │ ┌────────▼────────┐ │ │ │ localStorage │ │ │ │ (持久化) │ │ │ └─────────────────┘ │ │ │ └───────────────────────────┬─────────────────────────────────┘ │ HTTP API │ │ ┌───────────────────────────▼─────────────────────────────────┐ │ 后端服务 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ /auth/login │ │ /menus/user │ │ /permissions │ │ │ │ (已存在) │ │ (新增) │ │ (已存在) │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────┐ │ │ │ RBAC 权限系统 (角色-权限-菜单) │ │ │ └──────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ``` ### 2.2 数据流 ``` 登录成功 ↓ 解析 JWT token 获取角色 ↓ 调用 fetchUserMenus() 获取菜单和权限 ↓ 存入 Store + localStorage ↓ 页面刷新时从 localStorage 恢复 ``` ## 3. 详细设计 ### 3.1 Permission Store **文件位置**: `src/stores/permission.ts` **状态定义**: ```typescript interface PermissionState { roles: string[] // 用户角色 permissions: string[] // 用户权限码 menus: MenuItem[] // 用户菜单 loaded: boolean // 是否已加载 } interface MenuItem { id: number name: string path: string icon?: string parentId?: number sort: number children?: MenuItem[] } ``` **核心 Actions**: ```typescript // 初始化权限数据(从 localStorage 恢复) initFromStorage(): void // 登录后设置权限数据 setPermissionData(data: { roles: string[] permissions: string[] menus: MenuItem[] }): void // 从后端刷新权限数据 async fetchUserMenus(): Promise // 清除权限数据(退出登录) clearPermissionData(): void // 权限检查方法 hasRole(role: string | string[]): boolean hasPermission(permission: string | string[]): boolean ``` **持久化策略**: - 登录时:将角色、权限、菜单数据存入 localStorage - 页面刷新:Pinia 从 localStorage 恢复数据,立即渲染菜单 - 权限变更:提供刷新机制,同步更新 localStorage 和 Pinia - 退出登录:清除所有数据 ### 3.2 v-permission 指令 **文件位置**: `src/directives/permission.ts` **用法**: ```vue ``` **实现逻辑**: ```typescript export const permissionDirective = { mounted(el: HTMLElement, binding: DirectiveBinding) { const permissionStore = usePermissionStore() const { arg, value } = binding const checkType = arg || 'permission' // 默认权限检查 let hasAccess = false if (checkType === 'role') { hasAccess = permissionStore.hasRole(value) } else if (checkType === 'permission') { hasAccess = permissionStore.hasPermission(value) } if (!hasAccess) { el.style.display = 'none' } } } ``` **注册方式**: ```typescript // src/main.ts import { permissionDirective } from '@/directives/permission' app.directive('permission', permissionDirective) ``` ### 3.3 动态菜单渲染 **后端 API**: ``` GET /api/menus/user 请求头: Authorization: Bearer 响应: { "code": 200, "data": { "menus": [ { "id": 1, "name": "仪表盘", "path": "/dashboard", "icon": "Odometer", "parentId": null, "sort": 1 }, { "id": 2, "name": "系统管理", "path": "/system", "icon": "Setting", "parentId": null, "sort": 2, "children": [ { "id": 3, "name": "用户管理", "path": "/users", "icon": null, "parentId": 2, "sort": 1 } ] } ], "permissions": [ "user:read", "user:create", "user:update", "user:delete" ] } } ``` **前端组件**: ```vue ``` **递归菜单组件**: ```vue ``` ### 3.4 API 权限检查 **文件位置**: `src/utils/permission-check.ts` **权限映射配置**: ```typescript const apiPermissionMap: Record = { '/api/users:GET': { permission: 'user:read', method: 'GET' }, '/api/users:POST': { permission: 'user:create', method: 'POST' }, '/api/users/*:PUT': { permission: 'user:update', method: 'PUT' }, '/api/users/*:DELETE': { permission: 'user:delete', method: 'DELETE' }, '/api/roles:GET': { permission: 'role:read', method: 'GET' }, // ... 更多映射 } ``` **检查函数**: ```typescript export function canAccessApi(path: string, method: string): boolean { const permissionStore = usePermissionStore() const required = findRequiredPermission(path, method, apiPermissionMap) if (!required) { return true // 未定义权限要求的 API 默认允许 } return permissionStore.hasPermission(required.permission) } ``` **集成到请求拦截器**: ```typescript // src/utils/request.ts import { canAccessApi } from './permission-check' request.interceptors.request.use( (config) => { // 权限检查 const path = config.url || '' const method = config.method?.toUpperCase() || 'GET' if (!canAccessApi(path, method)) { return Promise.reject(new Error('无权限访问此 API')) } // 原有的 token 和签名逻辑 // ... return config } ) ``` ## 4. 测试策略 ### 4.1 测试覆盖范围 1. **Permission Store 单元测试** - 测试权限数据的存储和恢复 - 测试 hasRole 和 hasPermission 方法 - 测试 localStorage 持久化 - 测试数据清除功能 2. **v-permission 指令测试** - 测试角色检查功能 - 测试权限码检查功能 - 测试数组参数处理 - 测试元素隐藏/显示逻辑 3. **动态菜单测试** - 测试菜单数据获取 - 测试菜单树渲染 - 测试菜单缓存机制 - 测试菜单权限过滤 4. **API 权限检查测试** - 测试权限映射匹配 - 测试通配符匹配 - 测试请求拦截逻辑 ### 4.2 测试文件结构 ``` src/ ├── stores/ │ └── __tests__/ │ └── permission.test.ts ├── directives/ │ └── __tests__/ │ └── permission.test.ts ├── components/ │ └── __tests__/ │ └── MenuItem.test.ts └── utils/ └── __tests__/ └── permission-check.test.ts ``` ## 5. 实施计划 ### 5.1 实施顺序 **第 1 步:Permission Store(1-2 小时)** - 创建 `src/stores/permission.ts` - 实现 localStorage 持久化 - 编写单元测试 - 集成到登录流程 **第 2 步:v-permission 指令(1-2 小时)** - 创建 `src/directives/permission.ts` - 注册全局指令 - 编写单元测试 - 在现有页面应用示例 **第 3 步:后端 API 开发(2-3 小时)** - 新增 `GET /api/menus/user` 接口 - 根据用户角色返回菜单树 - 返回用户权限列表 - 编写后端测试 **第 4 步:动态菜单渲染(2-3 小时)** - 创建 `src/components/MenuItem.vue` - 修改 `DefaultLayout.vue` - 集成 Permission Store - 编写组件测试 **第 5 步:API 权限检查(1-2 小时)** - 创建 `src/utils/permission-check.ts` - 集成到请求拦截器 - 编写单元测试 - 优化性能 ### 5.2 后端 API 需求 **接口**: `GET /api/menus/user` **功能**: 获取当前登录用户可访问的菜单和权限 **业务逻辑**: 1. 从 token 获取用户 ID 2. 查询用户角色 3. 根据角色查询菜单和权限 4. 构建菜单树结构 5. 返回菜单和权限列表 **预估时间**: 7-12 小时 ## 6. 风险和约束 ### 6.1 技术风险 1. **后端 API 开发时间** - 需要后端配合开发新 API 2. **菜单数据迁移** - 需要将硬编码菜单迁移到数据库 3. **权限数据同步** - 前后端权限数据需要保持一致 ### 6.2 约束条件 1. **向后兼容** - 需要兼容现有的路由守卫逻辑 2. **性能要求** - 菜单加载不能影响页面首屏渲染速度 3. **测试覆盖** - 所有新增代码需要单元测试覆盖 ## 7. 验收标准 ### 7.1 功能验收 - [ ] Permission Store 正确管理权限数据 - [ ] v-permission 指令正确控制按钮显示 - [ ] 动态菜单根据用户权限正确渲染 - [ ] API 权限检查正确拦截无权限请求 ### 7.2 质量验收 - [ ] 所有单元测试通过 - [ ] 代码覆盖率 ≥ 80% - [ ] TypeScript 类型检查通过 - [ ] ESLint 检查通过 ### 7.3 性能验收 - [ ] 菜单加载时间 < 500ms - [ ] localStorage 读写不影响页面性能 - [ ] 权限检查不影响 API 请求速度 ## 8. 后续优化 ### 8.1 短期优化 1. **权限缓存过期** - 添加权限数据过期机制 2. **权限变更通知** - 实现权限变更后的实时通知 3. **权限日志** - 记录权限检查日志,便于调试 ### 8.2 长期优化 1. **权限可视化配置** - 提供权限配置界面 2. **权限审计** - 记录用户权限变更历史 3. **权限模板** - 提供常用权限模板,简化配置 ## 9. 参考资料 - [Vue 3 官方文档](https://vuejs.org/) - [Pinia 官方文档](https://pinia.vuejs.org/) - [Element Plus 文档](https://element-plus.org/) - [RBAC 权限模型](https://en.wikipedia.org/wiki/Role-based_access_control)