Vue3 实现 RBAC 权限管理

RBAC的基本概念

  • RBAC本质上就是一个授权的过程通过 用户 —> 角色---->资源

为啥要用RBAC

  • 一个系统中用户是非常多的,对于不同的用户,展示的内容也是不同的,如果为每一个用户都单独的设置权限那需要创造出多少提条数据,例如:一个系统有30个菜单,100个用户,一对一授权就是30 * 100 ,每一个菜单项基本有增删改查,那就是30 * 100 * 4用户越多产生的数据越多,后期增删改查统统要遍历所有的数据,设计是非常糟糕的,效率极低。
  • 若果采用RBAC 设计模式,引入了角色就会简单很多,10个用户先分为管理员和普通用户,通过角色去查对应的资源 ,那就是 30 * 4 *2,后期无论增加多少用户,只要不是增加角色种类数据就不会变。

RBAC的概述

  • 总的来说就是围绕着一句话通过用户的id查询对应的角色再查询对应的资源,从而绑定用户与资源之间的关系

RBAC数据库的设计

  • id 、crated_At、updated_at、deleted_at(逻辑删除用的) ,这些是非关联表的必备字段

  • 用户表 (储存用户信息的字段)

    • username
    • password
    • avatar
    • status(用户的状态)
    • phone
  • 角色表 (系统角色的信息)

    • name
    • status(角色状态)
    • remark(角色标识)
    • description(角色描述)
  • 资源表 (菜单表或者其他的一些系统资源,这里采用菜单作为资源)

    • name(菜单名称)
    • pid(资源关联成树形结构(目录为0,菜单为id、按钮为NULL))
    • enabled(是否启用)
    • grade(分组:0目录,1菜单,2按钮)
    • path(前端路由跳转的路径,路径要全)
    • component(前度端组件的名称(一般大写))
    • icon_cls(菜单的图标)、keep_alive(组件是否缓存)
    • require_auth(是否需要权限认证)
    • remark(菜单的唯一标识、用于后期的权限认证处理和动态路由以及动态菜单的关键字)
    • description(菜单的描述)
    • level (菜单的等级、菜单的排序(同一组菜单下的显示顺序))
  • 用户角色表(用户与角色多对多产生的关联表)

    • role_id (角色的id)
    • user_id (用户的id)
  • 角色资源表(角色与资源多对多产生的关联表)

    • rid (角色的id)
    • mid (菜单的id)
    • prim (权限码 对应的是菜单的remark字段) 这个字段的设计是方便我们获取权限码
      • 若不设置也可以通过关联菜单查到,但每次都要关联后端写起来太麻烦了,直接就设计下这里了

表的设计

用户表实例

角色表实例


菜单(资源)表

角色用户表实例

角色资源表实例

后端设计 egg.js

技术概要

  • egg.js 简单快速
  • sequelize Sequelize 是一个基于 promise 的 Node.js ORM
  • mysq 数据存储
  • jwt 生成token
  • svg-captcha 生成验证码

后端系统主要的架构

  • controller (用于数据的检验和放回结果)
  • service (结合sequelize 对数据的操作)
  • model (定义数据模型、完成表的映射关系)
  • router (定义路由)
  • middleware (中间件 权限的设置)
  • heeper (返回结果的统一处理)

sequelize 模型展示

  • 用户表
'use strict'
module.exports = (app) => {const { STRING, INTEGER, DATE, BOOLEAN } = app.Sequelizeconst User = app.model.define('users', //数据库里的对应表名{id: {type: INTEGER, // 类型primaryKey: true,autoIncrement: true, // 自增allowNull: true, // 允许为null 在插入数据的时候不用填id},username: STRING(30),password: INTEGER,avatar: STRING,phone: INTEGER,status: {type: BOOLEAN,defaultValue: true,},createdAt: DATE,updatedAt: DATE,deletedAt: DATE,},{tableName: 'users', //指定与数据库对应的表名称timestamps: true, // 启用自动更新时间})// associate 函数 设置模型之间的关联 User.associate = function () {// 多对多关联        关联的模型是RoleUser.belongsToMany(app.model.Role, {//   through 通过中间表RoleUser模型进行关联through: app.model.RoleUser,// 外键即 中间表RoleUser中与当前模型的关联字段userIdforeignKey: 'userId',// 外键 中间表RoleUser中与当Role模型的关联字段roleIdotherKey: 'roleId',})}return User
}
  • 角色表
'use strict'module.exports = (app) => {const { STRING, INTEGER, DATE, BOOLEAN } = app.Sequelizeconst Role = app.model.define('roles',{id: {type: INTEGER,autoIncrement: true,allowNull: true,primaryKey: true,},name: {type: STRING(30),primaryKey: true,},remark: STRING,description: INTEGER,status: {type: BOOLEAN,defaultValue: true,},createdAt: DATE,updatedAt: DATE,deletedAt: DATE,},{tableName: 'roles', //指定表名称timestamps: true,})Role.associate = function () {// 角色和用户是多对多Role.belongsToMany(app.model.User, {through: app.model.RoleUser,foreignKey: 'roleId',otherKey: 'userId',})// 角色和菜单是多对多Role.belongsToMany(app.model.Menu, {through: app.model.RoleMenu,foreignKey: 'rid',otherKey: 'mid',})}return Role
}
  • 资源表
'use strict'
module.exports = (app) => {const { STRING, INTEGER, BOOLEAN, DATE } = app.Sequelizeconst Menu = app.model.define('menu',{id: {type: INTEGER,primaryKey: true,allowNull: true,},name: STRING,pid: INTEGER,grade: INTEGER,url: STRING,orders: INTEGER,path: STRING,component: STRING,icon_cls: STRING,remark: INTEGER,description: STRING,level: INTEGER,keep_alive: {type: BOOLEAN,defaultValue: true,},require_auth: {type: BOOLEAN,defaultValue: true,},enabled: {type: BOOLEAN,defaultValue: true,},createdAt: DATE,updatedAt: DATE,deletedAt: DATE,},{timestamps: true,tableName: 'menu',})Menu.associate = function () {Menu.belongsToMany(app.model.Role, {through: app.model.RoleMenu,foreignKey: 'mid',otherKey: 'rid',})}return Menu
}
  • 用户角色表
'use strict'module.exports = (app) => {const { STRING, INTEGER, DATE } = app.Sequelizeconst RoleUser = app.model.define('role_user',{id: {type: INTEGER,primaryKey: true,autoIncrement: true,allowNull: true,},roleId: INTEGER,userId: INTEGER,},{tableName: 'role_user', timestamps: false,})return RoleUser
}
  • 角色资源表
'use strict'module.exports = (app) => {const { STRING, INTEGER, DATE } = app.Sequelizeconst RoleMenu = app.model.define('role_menu',{rid: INTEGER,mid: INTEGER,prim: INTEGER,},{tableName: 'role_menu', timestamps: false,})return RoleMenu
}

接口的设计

菜单接口
  • 根据当前登录用户的id 查询角色用户表得到用户对应的角色id(一个或多个)

  • 根据角色id 查询角色资源表 得到prim(对应菜单的权限编码)要去重(多个角色对应的菜单是可能存在重复的)

  • 考虑到vue3中移除了addRoutes不能进行批量的添加,就直接返回菜单数据(不转化为树形结构,由前端根据pid自行转化树结构)

    • 返回结果json展示

      {"code": 200,"msg": "成功","data": [{"id": 2,"name": "系统管理","pid": 0,"grade": 0,"url": null,"orders": 0,"path": "/","component": "","icon_cls": "fa fa-group","remark": "20","description": "RBAC核心管理","level": null,"keep_alive": true,"require_auth": true,"enabled": true,"createdAt": "2022-03-10 20:37:57","updatedAt": "2022-03-11 18:23:06","deletedAt": null},{"id": 3,"name": "文章管理","pid": 0,"grade": 0,"url": null,"orders": 0,"path": "/","component": "","icon_cls": "fa fa-group","remark": "30","description": "文章的管理功能,包括文章和分类管理","level": null,"keep_alive": true,"require_auth": true,"enabled": true,"createdAt": "2022-03-10 20:37:57","updatedAt": null,"deletedAt": null},{"id": 5,"name": "角色管理","pid": 2,"grade": 1,"url": null,"orders": 2,"path": "/home/role","component": "SysRole","icon_cls": null,"remark": "2010","description": "系统角色的增删改查等功能","level": null,"keep_alive": true,"require_auth": true,"enabled": true,"createdAt": "2022-03-10 20:37:57","updatedAt": null,"deletedAt": null},{"id": 6,"name": "角色管理添加","pid": null,"grade": 2,"url": null,"orders": 5,"path": null,"component": null,"icon_cls": null,"remark": "201001","description": null,"level": null,"keep_alive": true,"require_auth": true,"enabled": true,"createdAt": "2022-03-10 20:37:57","updatedAt": "2022-03-13 09:31:26","deletedAt": null},......],"inserCount": 0 // 批量插入才用到
      }
      
权限接口
  • 主要是返回当前用户的prim(权限码)的数据集合

    • 返回结果json展示

      {"code": 200,"msg": "成功","data": [20,2010,201001,201002,201003,201004,201005,201006,201007,2020,202001,202002,202003,202004,2030,203001,203002,203003,203004,203005,203006,203007,203008,30,3010,301001,301002,301003,301004,301005,301006,301007,3020,302001,302002,302003,302004,3030,302006,302005],"inserCount": 0
      }
      

前端设计 vue全家桶

路由设计

  • 静态路由设计 每个登录的用户都可以访问到
  • 动态路由设计 将菜单接口 转化为路由并动态添加进路由即可
静态路由代码
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import Login from '@/views/Login.vue'
// 公有路由列表
export const publicRouter = [{path: '/',redirect: '/home/userInfo',},{path: '/login',name: 'login',component: Login,},{path: '/home',name: 'home',component: Home,meta: {title: '系统首页',icon: 'fa fa-group',},children: [{path: '/home/userInfo',name: '个人中心',component: () => import('../views/user/UserInfo.vue'),meta: {title: '个人中心',icon: 'fa fa-group',},},],},
]
const router = createRouter({history: createWebHistory(),routes: publicRouter,
})export default router
动态路由和左侧菜单设计
  • 动态路由需要结合权限(路由守卫)以及借助vuex 数据的共享
  • 主要的思路是
    • 将请求回来的数据添加到路由,形成动态路由
    • 将请求回来的数据格式化成可以用element-plus菜单组件渲染的菜单格式
动态路由代码
 // 添加动态路由async routers(ctx) {const { data } = await getMenulist() // 获取菜单列表data.forEach((item) => {// home 为布局文件// 分类 0菜单标题  1路由(二级菜单) 2权限按钮 // 转成树形结构let whiteList = [1] if (whiteList.includes(item.grade)) {let op = {path: item.path,name: item.component,component: () => {if (item.component.startsWith('User')) {return import('@/views/user/' + item.component + '.vue')} else if (item.component.startsWith('Sys')) {return import('@/views/sys/' + item.component + '.vue')} else if (item.component.startsWith('Art')) {return import('@/views/art/' + item.component + '.vue')} else {return import('@/views/' + item.component + '.vue')}},meta: {title: item.name,},}// 通过循环一个一个添加到home中,因为是path是全路径所以可以直接添加到home的children:[]中router.addRoute('home', op)}})ctx.commit('setRouters', data)},
动态菜单添加
 // 形成左侧菜单结构menus(ctx) {const publicRuters = router.options.routes // 拿到公共的路由// 动态路由中请求回来的menu数据,后台已经过(权限)处理了,只包含当前登录用户可以访问的路径const data = ctx.state.routers let res = []let map = {}data.forEach((item) => {// 排除按钮,按钮不用添加到左侧菜单 if (item.grade != 2) {item.children = [] // 全部添加一个children属性// 将数据id和自己绑定形成{{1,{id:1,name:管理系统}},{2,{id:2,name:文章管理}}}map[item.id] = item if (item.pid === 0) {// 若果pid ===0 代表是目录全部放进res中res.push(item)} else {// 否则就是二级菜单: 把自己放到自己的父亲的 children数组中即可  map[item.pid].children.push(item)//**注意** 这种写法要求菜单在菜单表中的顺序:一定是父亲数据在自己儿子的上面,否则儿子会找不到父亲报错}}})const routerWhite = ['/', '/login', '/404', '/401', '/403'] // 设置白名单// 合并数据,公有的菜单中白名单中的数据是不需要展示到左侧菜单栏中的for (let i = 0; i < publicRuters.length; i++) {if (routerWhite.includes(publicRuters[i].path)) {// 找到就删除publicRuters.splice(i, 1)// i-- 不知道为什么这样会报错,数组总的数据会向前移动要i--才对,暂时不清楚}// 根据上面的bug 暂时这样写if (publicRuters[i].path == '/login') {publicRuters.splice(i, 1)}}// 合并成最终的菜单数据ctx.commit('setMenus', [...publicRuters, ...res])},},
路由守卫
import router from '@/router'
import store from '@/store'
import Nprogress from 'nprogress'
import 'nprogress/nprogress.css'
/*** 全局守卫*/
const whiteRouterPath = ['/login'] // 白名单
let falg = false // 标志(只执行一次添加路由操作)
router.beforeEach(async (to, from, next) => {Nprogress.start() // 加载进度条if (store.getters.token) {// 有token 代表用户登录了if (JSON.stringify(store.getters.userInfo) == '{}') {// 没有就获取用户信息await store.dispatch('user/getUserInfo')}// 登录后访问 '/login' 不允许直接跳转到首页if (to.path === '/login') {next('/') // 会重定向到 home组件页面} else {// 登录后访问其他路径 由于此时动态路由还未添加不能访问if (!falg) {falg = true// 触发添加动态路由的事件await store.dispatch('menu/routers')// 触发添加菜单的事件store.dispatch('menu/menus')// next({ ...to })必须要有,由于此时动态路由还未全部添加不能访问,//next()中如果有参数,会重新执行router.beforeEach(),//第二次执行router.beforeEach()的时候 flag为false 就不会在添加路由,直接走else 此时路由已经全部添加完成next({ ...to })} else {next()}}} else {// 没有token代表未登录 但访问路径在白名单中 放行if (whiteRouterPath.includes(to.path)) {next()} else {// 没有token代表未登录 访问后台路径 直接跳到登录next('/login')}}
})router.afterEach((to, from) => {// 关闭进度条Nprogress.done()
})
export default router

动态菜单小结

  • 这样就基本完成了权限管理的以菜单的部分了
  • 关于按钮的权限管理,就是通过自定义指令显示和隐藏按钮,权限码都拿到了还怕解决不了这个小问题
import store from '@/store'function checkPermission(el, binding) {// 获取绑定的值,此处为权限const { value } = binding// 获取所有的功能指令const points = store.getters.remarks // 获取到的权限码// 当传入的指令集为数组时if (value && value instanceof Array) {// 匹配对应的指令const hasPermission = points.some((point) => {return value.includes(point)  // 有就true })// 如果无法匹配,则表示当前用户无该指令,那么删除对应的功能按钮if (!hasPermission) {// 移除按钮el.parentNode && el.parentNode.removeChild(el)}} else {// 传入数据不合法throw new Error('v-permission value is ["302003"]')}
}export default {// 在绑定元素的父组件被挂载后调用mounted(el, binding) {checkPermission(el, binding)},// 在包含组件的 VNode 及其子组件的 VNode 更新后调用update(el, binding) {checkPermission(el, binding)},
}
 <el-buttontype="danger"v-permission="['302003']"  **和后台的权限码对应好即可,就相当与是v-if**size="small"icon="Delete"@click="del(scope.row)"
></el-button>

效果展示

  • 登录

  • 管理员登录

  • 权限管理

  • 用户管理

  • 普通用户登录

项目地址gitee: https://gitee.com/llvyr/rbac.git

接口文档 访问密码 : llvyr

https://www.apifox.cn/apidoc/shared-5d262e6f-ef1a-4e27-bdf9-9ccb87cf79bd

Vue3 实现 RBAC 权限管理相关推荐

  1. nest-mysql:RBAC权限管理

    文章问题导向 RBAC权限管理是什么?如何设计数据库?如何实现? 如果你都有了答案,可以忽略本文章,或去nest学习导图寻找更多答案 阅前必知 阅读此文,需要有一定的数据库知识 此文并非最佳实践,只能 ...

  2. RBAC权限管理设计思想

    RBAC权限管理设计 一.概述 二.权限模型 三.RBAC模型 什么是RBAC模型 基本模型RBAC0 角色分层模型RBAC1 角色限制模型RBAC2 统一模型RBAC3 基于RBAC的延展--用户组 ...

  3. RBAC权限管理设计

    RBAC权限管理设计 一.RBAC组成 1. RBAC 2. RBAC组成 3. RBAC支持的安全原则 4. RBAC的优缺点 二.RBAC权限分配 1. RBAC的功能模块 2. RBAC权限分配 ...

  4. php rbac实现,php实现rbac权限管理

    php实现rbac权限管理 介绍: RBAC(Role-Based Access Control)基于角色的权限管理方式. RBAC的最大特征就是将权限跟角色挂钩,用户又跟角色挂钩. 优点: ①管理维 ...

  5. rbac权限管理表mysql_RBAC权限管理数据库表小解

    TP2.0版本就已经支持扩展RBAC权限管理,也有对应的demo,Rbac权限管理在Examples目录下面. RBAC扩展库核心文件则可以在ThinkPHP/Lib/ORG/Util下面找到,查看源 ...

  6. Java Web RBAC权限管理

    前言 权限管理是在项目中经常要使用到的模块,有着极其重要的功能.比较出名的权限框架,分别为 Shiro 和 Spring Security,两者各有优缺,这次我们不用任何权限框架来实现 RBAC 权限 ...

  7. kubernetes(k8s)之rbac权限管理详解

    kubernetes(k8s)之rbac权限管理详解 RBAC简介 RBAC(Role-Based Access Control) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 ...

  8. php中rbac三张表的关系,thinkPHP的RBAC权限管理

    thinkPHP的RBAC权限管理 如何进行thinkPHP的rbac权限管理呢?下面是由百分网小编为大家整理的thinkPHP的RBAC权限管理,喜欢的可以收藏一下!了解更多详情资讯,请关注应届毕业 ...

  9. 在Egg.js中实现RBAC权限管理

    什么是RBAC? RBAC是基于角色的权限访问控制,在RBAC中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限,也就是说权限是和角色绑定在一起的. RBAC权限管理树形图 角色管理 ...

最新文章

  1. 数列分段pascal程序
  2. linux中php配置
  3. 通过path绘制点击区域
  4. cadence快捷键修改文件_PCB快捷键设置
  5. flink java生成流式数据
  6. 在weblogic12c中启动工程报错缺失BeanFactoryAware
  7. npm介绍及与Node.js关联
  8. 基于jquery响应式网站图片无限加载瀑布流布局
  9. map怎么转化dto_阿里面试:为什么Map桶中个数超过8才转为红黑树
  10. Centos6.5 安装 Mysql-5.6.41
  11. Vulnstack红日安全内网域渗透靶场1实战
  12. Redhat7.3修改密码
  13. nodejs返回带图片的HTML页面,Nodejs实现简单的网页图片获取
  14. 抖音服务器升级中无法修改名字,抖音名字改不了怎么回事
  15. Kafka配置SASL_SSL认证传输加密
  16. AI绘画网站最全收集!!
  17. ECharts之饼状图
  18. 给一个喝酒青年的公开状
  19. How to Pronounce PROBABLY
  20. 鬼吹灯之怒睛湘西 SDUT

热门文章

  1. 那么多人用破解版AD,Altium公司怎么赚钱
  2. H5网页播放器EasyPlayer.js也能做直播视频实时录像了
  3. 喜讯!OpenJWeb2.6快速开发平台开源版正式对外发布
  4. c语言桥牌游戏,桥牌游戏介绍
  5. element UI 组件封装--搜索表单(含插槽和内嵌组件)
  6. 公务员计算机类面试真题及答案,2019国家公务员考试面试真题及答案——现象类题...
  7. 易源数据_易源数据-网络热搜词排行【最新版】_数据API_数据应用_传媒-云市场-阿里云...
  8. codeigniter框架helpers,libraries,models目录说明
  9. Fl Studio 20.8英文切换中文正式版下载及新增功能介绍说明
  10. 银行家算法例题讲解_银行家算法