RBAC权限控制实现方案

news2024/9/22 4:53:02

上一文章讲述了利用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.token
  if (token) {
    // 如果有token
    if (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-item
          v-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.$route
      const { meta, path } = route
      // if set path, the sidebar will highlight the path you set
      if (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.coloradmin.cn/o/1437004.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL数据引擎、建库及账号管理

目录 一、MySQL数据库引擎 1.1.MySQL常见数据库引擎 1.InnoDB(MySQL默认引擎) 2.MyISAM 3.MEMORY&#xff08;Heap&#xff09; 1.2.存储引擎查看 二、建库 1.默认数据库介绍 2.建库 3.查看数据库 4.删除数据库 三、账号管理 1.创建用户 1.创建用户并设置登陆密码…

【电路笔记】-互感

互感 文章目录 互感1、概述2、互感3、耦合系数4、互感示例15、互感示例2 互感是一个线圈磁场与另一个线圈的相互作用&#xff0c;因为它在相邻线圈中感应出电压。 1、概述 互感是两个磁耦合线圈之间的电路参数&#xff0c;定义了一个线圈产生的时变磁通量被感应到相邻的第二个…

python coding with ChatGPT 打卡第17天| 二叉树:找树左下角的值、路径总和

相关推荐 python coding with ChatGPT 打卡第12天| 二叉树&#xff1a;理论基础 python coding with ChatGPT 打卡第13天| 二叉树的深度优先遍历 python coding with ChatGPT 打卡第14天| 二叉树的广度优先遍历 python coding with ChatGPT 打卡第15天| 二叉树&#xff1a;翻转…

Cpp-2

类与对象 /*类与对象&#xff1a;1.类是一种用户自定义的数据类型&#xff08;函数&#xff0c;数据&#xff09;2.类是具有相同的属性和行为的对象的集合3.类是对象的抽象&#xff0c;对象是类的具体4.对象&#xff1a;通过使用类类型定义的变量 */定义类 /*如何定义类&…

LabVIEW汽车自燃监测预警系统

LabVIEW汽车自燃监测预警系统 随着汽车行业的飞速发展&#xff0c;汽车安全问题日益受到公众的关注。其中&#xff0c;汽车自燃现象因其突发性和破坏性&#xff0c;成为一个不可忽视的安全隐患。为了有效预防和减少自燃事故的发生&#xff0c;提出了LabVIEW的汽车自燃监测预警…

ES节点故障的容错方案

ES节点故障的容错方案 1. es启动加载逻辑1.1 segment和translg组成和分析1.2 es节点启动流程1.3 es集群的初始化和启动过程 2. master高可用2.1 选主逻辑2.1.1 过滤选主的节点列表2.1.2 Bully算法2.1.2 类Raft协议2.1.3 元数据合并 2.2 HA切换 3. 分片高可用3.1 集群分片汇报3.…

前端常用代码整理(不断更新中)— js,jquery篇

1.随机函数代码 function getRandom(min, max) {return Math.floor(Math.random() * (max - min 1)) min}2.倒计时代码 let now new Date()// 2. 得到指定时间的时间戳let last new Date(这里写想要达到的时间)// 3. &#xff08;计算剩余的毫秒数&#xff09; / 1000 剩余…

ChinaXiv:中科院科技论文预发布平台

文章目录 Main彩蛋 Main 主页&#xff1a;https://chinaxiv.org/home.htm 彩蛋

11.0 Zookeeper watcher 事件机制原理剖析

zookeeper 的 watcher 机制&#xff0c;可以分为四个过程&#xff1a; 客户端注册 watcher。服务端处理 watcher。服务端触发 watcher 事件。客户端回调 watcher。 其中客户端注册 watcher 有三种方式&#xff0c;调用客户端 API 可以分别通过 getData、exists、getChildren …

vue3 使用defineAsyncComponent 动态加载组件

问题场景 在项目中使用静态加载组件基本能覆盖80%的场景了&#xff0c;如下图 但是我们在需要 循环生成一些的component 的时候或者在 开发ssr服务端渲染的页面 就会遇到有些组件以静态方式导入就会报错&#xff0c;导致进程失败&#xff0c;那么这时候就需要用到动态组件。那…

智能运维适合哪些行业?智能运维需要哪些设备?

构建一个智能运维系统所必须的设备和工具包括&#xff1a; 硬件设备&#xff1a; 服务器&#xff1a;用于部署智能运维平台和存储数据。 网络设备&#xff1a;包括路由器、交换机等&#xff0c;用于连接各种设备和系统。 存储设备&#xff1a;如硬盘、SSD等&am…

时序预测 | Matlab实现基于LSTM长短期记忆神经网络的电力负荷预测模型

文章目录 效果一览文章概述源码设计参考资料效果一览 文章概述 时序预测 | Matlab实现基于LSTM长短期记忆神经网络的电力负荷预测模型 LSTM(长短期记忆)是一种递归神经网络(RNN)的变体,它在序列数据建模方面表现出色。电力负荷预测是一项重要的任务,可以利用LSTM神经网络…

Adb显示第3方应用的包名原理

Android早期版本实现原理请看 Android源码分析-pm命令的实现&#xff0c;列出包名pm list package&#xff0c;列出系统库pm list libraries_pm list packages-CSDN博客 Android12 对adb shell pm 实现原理做了重构&#xff1a;改成了template模式PackageManagerShellCommand …

如何在 Java 中通过 Map.Entry 访问 Map 的元素

我们使用 Map.Entry 来遍历 ConcurrentHashMap 的代码片段如下&#xff1a; for (Map.Entry<String, String> entry : map.entrySet()) { System.out.println("Key: " entry.getKey() ", Value: " entry.getValue()); } 在 Map.java 中&…

Java20:新特性

一&#xff1a;Lambda表达式&#xff1a; 1. Lambda表达式使用前后对比&#xff1a; 举类一&#xff1a; Testpublic void test(){ Runnable r1 new Runnable() {Overridepublic void run() {System.out.println("我爱北京天安门&#xff01;");} };r1.run();Syst…

华为机考入门python3--(11)牛客11-数字颠倒

分类&#xff1a;字符串 知识点&#xff1a; int转字符串 str int(num) 对字符串进行逆序 my_str str[::-1] 题目来自【牛客】 def reverse_integer(n): # 将整数转换为字符串 str_n str(n) # 使用[::-1]来反转字符串 reversed_str str_n[::-1] return reversed…

FINN: 使用神经网络对网络流进行指纹识别

文章信息 论文题目&#xff1a;FINN: Fingerprinting Network Flows using Neural Networks 期刊&#xff08;会议&#xff09;&#xff1a;Annual Computer Security Applications Conference 时间&#xff1a;2021 级别&#xff1a;CCF B 文章链接&#xff1a;https://dl.ac…

BioTech - 小分子药物设计与优化 概述

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/135998902 小分子药物设计与优化&#xff0c;是利用计算机辅助技术&#xff0c;根据特定的生物学靶点&#xff0c;发现和改进具有治疗作用的小分子…

Codeforces Round 920 (Div. 3)(A~F)

文章目录 ABCDEF A 按题意模拟即可 #include <bits/stdc.h> #define int long long #define rep(i,a,b) for(int i (a); i < (b); i) #define fep(i,a,b) for(int i (a); i > (b); --i) #define pii pair<int, int> #define pll pair<long long, long…

JavaEE作业-实验二

目录 1 实验内容 2 实验要求 3 思路 4 核心代码 5 实验结果 1 实验内容 实现两个整数求和的WEB程序 2 实验要求 ①采用SpringMVC框架实现 ②数据传送到WEB界面采用JSON方式 3 思路 ①创建一个SpringMVC项目&#xff0c;配置好相关的依赖和配置文件。 ②创建一个Con…