权限系统
系统采用经典的 RBAC(Role-Based Access Control) 模型,实现细粒度的权限控制。
权限模型
用户 (User) ←多对多→ 角色 (Role) ←多对多→ 菜单/按钮 (Menu/Btn)三者关系:
- 用户:系统的操作主体,一个用户可拥有多个角色
- 角色:权限的载体,一个角色可绑定多个菜单和按钮权限
- 菜单/按钮:具体的资源,菜单控制页面访问,按钮控制操作权限
权限控制流程
1. 用户登录 → 获取角色列表
2. 角色列表 → 获取菜单/按钮权限
3. 前端根据权限 → 动态生成路由和渲染按钮
4. 后端接口 → Guard 验证权限后端实现
JWT 认证
所有接口默认需要 JWT Token 认证(使用 JwtAuthGuard)。
使用 @Public() 装饰器可以跳过认证:
typescript
@Public()
@Get('captcha')
getCaptcha() {
return this.authService.getCaptcha();
}权限守卫
使用 @RequirePermissions() 装饰器保护接口:
typescript
@Get(':id')
@RequirePermissions('system:user:query')
async findOne(@Param('id') id: string) {
return this.userService.findOne(id);
}只有拥有 system:user:query 权限标识的角色才能访问该接口。
装饰器总结
| 装饰器 | 说明 |
|---|---|
@Public() | 跳过 JWT 认证 |
@RequirePermissions('xxx') | 要求指定权限 |
@Action('xxx') | 记录操作日志 |
@User() | 获取当前登录用户 |
@Original() | 返回原始数据,不经过响应拦截器包装 |
前端实现
动态路由
登录后前端调用 /auth/routes 获取当前用户的菜单列表,动态生成路由:
typescript
// 根据后端返回的菜单数据生成路由
const routes = menuListToRoutes(menus)
routes.forEach(route => router.addRoute(route))v-auth 指令
使用 v-auth 指令控制按钮的显示/隐藏:
vue
<template>
<el-button v-auth="'system:user:add'" type="primary">新增</el-button>
<el-button v-auth="'system:user:edit'" type="warning">编辑</el-button>
<el-button v-auth="'system:user:delete'" type="danger">删除</el-button>
</template>如果用户没有对应的按钮权限,该元素会被自动隐藏。
权限判断
也可以在代码中手动判断权限:
typescript
const { permissions } = useUserStore()
if (permissions.includes('system:user:delete')) {
// 有删除权限
}权限标识规范
推荐使用 模块:资源:操作 的格式:
system:user:query # 查询用户
system:user:add # 新增用户
system:user:edit # 编辑用户
system:user:delete # 删除用户
system:user:export # 导出用户
system:role:query # 查询角色
system:role:add # 新增角色
...API 接口
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /auth/login | 用户登录 |
| GET | /auth/captcha | 获取验证码 |
| GET | /auth/routes | 获取用户菜单路由 |
| GET | /auth/allPermissions | 获取所有权限列表 |
| GET | /auth/userInfo | 获取当前用户信息 |
| POST | /auth/logout | 用户登出 |