RBAC权限控制实现方案

上一文章讲述了利用RBAC实现访问控制的思路(RBAC实现思路),本文主要详细讲解利用vuex实现RBAC权限控制。

一、准备工作

        从后台获取到权限对照表,如下:

1、添加/编辑楼宇    park:building:add_edit
2、楼宇管理    park:building:list
3、删除楼宇    park:building:remove
4、添加/编辑企业    park:enterprise:add_edit
5、企业管理    park:enterprise:list
6、查看企业详情    park:enterprise:query
7、删除企业    park:enterprise:remove

        释义: “parking:rule:add_edit”--- parking是一级菜单,rule是二级子菜单,add_edit是页面上的添加或编辑按钮 

        注:管理员权限为:*:*:*

二、通过调用接口获取用户权限 

        在判断是否登录成功的路由守卫页面中,调用获取权限的接口,将结果存到vuex中。

        路由守卫内容如下:

router.beforeEach(async (to, from, next) => {const token = store.state.user.tokenif (token) {// 如果有tokenif (to.path === '/login') {// 如果有token,且访问登录页面,则跳转至首页next('/')} else {// 有token,访问其他路径,直接放行next()// 获取权限信息,存在vuex中const permission = await store.dispatch('menu/getPermission')}}
})

        vuex中的内容如下:

// 导入获取权限的接口
import { getProfileAPI } from '@/api/user'
import { routes, resetRouter } from '@/router/index'
export default {namespaced: true,state: {// 权限标识permission: [],},mutations: {// 修改权限标识setPermissions (state, newPermission) {state.permission = newPermission},},actions: {async getPermission (store) {// 获取用户权限信息const { data: res } = await getProfileAPI()store.commit('setPermissions', res.permissions)return res.permissions}}
}

        接口返回数据如下:

         

 三、处理数据,得到一级路由标识和二级路由标识

        在路由守卫中的方法获取到权限数据后,再通过过滤、去重等方法,得到一级路由标识和二级路由标识。具体方法如下:

// 筛选一级路由
function getFirstPermission (permission) {const firstArr = permission.map(item => {return item.split(':')[0]})// 去重return Array.from(new Set(firstArr))
}// 筛选二级路由
function getSecondPermission (permission) {const secondArr = permission.map(item => {const arr = item.split(':')return `${arr[0]}:${arr[1]}`})// 去重return Array.from(new Set(secondArr))
}{next()// 获取权限信息,存在vuex中const permission = await store.dispatch('menu/getPermission')// 根据权限标识,筛选一级路由const firstPermission = getFirstPermission(permission)// 根据权限标识,筛选二级路由const secondPermission = getSecondPermission(permission)
}

        筛选后的一级路由标识和二级路由标识如下:

         

四、设置动态路由数组 

        将路由分为静态路由数组和动态路由数组,静态路由数组是由无权限控制的页面,如/login、/404等静态页面组成,动态路由数组是由有权限控制的页面,如/park、/parking等页面组成。

        router下的文件夹目录结构如下,index.js是静态路由数组,asyncRoutes.js是动态路由数组。

        

        index.js的内容如下:

import Vue from 'vue'
import Router from 'vue-router'Vue.use(Router)/* Layout */
import Layout from '@/layout'export const routes = [{path: '/login',component: () => import('@/views/Login/index'),hidden: true},{path: '/404',component: () => import('@/views/404'),hidden: true}
]const createRouter = () =>new Router({mode: 'history',scrollBehavior: () => ({ y: 0 }),routes: routes})const router = createRouter()// 重置路由方法
export function resetRouter () {// 得到一个全新的router实例对象const newRouter = createRouter()// 使用新的路由记录覆盖掉老的路由记录router.matcher = newRouter.matcher
}export default router

        asyncRoutes.js的内容如下:

import Layout from '@/layout'// 1. 动态路由: 需要做权限控制 可以根据不同的权限 数量上的变化
// 2. 静态路由: 不需要做权限控制 每一个用户都可以看到 初始化的时候初始化一次// 动态路由表
export default [{path: '/park',component: Layout,permission: 'park',meta: { title: '园区管理', icon: 'el-icon-office-building' },children: [{path: 'enterprise',permission: 'park:enterprise',meta: { title: '企业管理' },component: () => import('@/views/Park/Enterprise/index')}]},{path: '/parking',component: Layout,permission: 'parking',meta: { title: '行车管理', icon: 'el-icon-guide' },children: [{path: 'card',permission: 'parking:card',component: () => import('@/views/Car/CarCard'),meta: { title: '月卡管理' }},{path: 'rule',permission: 'parking:rule',component: () => import('@/views/Car/CarRule'),meta: { title: '计费规则管理' }}]},
]

 五、根据路由标识过滤原始动态路由表,得到用户对应的动态路由表

        根据第三步得到的一级路由标识和二级路由标识 ,对动态路由数组进行筛选,得到最终的动态路由表。

// 根据一级路由和二级路由,筛选可展示的动态路由
function getRoutes (firstPermission, secondPermission, asyncRoutes) {// 如果是管理员用户,就不用进行筛选if (firstPermission.includes('*')) {return asyncRoutes}const firstRoutes = asyncRoutes.filter(item =>firstPermission.includes(item.permission))const routes = firstRoutes.map(item => {return {...item,children: item.children.filter(child =>secondPermission.includes(child.permission))}})return routes
}{// 根据权限标识,筛选一级路由const firstPermission = getFirstPermission(permission)console.log('firstPermission', firstPermission)// 根据权限标识,筛选二级路由const secondPermission = getSecondPermission(permission)console.log('secondPermission', secondPermission)// 根据标识筛选路由const routes = getRoutes(firstPermission, secondPermission, asyncRoutes)console.log(routes)
}

        最终动态路由表如下:

         

六、将动态路由表渲染到页面 

        将上一步得到的动态路由表添加至路由对象中,从而实现通过链接跳转;再将其存在vuex中,已达到页面左侧菜单的渲染。

{// 根据标识筛选路由const routes = getRoutes(firstPermission, secondPermission, asyncRoutes)console.log(routes)// 筛选的路由渲染到左侧// 把筛选后的路由添加到路由对象中可以跳转routes.forEach(route => router.addRoute(route))// 再把筛选后的路由添加到vuex中(渲染)store.commit('menu/setMenuList', routes)
}

        左侧菜单完整内容如下: 

<template><div class="has-logo"><logo /><el-scrollbar wrap-class="scrollbar-wrapper"><!-- 左侧菜单组件 --><el-menu:default-active="activeMenu"mode="vertical":collapse-transition="false":unique-opened="true"><!-- 菜单中的每一项 --><sidebar-itemv-for="route in routes":key="route.path":item="route":base-path="route.path"/></el-menu></el-scrollbar></div>
</template><script>
import Logo from './Logo'
import SidebarItem from './SidebarItem'
export default {components: { SidebarItem, Logo },computed: {routes() {// 左侧菜单的渲染是通过this.$router.options.routes实现的// 权限标识和路由规则进行对比// this.$router.options.routes 不是相应式的// 只能取创建路由对象时传入的路由规则,后续通过addRoute添加的路由规则,是获取不到的return this.$store.state.menu.menuList},activeMenu() {const route = this.$routeconst { meta, path } = route// if set path, the sidebar will highlight the path you setif (meta.activeMenu) {return meta.activeMenu}return path}}
}
</script>

 七、使用自定义指令实现页面按钮的显示和隐藏

         1、新建directive/index.js文件,创建全局指令,实现按钮的显示和隐藏,具体内容如下:

// 放置全局指令
import Vue from 'vue'
import store from '@/store'
// 管理员权限
const adminPerms = '*:*:*'
Vue.directive('permission', {// el 使用自定义指令的dom元素// binding 对象,binding.value 可以接受外部传过来的值inserted (el, binding) {const perms = store.state.menu.permission// 管理员账号单独处理if (perms.includes(adminPerms)) {return}if (!perms.includes(binding.value)) {// 隐藏el// display的设置,可以通过开发者工具修改,建议使用remove// el.style.display = 'none'el.remove()}}
})

        2、在main.js中注册全局指令

// 导入自定义指令
import '@/directive'

        3、在页面中使用全局指令

<el-button v-permission="'park:rent:add_surrender'" size="mini" type="text" @click="addRent(scope.row.id)">添加合同</el-button>
<el-button v-permission="'park:enterprise:query'" size="mini" type="text" @click="$router.push(`/enterprise/${scope.row.id}`)">查看</el-button>
<el-button v-permission="'park:enterprise:add_edit'" size="mini" type="text" @click="edit(scope.row.id)">编辑</el-button>
<el-button v-permission="'park:enterprise:remove'" size="mini" type="text" @click="deleteEnterprise(scope.row.id)">删除</el-button>

八、退出时清空路由规则 

        在退出时,对数据进行清除,以防下次无权限用户登录时,自动跳转。 

// 退出登录
logout() {this.$store.commit('user/clearToken')this.$store.commit('menu/clearMenuList')this.$router.push(`/login?redirect=${this.$route.fullPath}`)
}

        store的方法如下:

import { resetRouter } from '@/router/index'
// 清空路由规则
clearMenuList (state) {state.menuList = []resetRouter()
}

        router的方法如下:

// 重置路由方法
export function resetRouter () {// 得到一个全新的router实例对象const newRouter = createRouter()// 使用新的路由记录覆盖掉老的路由记录router.matcher = newRouter.matcher
}

九、完整项目可参考以下项目

 智慧园区(利用RBAC实现权限控制)

 

 

         

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/465456.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

测试编码规范

0.测试代码和业务代码要分离 把测试代码和业务代码放进各自的所属的"盒子"中&#xff0c;互不干扰 Q:为什么要分离? 分门别类&#xff0c;避免混乱&#xff0c;方便维护 不在试卷上打草稿而是专门准备草稿纸 没人会在客厅做饭吧&#xff0c;不然要厨房干什么 Q:如…

BootstrapBlazor 模板适配移动设备使用笔记

项目模板 Bootstrap Blazor App 模板 为了方便大家利用这套组件快速搭建项目&#xff0c;作者制作了 项目模板&#xff08;Project Templates&#xff09;&#xff0c;使用 dotnet new 命令行模式&#xff0c;使用步骤如下&#xff1a; 安装项目模板 dotnet new install Boo…

第四节 zookeeper集群与分布式锁

目录 1. Zookeeper集群操作 1.1 客户端操作zk集群 1.2 模拟集群异常操作 1.3 curate客户端连接zookeeper集群 2. Zookeeper实战案例 2.1 创建项目引入依赖 2.2 获取zk客户端对象 2.3 常用API 2.4 客户端向服务端写入数据流程 2.5 服务器动态上下线、客户端动态监听 2…

寒假 day10

1、请使用递归实现n! #include<stdio.h> #include<string.h> #include<stdlib.h>int fun(int m) {if(m0)return 1;else{return m*fun(m-1);} } int main(int argc, const char *argv[]) {int m;printf("please enter m:");scanf("%d",…

linux 08 文件查找

02. 第一. alias&#xff1a;起别名(可以输入别名就可以执行对应的命令)&#xff0c;语法&#xff1a;alias 别名‘ls -l’ 第二. locate&#xff1a; locate 找不到最近的文件 更新locate 后 find命令&#xff1a; find&#xff1a; find 路径 选项 文件名&#x…

ZigBee学习——在官方例程实现组网

✨Z-Stack版本&#xff1a;3.0.2 ✨IAR版本&#xff1a;10.10.1 ✨这篇博客是在善学坊BDB组网实验的基础上进行完善&#xff0c;并指出实现的过程中会出现的各种各样的问题&#xff01; 善学坊教程地址&#xff1a; ZigBee3.0 BDB组网实验 文章目录 一、基础工程选择二、可能遇…

C++入门学习(二十七)跳转语句—continue语句

当在循环中遇到continue语句时&#xff0c;它会跳过当前迭代剩余的代码块&#xff0c;并立即开始下一次迭代。这意味着continue语句用于跳过循环中特定的执行步骤&#xff0c;而不是完全终止循环。 直接看一下下面的代码更清晰&#xff1a; 与上一节的break语句可以做一下对比…

CentOS 7.9安装Tesla M4驱动、CUDA和cuDNN

正文共&#xff1a;1333 字 21 图&#xff0c;预估阅读时间&#xff1a;2 分钟 上次我们在Windows上尝试用Tesla M4配置深度学习环境&#xff08;TensorFlow识别GPU难道就这么难吗&#xff1f;还是我的GPU有问题&#xff1f;&#xff09;&#xff0c;但是失败了。考虑到Windows…

python+vue+django体育场地器材预约管理系统dyn9h

技术栈 后端&#xff1a;python 前端&#xff1a;vue.jselementui 框架&#xff1a;django Python版本&#xff1a;python3.7 数据库&#xff1a;mysql5.7 数据库工具&#xff1a;Navicat 开发软件&#xff1a;PyCharm .体育馆管理系统有管理员和用户两个角色。用户功能有场地…

算法沉淀——位运算(leetcode真题剖析)

算法沉淀——位运算 常用位运算总结1.基础位运算2.确定一个数中第x位是0还是13.将一个数的第x位改成14.将一个数的第x位改成05.位图6.提取一个数最右边的17.删掉一个数最右边的18.异或运算9.基础例题 力扣题目讲解01.面试题 01.01. 判定字符是否唯一02.丢失的数字03.两整数之和…

给定具体日期 返回给定日期是星期几 calendar.weekday(year,month,day)

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 给定具体日期 返回给定日期是星期几 calendar.weekday(year,month,day) [太阳]选择题 如果2024年2月12日是星期一&#xff0c;请问最后一个print语句的运行结果是&#xff1f; import calenda…

【Linux】信号保存与信号捕捉处理

信号保存与信号捕捉 一、信号保存1. 信号的发送2. 理解信号保存&#xff08;1&#xff09;信号保存原因&#xff08;2&#xff09;信号保存概念 3. 信号保存系统接口&#xff08;1&#xff09;sigset_t&#xff08;2&#xff09;sigprocmask()&#xff08;3&#xff09;sigpend…