系统权限控制插件封装-实现系统权限控制插件化

        背景:按照传统的开发方式方式,每次新开发一个系统,就需要花费大量时间精力去搭建权限控制模块,如果我们把权限控制这一整个模块都抽离成一个独立的权限控制插件,支持单命令安装,全面暴露参数与方法,就可以通过配置快速集成完整的权限控制机制。

        意义:便于集成与扩展,提高项目构建速度,减少重复代码,降低工作量。提高开发效率,减少因人工手动搭建导致的不必要的错误。

vivien-permission插件

        这是一个基于后台管理系统中的路由菜单权限控制系统,通过 vue-router 全局控制后台管理系统的菜单权限。

功能

① 能支持单点登录、 Token 维护与路由权限判断
② 提供灵活的配置选项,满足用户个性化需求

使用文档

        该插件的源代码及其使用文档均放在该仓库中。

GitHub - yoguoer/vivien-permissionContribute to yoguoer/vivien-permission development by creating an account on GitHub.icon-default.png?t=N7T8https://github.com/yoguoer/vivien-permission.git

实现原理

        页面/菜单权限实现思路

1、后端权限管理配置

  • 后台系统维护侧边栏目录的配置,包括目录名称、图标、链接等。

  • 后端接口能够返回侧边栏的树形结构数据,这些数据应该包含每个菜单项对应的路由地址和权限标识。

2、前端路由配置

  • 前端项目中定义好静态路由和动态路由的配置。

  • 静态路由通常是那些不需要权限即可访问的页面,如登录页、404页面等。动态路由则是根据用户角色和权限来动态生成的路由。

3、路由匹配与生成

  • 调用后端接口获取侧边栏树形结构数据。前端通过递归遍历后端返回的树形结构数据,并与前端配置的路由进行匹配。

  • 对于匹配成功的路由,将其加入到异步路由表中。

4、路由表整合

  • 将动态生成的异步路由表和静态的常规路由表进行整合。

  • 确保整合后的路由表是完整的,并且按照正确的顺序排列。

5、生成侧边栏菜单

  • 根据整合后的路由表,生成侧边栏菜单的DOM结构。

  • 侧边栏菜单应该包含所有用户有权限访问的菜单项。对于没有权限访问的菜单项,应该进行隐藏或者显示为不可点击状态。

6、路由守卫与权限校验

  • 在前端实现路由守卫,对用户的访问进行权限校验。

  • 当用户尝试访问某个页面时,检查该用户是否具有访问该页面的权限。如果没有权限,则重定向到无权限页面或提示用户。

7、缓存与性能优化

  • 对于一些不经常变动的侧边栏数据,可以考虑使用缓存来提高性能。

  • 在用户登录成功后,可以将侧边栏数据缓存起来,避免重复请求后端接口。

        实现之前,需要先知道一些前置知识,有利于更好地理解。

http://t.csdnimg.cn/4zkwQicon-default.png?t=N7T8http://t.csdnimg.cn/4zkwQ

核心片段

1、登录成功后,获取到token和用户信息,进行存储,然后跳转首页

// 登录方法
const login = async function (params: any) {try {//添加 try catch 捕获异常await userStore.Login(params);await userStore.GetUserInfo();routerNext();} catch (err) {console.error(err);}
};
接着,进行路由跳转到首页
const routerNext = function () {if (router.currentRoute.value.query.redirect) { //如果重新登陆后需要返回原先的路由地址router.push(router.currentRoute.value.query.redirect as string);} else {router.push({ name: "TV_FDS_LIST" });}
};

2、在后台权限管理系统根据侧边栏目录配置侧边栏和菜单、前端项目代码配置路由

 3、后端接口返回用户有权限访问的路由表和拥有的权限列表

4、 递归匹配后端路由和前端路由配置,添加路由异步路由表和常规路由表,形成最终的路由表

  • 递归后端接口返回的信息获取用户权限列表的方法:
/*** 获取嵌套对象的所有对象的 key 对应 value值* @param {*} data 嵌套对象* @param {*} arr 存放属性数组* @param {*} children 保存嵌套子对象的属性* @param {*} key 获取的 value 对应的 key* @returns*/
export function getChildValue(data: Array<T> = [],arr: Array<T> = [],key: string = '',children: string = 'children'
) {if (!key || data.length <= 0) returndata.forEach(item => {if (item[children]) {getChildValue(item.children, arr, key, children)}arr.push(item[key])})
}
    // 获取用户权限列表async GetAuthority(getAuthList: Function, domain: string): Promise<T> {try {if (!getAuthList || typeof getAuthList !== "function") {return Error("getAuthList 参数错误")}const authority: authorityType = {menuNames: [], // 菜单权限名称列表rule: [],// 按钮级别权限}/***请求获取路由权限列表,返回对象:{menuNames: [], // 菜单权限名称列表rule: [],// 按钮级别权限}*/const data = await getAuthList({token: getToken()})authority.menuNames = data.menuNamesauthority.rule = data.rulethis.SetAuthority(authority);return authority} catch (error) {this.ClearLocal(domain);return null;}},
  • 前端匹配生成路由的方法:
    // 生成异步路由GenerateRoutes(routesMenuNames: Array<RouteItem>, asyncRoutes: AppRouteModule[], basicRoutes: AppRouteModule[]) {// 过滤常量路由:过滤没有权限的异步路由filterRoutes(basicRoutes, routesMenuNames)// 过滤异步路由:过滤没有权限的异步路由filterRoutes(asyncRoutes, routesMenuNames)this.SetRoutes(asyncRoutes, basicRoutes)return asyncRoutes},
  • 过滤路由的方法:
/*** Filter asynchronous routing tables by recursion* 过滤没有权限的常量路由路由:递归前端路由,查找 name 不存在的路由,删除* @param routes asyncRoutes* @param roles*/
export function filterRoutes(routesInstans: Array<T>, routesMenuNames: Array<T>): void {// 开发环境侧边栏路由不由后端管理系统控制// if (process.env.NODE_ENV === envEnum.DEVELOPMENT) return// 测试和生产环境下,对常量路由进行过滤for (let i = 0; i < routesInstans.length; i++) {const route = routesInstans[i]if (route.children) {filterRoutes(route.children, routesMenuNames)}if (routesMenuNames && routesMenuNames.length > 0 && (!route?.hidden)) {route.hidden = (routesMenuNames.indexOf(route.name) < 0)}}
}
  • 整合路由表的方法:
    // 设置所有路由SetRoutes(asyncFilterRoutes: Array<T>, constantAsyncRoutes: Array<T>) {this.routes = constantAsyncRoutes.concat(asyncFilterRoutes).sort((value1: RouteItem, value2: RouteItem) => value1?.order - value2?.order) //所有路由this.addRoutes = asyncFilterRoutes //新增异步路由获取后台管理系统路由(前台未设置权限页面,因此异步路由即为后台管理路由)},

5、根据生成的路由表设置侧边栏菜单

    // 设置侧边栏路由SetRoute(routes: Array<RouteItem>) {this.routes = routes},
  • 点击某一个主菜单,生成对应侧边栏菜单的方法:
/*** 设置二级菜单显示的路由* @param {} param0* @param {*} routes 当前路由对象,包含路由名称 name 或则路由路径* @returns*/SetShowRouters(routes: RouteItem) {const { name, matched } = routeslet topRouteName = name // 二级路由顶部菜单栏名称if (matched && matched.length > 0) { // 根据路由匹配路径获取二级顶部菜单栏名称topRouteName = matched[0].name}const filterRouter = this.routes.map((item: RouteItem) => {if (item.name !== topRouteName) {item.hidden = true} else {item.hidden = false}return item})this.SetRoute(filterRouter)return routes}

6、当进行路由跳转时,路由守卫先判断token,没有token且路由地址也不在路由白名单内,就让用户跳转到登录页重新登陆拿token;如果有token,就需要对用户权限进行校验。


import type { Router, RouteItem } from 'vue-router';
import { getToken as toGetToken, getOAToken } from "@/utils/token";
import { routesStoreWithOut } from "@/store/routes";
import { useUserStoreWithOut } from "@/store/user";
import type { AppRouteModule } from "@/types/router";
import { Message as showMsg } from '@/plugin/Message.ts';const routeStore = routesStoreWithOut();
const userStore = useUserStoreWithOut();export async function createPermissionGuard(router: Router,whiteList: string[],asyncRoutes: AppRouteModule[],basicRoutes: AppRouteModule[],getAuthList: Function,checkOaLogin: Function,domain: string,Message: Function
) {/*** 问题: 直接使用 router.beforeEach 会导致在刷新页面时无法进入 router.beforeEach 的回调函数* 原因:可能是因为在刷新页面时,Vue Router 的初始化过程尚未完成,导致路由守卫无法正常触发。* 解决方案:将 router.beforeEach 回调函数的逻辑放在一个异步函数中,并在 Vue Router 初始化完成后再调用这个异步函数。你可以使用 router.isReady() 方法来判断 Vue Router 是否已经初始化完成。* isReady: isReady(): Promise<void> 返回一个 Promise,它会在路由器完成初始导航之后被解析,也就是说这时所有和初始路由有关联的异步入口钩子和异步组件都已经被解析。如果初始导航已经发生,则该 Promise 会被立刻解析。*/router.isReady().then(() => {router.beforeEach(async (to: any, from: any, next: Function) => {// 判断用户是否已经登录,已经登录情况下,进入权限判断if (toGetToken()) {return await routerPermission(to, from, next, whiteList, asyncRoutes, basicRoutes, getAuthList, domain, Message)} else {// 兼容oa 系统单点登录,获取 oa 中的 tokenconst { oaToken } = getOAToken(domain)// oa 存在 token,用户已经登录 oaif (oaToken) {try {// 使用 oa token 换取当前系统的 token, 登录系统await userStore.CheckOaLogin(checkOaLogin, domain);return next();} catch (err) {userStore.ClearLocal(domain);return next("/login?redirect=" + to.path);}// 用户未登录, 判断是否进入白名单页面路由} else if (whiteList.includes(to.name as string)) {return next();} else {return next("/login?redirect=" + to.path);}}});});}/*** 路由权限判断函数,根据路由权限进入不同路由*/
export async function routerPermission(to: RouteItem,from: RouteItem,next: Function,whiteList: string[],asyncRoutes: AppRouteModule[],basicRoutes: AppRouteModule[],getAuthList: Function,domain: string,Message: Function
) {// 已经存在 token, 进入用户登录页面if (to.path == '/login' && from) {// 从登录页面进入,直接进入登录页面if (from.path === '/login' || '/') {return next();} else {//已经存在 token, 从其他页面进入用户登录页面,直接返回来源页面return next(from.path);}} else {// 获取是否用户权限const canAccess = await canUserAccess(to, whiteList, asyncRoutes, basicRoutes, getAuthList, domain)if (canAccess) {return next()} else {if (Message) {Message({message: "您没有权限访问页面,请联系系统管理员!",type: "warning",});} else {showMsg.error({message: "您没有权限访问页面,请联系系统管理员!",});}return false}}
}/**
* 获取异步权限
* @param to 
* @returns 
*/
export async function canUserAccess(to: RouteItem,whiteList: string[],asyncRoutes: AppRouteModule[],basicRoutes: AppRouteModule[],getAuthList: Function,domain: string
) {if (!to || to?.name === "Login") return falsetry {let accessRoutes = userStore.getAuthority || {}if (accessRoutes?.menuNames && accessRoutes?.menuNames?.length === 0) {// 获取用户异步路由权限accessRoutes = await userStore.GetAuthority(getAuthList, domain)// 生成用户所有路由权限routeStore.GenerateRoutes(accessRoutes?.menuNames || [], asyncRoutes, basicRoutes)}const allRoutes = [...whiteList, ...accessRoutes?.menuNames]return allRoutes.length > 0 && allRoutes.includes(to.name)} catch (err) {userStore.Logout(domain)return false}}

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

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

相关文章

k8s 理论知识基本介绍

目录 一 k8s 理论前言 &#xff08;一&#xff09;微服务是什么 1&#xff0c;应用场景 2&#xff0c;API 是什么 &#xff08;二&#xff09;&#xff0c;微服务 如何做版本迭代 1. Docker镜像构建 2. 版本标记 3. Docker Registry 4. 环境一致性 5. 滚动更新…

美国海军部发布《海军科学与技术战略》

文章目录 前言一、战略目标二、美海军部科学与技术战略总体归纳(一)强化海上统治地位1、实现更快的技术收益2、应用颠覆性技术3、发挥海军部战略优势4、完善试验和兵棋推演机制(二)建立卓越战斗文化1、打造集合作战人员、科学家和工程师的团队2、强化合作和建设能力3、科学…

多进程编程

创建一对父子进程&#xff1a; 父进程负责向文件中写入 长方形的长和宽 子进程负责读取文件中的长宽信息后&#xff0c;计算长方形的面积 代码&#xff1a; #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #inc…

串口通信---了解

1 串口接线方式 RXD&#xff1a;数据输入引脚&#xff0c;数据接受&#xff1b;STC89系列对应P3.0口 TXD&#xff1a;数据发送引脚&#xff0c;数据发送&#xff1b;STC89系列对应P3.1口 接线方式 串口编程要素 输入/输出数据缓冲器叫做SBUF&#xff0c;都用99H地址码&#x…

Java文件与IO操作

1. 文件与IO操作 1.1 文件 什么是文件: 文件,对我们并不陌生,文件是保存数据的地方,比如大家经常使用的word文档,txt文件.excel文件...都是文件。它既可以保存一张图片,也可以保持视频,声音.… 1.1.1 文件流: 1.1.2 常用的文件操作: 创建文件对象相关构造器和方法: 案例&a…

NeRF算法

目录 算法介绍 基本原理 1. 体渲染 2. 多层感知机&#xff08;MLP&#xff09; 3. 位置编码 4. 两阶段层次化体采样 实验展示 代码解析 算法介绍 NeRF&#xff08;Neural Radiance Fields&#xff09;是一种用于从2D图像中重建3D场景的神经网络模型。它通过训练一个深度…

SAP-ABAP-视图

1、什么是视图&#xff1f; 当需要查询多个表中的某些字段的数据时&#xff0c;就可以使用视图。视图不影响数据库中的数据&#xff0c;仅作为查询手段或工具。 2、视图类型&#xff1a; 数据库视图和维护视图经常使用。 3、创建视图SE11 3.1、数据库视图 可以直接输入表名…

VS中Halcon环境配置

环境配置步骤&#xff1a; 1、项目->属性-> C/C 常规 ->附加包含目录->添加include和include\halconcpp目录 目录如下&#xff1a;&#xff08;在你halcon的安装目录下找到include和include\halconcpp目录&#xff09; D:\HALCON\HALCON-22.11-Progress\include…

万能知识付费系统,为什么培训机构一直年年招老师?

培训机构年年招老师&#xff0c;说明机构的老师流失率大&#xff0c;稳定性不强。为什么稳定性不强&#xff0c;小认为主要有以下几个原因&#xff1a; 1、新入职机构的老师流动性是最大的&#xff0c;一方面&#xff0c;刚进入这个行业&#xff0c;对行业高强度工作不适应&…

【面试八股总结】C++11新特性:智能指针

参考资料 &#xff1a;阿秀、代码随想录 智能指针是一个类&#xff0c;用于存储指向动态分配对象的指针&#xff0c;负责自动释放动态分配的对象&#xff0c;防止堆内存泄露。动态分配的资源&#xff0c;交给一个类对象去管理&#xff0c;当类对象声明周期结束时&#xff0c;自…

Linux 文件

文章目录 文件操作回顾(C/C)系统调用接口 管理文件认识一切皆文件C/C的文件操作函数与系统调用接口的关系……重定向与缓冲区 -- 认识重定向与缓冲区 -- 理解使用重定向缓冲区实现一个简单的Shell(加上重定向)标准输出和标准错误(在重定向下的意义) 磁盘文件磁盘存储文件操作系…

Redis大key问题

Big Key就是某个key对应的value很大&#xff0c;占用的redis空间大&#xff0c;本质上是大value问题。比如用String类型的Key存放大体积二进制文件型数据用List数据结构保存热点新闻的评论列表&#xff0c;因为评论数爆发增长导致存放的元素多&#xff0c;value过大。 大key带来…

【BUUCTF】[RoarCTF 2019]Easy Java1

工具&#xff1a;hackbar发包&#xff0c;bp抓包。 解题步骤&#xff1a;【该网站有时候send不了数据&#xff0c;只能销毁靶机重试】 这里的登录界面是个天坑【迷魂弹】 直接点击help&#xff0c;然后进行打开hackbar——通过post请求&#xff0c;再通过bp抓包&#xff0c;…

Java Collections.emptyList() 方法详解

前言 在Java开发的日常中&#xff0c;我们常常需要处理集合数据结构&#xff0c;而这其中就免不了要面对“空集合”的场景。传统的做法可能是直接返回 null&#xff0c;但这往往会引入空指针异常的风险&#xff0c;降低了代码的健壮性。幸运的是&#xff0c;Java为我们提供了一…

....comic科学....食用手册....

1.点击链接后&#xff0c;保存漫画至夸克网盘&#xff0c;若是新用户需要用手机注册. 2.在应用商店下载夸克APP. 3.登录APP下载已保存的漫画. 3.1 进入APP点击 夸克网盘 3.2 点击“转存的内容”后&#xff0c;长按 漫画文件夹&#xff0c;点击下载&#xff0c;下载速度400K左…

桥田汉诺威工业展观察:走好脚下更需着眼未来

2024年4月21日&#xff0c;桥田创始人刘小平携核心团队6人共赴“制造业展会天花板”——德国汉诺威工业博览会参观学习&#xff0c;此次参访&#xff0c;是桥田智能组队出海的第二次学习之旅&#xff0c;未来&#xff0c;我们将组织更多优秀员工出海交流学习&#xff0c;让每一…

一套C语言VC + MSSQL开发PACS系统源码 带三维重建和还原的PACS医学影像全套系统源码

一套C语言VC MSSQL开发PACS系统源码 带三维重建和还原的PACS医学影像全套系统源码 本套PACS系统成品源码&#xff0c;自主版权。集成三维影像后处理功能&#xff0c;包括三维多平面重建、三维容积重建、三维表面重建、三维虚拟内窥镜、最大/小密度投影、心脏动脉钙化分析等功…

03-单片机商业项目编程,从零搭建低功耗系统设计

一、本文内容 上一节《02-单片机商业项目编程&#xff0c;从零搭建低功耗系统设计-CSDN博客》引出了伪时间片的概念&#xff0c;这也是再低功耗系统设计中必须使用的程序设计逻辑&#xff0c;本文着重来讲解如何利用伪时间片来设计伪多任务&#xff0c;以及伪时间片多任务内核设…

【信息系统项目管理师知识点速记】沟通管理基础

项目沟通管理是确保及时、正确地产生、收集、分发、存储和最终处理项目信息所需的过程。它包括制定沟通策略以确保沟通对干系人行之有效&#xff0c;以及执行必要活动以落实沟通策略。 14.1 管理基础 14.1.1 沟通 沟通是指用各种可能的方式来发送或接收信息&#xff0c;包括…

Abp框架,EF 生成迁移文件时,自动添加表和字段注释内容

在使用 abp 框架&#xff0c;或者ef 的时候都会遇到一个问题&#xff0c;就是建实体后要将实体描述生成到数据库中&#xff0c;就需要手动去添加 [Comment("注释内容")] 注解&#xff0c;这样相当于手动写两次注释&#xff08;即使你是 Ctrl C&#xff09;&#x…