2023-11-13 20:40:00
想看完整的 权限管理系统 实现,可以查看个人项目:vue3-ting-admin
一、RBAC 权限设计
1、基本概述
RBAC (Role-Based Access Control) 是一种基于角色的访问控制策略。
简单地说,一个用户拥有多个角色,一个角色拥有多个权限。管理员只需要管理角色的权限,而不必直接管理每个用户的权限
这样,就构造成 “用户 -> 角色 -> 权限” 的授权模型。
从模块角度来讲,可以分为以下 RBAC 模块:
1 | 系统管理 |
2、数据库设计
在 RBAC 模型中,用户与角色之间、角色与权限之间,通常都是多对多的关系。
上述只是一个简单的数据库设计思路(具体的话会根据不同的业务变得复杂)。
3、数据交互
下面简单说一下流程:
方式 1(精准版):
- 前端发起用户登录请求 ——> 后端返回该用户的 token、userID、用户信息 等
- 前端可以通过 userID + token 获取角色信息请求 ——> 后端返回该用户的 详细信息(包含 roleID) 等
- 前端可以通过 roleID + token 获取菜单信息请求 ——> 后端返回该用户的 菜单树(扁平、树形) 等
方式 2(暴力版):
- 前端发起用户登录请求 ——> 后端返回该用户的 token、userID、roleID、用户信息、菜单树 等
4、动态路由方案
① 基于角色
- 后端:只提供角色 role 信息即可
- 前端:根据 role 匹配指定的 静态路由组合即可
为了防止产生大量的重复数据,通常采用在路由的 mate 字段上加角色数组(例如:vue-element-admin)
基于角色的动态路由方案并不能实现真正意义上的动态,只是前端的固有设置罢了。
🌟 代码实现🌟
- 后端
根据 userId 获取用户的详细信息
1 | { |
- 前端
菜单和路由的生成,由前端自己在 meta 字段定义所需数据即可:
title、icon 进行视图渲染,roles 进行路由匹配(具体实现可以参考:vue-element-admin)
1 | export default { |
② 基于菜单
- 方案 1:后端同时提供 path 和 component,
前端需要将数据强行转换为路由结构,但麻烦是的 component: () => import()组件的路径生成,并且组件名由后端提供,前端开发受阻。
代码实现(强制转换 ✖)
1 | { |
1 | // 1、菜单对象转路由对象 |
- 方案 2:后端只提供 path
手动映射(path 映射 Route ✔)
1 | { |
1 | // 优点:组件命名自由了 |
自动映射(path 映射 Route ✔)
这个就是我采用的最终方案,下面的教程采用的就这个方案。
提前准备好静态路由,并提前和后端沟通好,保持前端本地的路由 path 值,和后端传过来的 path 属性值一致。
二、菜单数据
1、菜单树
菜单数据中必须有下面几个基础属性,必须和后端沟通好:
| nav-aside 视图渲染 | main-contain 路由匹配 |
|---|---|
| 标识 id | 路由地址 url |
| 图标 icon | |
| 名称 name |
下面给出菜单数据的示例结构:
如果不是树形结构,我们自己根据 parentId 结合递归转换为树结构即可。
🌟 菜单数据示例🌟
1 | { |
2、视图渲染 ✨
我们可以通过上面的菜单数据的 id、icon、name 属性,先渲染右侧菜单栏视图。
下列的菜单栏设计:必须有子菜单,且只有一个。(可以根据项目需求进一步优化)
🌟 右侧菜单栏视图🌟
1 | <template> |
3、静态路由
不同的静态路由处理方式,可能会产生不同的动态路由方案
🌟 静态路由示例🌟
- 基本路由
1 | const router = createRouter({ |
- 其他路由
放在合理的位置,然后后期根据用户的菜单树映射并注册指定路由即可
4、菜单-路由映射 ✨
接下来我们可以利用菜单数据的 url 属性 进行 vue-router 路由的动态注册 :
将后端传来的 url(path)与路由的 path 进行匹配,前提是要与后端沟通好 path 路径的构成
制作 initDynamicRoutes 函数 🎈
- utils / map-menus.ts
1 | import type { RouteRecordRaw } from 'vue-router' |
- utils / initDynamicRoutes.ts
1 | import { mapMenusToRoutes } from './map-menus.ts' |
此时,我们可以在需要生成动态路由的代码段中调用 initDynamicRoutes 函数为项目注册动态路由。比如:
- 用户登录时
- 页面刷新时
1 | // @/store/login/login.ts |
5、页面刷新处理
页面刷新时,需要在合适的地方,路由缓存的菜单树重新生成 注册动态路由:
- main.ts
1 | import { createApp } from 'vue' |
- @/global/register-pinia.ts
1 | import type { App } from 'vue' |
三、权限操作控制
1、基本介绍
页面级权限控制简单的说是按钮的权限控制,从根本上讲是进行增删改查请求的访问控制。
权限操作设计也是有一定的讲究的,本教程采用的是RuoYi-Vue3的权限操作方案。
1 | // 解析:当前用户在 /main/system/menu 路由组件页面中用户查询(query)权限 |
按钮权限 数据
其实,关于权限操作控制的 permission 字段,后端在用户的菜单树中已经提供了:
1 | { |
也有后端是这样传的,其想法就是在进行动态路由生成时,顺便将下列信息放到路由的 meta 中备用。
1 | { |
2、权限数组
首先我们得根据 菜单树 映射出一个当前角色所拥有的 按钮权限数组:
- utils / map-menus.ts
1 | // 将userRoleMenu树 映射为 permission数组 |
3、权限控制
获取了权限数组后,我们可以可以利用该数组实现权限控制了,具体的实现流行有以下两种方案:
- 使用 vue 自定义指令(倾向于 按钮权限控制)
- 使用自己封装的 hook(倾向于 请求权限控制)
① 自定义指令 方案
- 全局 permissions 指令封装
1 | // 详见个人项目:https://github.com/Lencamo/vue3-ting-admin/tree/main/src/directives |
- v-permissions 指令使用示例
1 | <template> |
② 封装 hook 方案 👀
- @/hooks/usePermissions.ts
1 | import useLoginStore from '@/store/login/login' |
- 具体使用
1 | <template> |
四、权限与位运算
我们可以将&、|、^ 位运算符应用于权限控制中:
这种方式可以极大的简化 按钮级别 的权限控制效率(后端只需要传一个数字即可)。
1 | const CREATE = 1 // 0001 |
测试代码
1 | const CREATE = 1 // 0001 |
1、权限组合
有 1 则 1
- 按位或 |
1 | // 权限组合 | |
2、权限解读
同为 1 则 1
- 按位与 &
1 | // 权限解读 & |
3、权限操作
同则 0,异则 1
- 按位异或 ^
1 | // 权限操作 ^ (异或:有就去掉,没有就加上) |