Vue 之 后台管理系统的权限路由的管理

目录

    • 前言
    • 实现
      • 理解三者的概念以及之间的关联
        • 账号(用户)
        • 角色
        • 菜单
      • 用户权限授权相关概念
      • 实现
        • 代码实现
          • 登录
          • 跳转路由,路由守卫中进行权限验证
          • 按钮权限
            • 封装指令:
            • 调用(其中一个页面参考)
        • 思路,操作流程

前言

一个完整的后台管理系统,往往有很多种角色,如超级管理员、业务模块的管理员、财务模块的管理员等等……
那么不同的管理员(角色)所能做的操作以及能看到的菜单应该是不一样的,比如一个商品的后台管理系统,业务模块的管理员需要维护一些商品的上下架,商品的促销等,财务模块的管理员并不需要对商品数据模块进行这些维护工作,这个时候就需要在财务角色的后台管理系统中隐藏那些功能……

实现

  • 账号(用户)?
  • 角色?
  • 菜单?
    实现之前我们先去理解上述的3个概念并找到他们之间的关联(这里我们用ruoyi项目为示例):
    在这里插入图片描述

理解三者的概念以及之间的关联

账号(用户)

这个好理解,后台管理系统等登录页面需要填写的那几个信息就是账号的相关信息:
账号:---------
密码:********
在这里插入图片描述
方便理解:

账号(用户)这个在现实生活中,可以理解为类似人的姓名,如张三、李四等。

角色

方便理解:

现实生活中,人有姓名,他也有身份,如母亲、父亲、老师等,不同的身份所要处理的事情、接触到的事物也不同,这个身份就可以看作是这个人的角色。
例如老师这个角色,任何一个人都可能是老师,你可以是,我也可以是,他也可以是,这个就需要这个人(姓名=账号(用户))选择(分配)的身份(角色)是什么……
在这里插入图片描述

菜单

方便理解:

前面讲角色的时候说到,不同的身份(角色)所要面对、处理的事物都有所不同,这个事物就可以简单理解为菜单
例如老师这个角色,老师面对的大部分都是学生、学校、书本,处理的事物大部分都是教书育人;
传授知识,这些(学生、学校、书本、教书育人、传授知识) ≈ 事物(菜单)
在这里插入图片描述

看到这里,对于这三个概念以及他们之间的关联应该有个大致的模糊的概念了:

这三个之间可以说没有关联,也可以说是环环相扣;

  • 没有关联?:给不同的账号(用户)分配不同的角色,在不同角色里面分配不同的菜单,这就是我们暂时看到的关联;
  • 环环相扣?:他们都在不同的页面(表)里面,可以有不同的维护逻辑。

用户权限授权相关概念

用户权限授权是对用户身份认证的细化。可简单理解为访问控制,在用户身份认证通过后,系统对用户访问菜单或按钮进行控制。也就是说,该用户有身份进入系统了,但他不一定能访问系统里的所有菜单或按钮,而他只能访问管理员给他分配的权限菜单或按钮。主要包括:

  • Permission(权限标识、权限字符串):针对系统访问资源的权限标识,如:用户添加、用户修改、用户删除。
  • Role (角色):可以理解为权限组,也就是说角色下可以访问和点击哪些菜单、访问哪些权限标识。

权限标识或权限字符串校验规则:

  • 权限字符串:指定权限串必须和菜单中的权限标识匹配才可访问;
  • 权限字符串命名规范为:模块:功能:操作,例如:system:user:edit;
  • 使用冒号分隔,对授权资源进行分类,如 system:user:edit 代表 系统模块:用户功能:编辑操作;
  • 设定的功能指定的权限字符串与当前用户的权限字符串进行匹配,若匹配成功说明当前用户有该功能权限;
  • 还可以使用简单的通配符,如 system:user:*,建议省略为 system:user(分离前端不能使用星号写法);
  • 举例1 system:user 将于 system:user 或 system:user: 开头的所有权限字符串匹配成功;
  • 举例2 system 将于 system 或 system: 开头的所有权限字符串匹配成功 这种命名格式的好处有:
    1. 可读性和可理解性:使用模块、功能和操作的格式可以直观地表达权限的含义。每个部分都有明确的作用,模块表示特定的模块或子系统,功能表示模块内的某个功能或页面,操作表示对功能进行的具体操作。通过这种格式,权限名称可以更容易地被开发人员、管理员和其他人员理解和解释。
    2. 可扩展性和灵活性: 通过使用模块、功能和操作的格式,可以轻松地扩展和管理权限。每个模块、功能和操作都可以被单独定义和控制。当系统需要增加新的功能或操作时,可以根据需要添加新的权限字符串,而不需要修改现有的权限规则和代码。
    3. 细粒度的权限控制: 这种格式支持细粒度的权限控制,可以针对特定的功能和操作进行权限管理。通过将权限名称拆分为模块、功能和操作,可以精确地定义哪些用户或角色具有访问或操作特定功能的权限。
    4. 避免权限冲突: 使用模块、功能和操作的格式可以避免权限之间的冲突。不同模块、功能和操作的权限名称是唯一的,这样可以避免同名权限之间的混淆和冲突。

实现

代码实现

接下来,以代码的形式来先走一下流程:

这里进行模块分类(使用vuex进行数据状态管理):用户信息的放在user.js里面,权限验证,构建路由放在premission.js;

登录

modules/store/user.js
登录成功后获取token

// 登录
Login({ commit }, userInfo) {const username = userInfo.username.trim()const password = userInfo.passwordconst code = userInfo.codeconst uuid = userInfo.uuidreturn new Promise((resolve, reject) => {login(username, password, code, uuid).then(res => {setToken(res.token)commit('SET_TOKEN', res.token)resolve()}).catch(error => {reject(error)})})
},

在这里插入图片描述

跳转路由,路由守卫中进行权限验证

获取用户信息

// 获取用户信息
GetInfo({ commit, state }) {return new Promise((resolve, reject) => {getInfo().then(res => {const user = res.userconst avatar = (user.avatar == "" || user.avatar == null) ? require("@/assets/images/profile.jpg") : process.env.VUE_APP_BASE_API + user.avatar;if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组commit('SET_ROLES', res.roles)commit('SET_PERMISSIONS', res.permissions)} else {commit('SET_ROLES', ['ROLE_DEFAULT'])}commit('SET_ID', user.userId)commit('SET_NAME', user.userName)commit('SET_AVATAR', avatar)resolve(res)}).catch(error => {reject(error)})})},

生成路由配置表
store/modules/premission.js

import auth from '@/plugins/auth'
import router, { constantRoutes, dynamicRoutes } from '@/router'
import { getRouters } from '@/api/menu'
import Layout from '@/layout/index'
import ParentView from '@/components/ParentView'
import InnerLink from '@/layout/components/InnerLink'const permission = {state: {routes: [],addRoutes: [],defaultRoutes: [],topbarRouters: [],sidebarRouters: []},mutations: {SET_ROUTES: (state, routes) => {state.addRoutes = routesstate.routes = constantRoutes.concat(routes)},SET_DEFAULT_ROUTES: (state, routes) => {state.defaultRoutes = constantRoutes.concat(routes)},SET_TOPBAR_ROUTES: (state, routes) => {state.topbarRouters = routes},SET_SIDEBAR_ROUTERS: (state, routes) => {state.sidebarRouters = routes},},actions: {// 生成路由GenerateRoutes({ commit }) {return new Promise(resolve => {// 向后端请求路由数据getRouters().then(res => {const sdata = JSON.parse(JSON.stringify(res.data))const rdata = JSON.parse(JSON.stringify(res.data))const sidebarRoutes = filterAsyncRouter(sdata)const rewriteRoutes = filterAsyncRouter(rdata, false, true)const asyncRoutes = filterDynamicRoutes(dynamicRoutes);rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })router.addRoutes(asyncRoutes);commit('SET_ROUTES', rewriteRoutes)commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))commit('SET_DEFAULT_ROUTES', sidebarRoutes)commit('SET_TOPBAR_ROUTES', sidebarRoutes)resolve(rewriteRoutes)})})}}
}// 遍历后台传来的路由字符串,转换为组件对象
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {return asyncRouterMap.filter(route => {if (type && route.children) {route.children = filterChildren(route.children)}if (route.component) {// Layout ParentView 组件特殊处理if (route.component === 'Layout') {route.component = Layout} else if (route.component === 'ParentView') {route.component = ParentView} else if (route.component === 'InnerLink') {route.component = InnerLink} else {route.component = loadView(route.component)}}if (route.children != null && route.children && route.children.length) {route.children = filterAsyncRouter(route.children, route, type)} else {delete route['children']delete route['redirect']}return true})
}function filterChildren(childrenMap, lastRouter = false) {var children = []childrenMap.forEach((el, index) => {if (el.children && el.children.length) {if (el.component === 'ParentView' && !lastRouter) {el.children.forEach(c => {c.path = el.path + '/' + c.pathif (c.children && c.children.length) {children = children.concat(filterChildren(c.children, c))return}children.push(c)})return}}if (lastRouter) {el.path = lastRouter.path + '/' + el.pathif (el.children && el.children.length) {children = children.concat(filterChildren(el.children, el))return}}children = children.concat(el)})return children
}// 动态路由遍历,验证是否具备权限
export function filterDynamicRoutes(routes) {const res = []routes.forEach(route => {if (route.permissions) {if (auth.hasPermiOr(route.permissions)) {res.push(route)}} else if (route.roles) {if (auth.hasRoleOr(route.roles)) {res.push(route)}}})return res
}export const loadView = (view) => {if (process.env.NODE_ENV === 'development') {return (resolve) => require([`@/views/${view}`], resolve)} else {// 使用 import 实现生产环境的路由懒加载return () => import(`@/views/${view}`)}
}export default permission

路由守卫premission.js

import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
import { isRelogin } from '@/utils/request'NProgress.configure({ showSpinner: false })
// 免登录白名单
const whiteList = ['/login', '/register']
// 路由守卫
router.beforeEach((to, from, next) => {NProgress.start()// 判断是否有tokenif (getToken()) {// 设置页面标题to.meta.title && store.dispatch('settings/setTitle', to.meta.title)/* has token*/if (to.path === '/login') {next({ path: '/' })NProgress.done()} else if (whiteList.indexOf(to.path) !== -1) {next()} else {// 判断是否拉取了用户信息,角色信息等信息if (store.getters.roles.length === 0) {// 未拉取user_info信息isRelogin.show = true// 调接口获取相关信息store.dispatch('GetInfo').then(() => {isRelogin.show = false// 获取路由表信息store.dispatch('GenerateRoutes').then(accessRoutes => {// 根据roles权限生成可访问的路由表router.addRoutes(accessRoutes) // 动态添加可访问路由表next({ ...to, replace: true }) // hack方法 确保addRoutes已完成})}).catch(err => {// 退出登录store.dispatch('LogOut').then(() => {Message.error(err)next({ path: '/' })})})} else {// 已拉取完user_info信息,直接进入下一步next()}}} else {// 没有tokenif (whiteList.indexOf(to.path) !== -1) {// 在免登录白名单,直接进入next()} else {next(`/login?redirect=${encodeURIComponent(to.fullPath)}`) // 否则全部重定向到登录页NProgress.done()}}
})router.afterEach(() => {NProgress.done()
})

按钮权限

前端按钮级权限,是指在前端界面中,根据用户的权限不同,对不同的按钮进行权限控制。这样做的目的是为了确保系统的安全性和数据的保密性,使得不同用户只能执行其有权执行的操作,从而避免潜在的安全风险。

大致主流方式有两种:

  • 条件渲染(Conditional Rendering): 这是一种简单有效的方法,通过在前端代码中根据用户的权限信息来决定是否渲染特定的按钮或组件。比如,你可以使用条件语句(如v-if等)来判断用户是否有权限,从而决定是否渲染按钮。
  • 指令/组件封装: 使用一些自定义指令或组件,可以封装权限控制逻辑。你可以创建一个自定义指令或组件,接受用户权限作为输入,然后根据权限来决定是否显示按钮。
封装指令:

/directive/premission/hasPermi.js:操作权限处理 - 包含指定权限方可显示调用

/*** v-hasPermi 操作权限处理* Copyright (c) 2019 ruoyi*/import store from '@/store'export default {inserted(el, binding, vnode) {const { value } = bindingconst all_permission = "*:*:*";const permissions = store.getters && store.getters.permissionsif (value && value instanceof Array && value.length > 0) {const permissionFlag = valueconst hasPermissions = permissions.some(permission => {return all_permission === permission || permissionFlag.includes(permission)})if (!hasPermissions) {el.parentNode && el.parentNode.removeChild(el)}} else {throw new Error(`请设置操作权限标签值`)}}
}

/directive/premission/hasRole.js:角色权限处理 - 包含指定角色方可显示调用

/*** v-hasRole 角色权限处理* Copyright (c) 2019 ruoyi*/import store from '@/store'export default {inserted(el, binding, vnode) {const { value } = bindingconst super_admin = "admin";const roles = store.getters && store.getters.rolesif (value && value instanceof Array && value.length > 0) {const roleFlag = valueconst hasRole = roles.some(role => {return super_admin === role || roleFlag.includes(role)})if (!hasRole) {el.parentNode && el.parentNode.removeChild(el)}} else {throw new Error(`请设置角色权限标签值"`)}}
}
调用(其中一个页面参考)

在这里插入图片描述

思路,操作流程
  1. 一个后台管理系统默认是有一个超级管理员的,这个超级管理员是有这个系统的所有权限,如果没有,那就直接在数据库insert 一个超级管理员;

  2. 然后这个超级管理员在账号(用户)列表里面创建的一个账号(用户);
    在这里插入图片描述

  3. 并给这个新创建的账号(用户)分配了一个角色(这个角色可以是之前就存在的角色,也可以是这个超级管理员刚刚创建的角色);
    在这里插入图片描述
    在这里插入图片描述

  4. 然后在角色列表里面,给这个角色分配不同的菜单;
    在这里插入图片描述

  5. 最后这个新创建的账号(用户)就可以进行登录;

  6. 用户登录接口,获取token;

  7. 跳转路由,在路由守卫中获取相应的信息(如:用户个人信息、角色,对应角色权限);

  8. 获取到用户、角色、对应角色权限的数据后,保存相关信息并进行前端路由动态渲染;

  9. 按钮权限,按钮权限这边同样是根据获取的信息中的角色权限信息来进行渲染(显示、隐藏);

这里有个需要注意的是:获取到对应的角色权限(也就是现在说的:权限验证、构建路由表)是在路由守卫中发生的。

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

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

相关文章

数学:矩阵范数的定义、常见的矩阵范数

1 算子范数【从属范数】 1.1 1-算子范数【列和范数】 :即对A的每列的绝对值求和再求其中的最大值 1.2 ∞-算子范数【行和范数】即对 A 的每行的绝对值求和再求其中的最大值 1.3 2-算子范数【谱范数】 学过奇异值分解就知道谱范数是最大奇异值/ 二次型的最大特…

大数据Spark教程从入门到精通第四篇:Spark快速上手

一:Spark快速上手 1:创建Maven项目 idea安装scala_idea scala插件-CSDN博客 代表了我们安装scala的maven环境已经准备好了,代码可以正常跑了

Git使用(4):分支管理

一、新建分支 首先选择Git -> Branches... 然后选择 New Branch,输入新分支名称,例如dev。 可以看到右下角显示已经切换到新建的dev分支了。 push到远程仓库,可以看到新添加的分支。 二、切换分支与合并分支 为了演示合并分支&#xff0c…

【MySQL数据库开发设计规范】之SQL使用规范

欢迎点开这篇文章,自我介绍一下哈,本人姑苏老陈 ,是一名JAVA开发老兵。 本文收录于 《MySQL数据库开发设计规范》专栏中,该专栏主要分享一些关于MySQL数据库开发设计相关的技术规范文章,定期更新,欢迎关注&…

Shell之高效文本处理命令

目录 一、排序命令—sort 基本语法 常用选项 二、去重命令—uniq 基本语法 常用选项 三、替换命令—tr 基本语法: 常用选项 四、裁剪命令—cut 基本语法: 常用选项 字符串分片 五、拆分命令—split 基本语法: 六、 文件…

NVM安装及VUE创建项目的N种方式

VUE 参考官网:https://cli.vuejs.org/zh/guide/ 目录 NVM安装 1.卸载node.js 2.安装nvm ​编辑​ 3.配置 4.使用nvm安装node.js 5.nvm常用命令 创建VUE项目 1.使用vue init 创建vue2(不推荐) 2.使用vue create创建vue2和3&#xff…

TINA 使用教程

常用功能 分析-电气规则检查:短路,断路等分析- 直流分析 交流分析 瞬态分析 视图-分离曲线 由于输出的容性负载导致的振荡 增加5欧电阻后OK 横扫参数 添加横扫曲线的电阻,选择R3:8K-20K PWL和WAV文件的支持 示例一:…

AWS简介

AWS AWS,全称为Amazon Web Services,是亚马逊公司旗下的云计算服务平台,自2006年起向全球用户提供广泛而深入的云计算服务。AWS是全球最全面、应用最广泛的云平台之一,它从全球的数据中心提供超过200项功能齐全的服务&#xff0c…

防泄密软件有哪些|2024年企业防泄密软件排行榜

在当今数字化时代,企业的信息安全问题愈发显得重要,尤其是随着网络技术的飞速发展,信息泄露和数据窃取的风险也日益增大。为了保障企业的核心机密和客户隐私,许多企业开始使用防泄密软件,以确保信息的安全性和完整性。…

【Linux 网络】网络基础(二)(应用层协议:HTTP、HTTPS)-- 详解

我们程序员写的一个个解决我们实际问题,满足我们日常需求的网络程序,都是在应用层。 前面写的套接字接口都是传输层经过对 UDP 和 TCP 数据发送能力的包装,以文件的形式呈现给我们,让我们可以进行应用层编程。换而言之&#xff0c…

MP3解码入门(基于libhelix)

主要参考资料: 【Arduino Linux】基于 Helix 解码库实现 MP3 音频播放: https://blog.csdn.net/weixin_42258222/article/details/122640413 libhelix-mp3: https://github.com/ultraembedded/libhelix-mp3/tree/master 目录 一、MP3文件二、MP3 解码库三、libhelix-mp3库3.1 …

Shell之(数组)

目录 一、shell数组 1.数组的定义 2.定义数组的方法 第一种 第二种 第三种 第四种 3.数组分片 4. 数组字符替换 临时替换 永久替换 5.删除数组 删除指定的下标 删除整组 6.数组遍历和重新定义 7.数组追加元素 方式一:指定位置添加 方法二&a…

【JavaScript】WeakMap 和 WeakSet

Map Map 用于存储键值对。 添加属性: 使用 Map 的 set() 方法可以向 Map 对象中添加键值对。例如: const map new Map(); map.set(key1, value1); map.set(key2, value2);通过二维数组快速创建 map 键值对。 let arr [[1, 2],[2, 3],[3, 4]]let map …

计算机Java项目|Springboot高校心理教育辅导设计与实现

作者主页:编程指南针 作者简介:Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容:Java项目、Python项目、前端项目、人工智能与大数据、简…

yolov9训练自定义数据

1.训练yolov9,先准备好一份自定义数据.。到roboflow下载一份数据,数据格式是yolo格式。 2.到github下载yolov9源码 https://github.com/WongKinYiu/yolov9 3.为了方便配置环境,把代码上传到矩池云上面,使用云服务器 4.执行 pip i…

JSP+SQL学生成绩管理系统

Java版本:1.8 数据库:MySQL 框架:Spring Spring MVC MyBatis 服务器:Tomcat 前端解析框架:Thymeleaf 开发工具:Idea 2017 版本管理工具:Maven 版本控制工具:GitHub 经过对系统的需…

解决kali Linux安装后如何将语言修改为中文

开启虚拟机 用root用户进入终端 进入终端执行dpkg-reconfigure locales命令 选择en_US.UTF-8 UTF-8选项,按空格键将其取消。 选择zh_CN.UTF-8 UTP-8,按空格选择,按tab键选择ok。 选择zh_CN.UTF-8字符编码,按tab键选择ok&#xff0…

jQuery EasyUI textbox 值取不到问题

用textbox 存值点击修改的时候有些字段是不能更改的所以将textbox 的disabled属性设为true后 像这里的textbox disabled属性设为true是灰的 点击保存时这两个值没传 我们可以在提交保存前先将disabled属性先设为false,保存后又设为true

简单记录下:Navicat 导出表结构至 Excel

首先我们需要通过sql语句查询出相关的表结构的结构 SELECT COLUMN_NAME AS 字段名称,COLUMN_TYPE AS 字段类型,IF(IS_NULLABLENO,否,是) AS 是否必填,COLUMN_COMMENT AS 注释FROM INFORMATION_SCHEMA.COLUMNSWHERE table_schema bs-gdsAND table_name sys_menu;查询的结构如下…

软考-软件工程

软件工程概述 软件工程指的是应用计算机科学、数学及管理科学等原理,以工程化的原则和方法来解决软件 问题的工程,目的是提高软件生产率、提高软件质量、降低软件成本。 概述: 软件开发模型:指导软件开发的体系 需求分析确定软件…