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

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

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

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. 滚动更新…

多进程编程

创建一对父子进程&#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、数据库视图 可以直接输入表名…

Linux 文件

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

【BUUCTF】[RoarCTF 2019]Easy Java1

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

....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;以及伪时间片多任务内核设…

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

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

景源畅信电商:抖音小店有哪些比较热门的宣传方法?

抖音小店的热门宣传方法&#xff0c;是许多商家关注的焦点。在数字化营销时代&#xff0c;有效的宣传手段不仅能提升品牌知名度&#xff0c;还能吸引潜在消费者&#xff0c;促进销售。以下是针对抖音小店热门宣传方法的详细阐述&#xff1a; 一、短视频内容营销 作为抖音的核心…

【进程等待】阻塞等待 | options非阻塞等待

目录 waitpid 阻塞等待 options&非阻塞等待 pid_t返回值 阻塞等待VS非阻塞等待 waitpid 回顾上篇&#xff1a; pid_ t waitpid(pid_t pid, int *status, int options); 返回值&#xff1a; 当正常返回的时候waitpid返回收集到的子进程的进程ID&#xff1b;如果设置了…

PSCA电源管理软件栈示例

安全之安全(security)博客目录导读 目录 1、移动通讯系统 2、基础设施系统 本博客就PSCA电源管理软件栈进行举例&#xff0c;主要以移动通讯系统和基础设施系统为例来说明。 1、移动通讯系统 图3.4显示了一个可以在基于Linux的移动设备中实现的电源管理堆栈示例。 在Linux…

使用WPF中的Trigger实现按钮样式动态更改

使用WPF中的Trigger实现按钮样式动态更改 在Windows Presentation Foundation (WPF)中&#xff0c;Trigger 是一种强大的机制&#xff0c;它可以基于控件的属性值来动态更改控件的样式。这篇博客将介绍如何使用Trigger实现按钮在鼠标悬停时样式动态更改的效果。我们将详细讨论为…

Faiss核心解析:提升推荐系统的利器【AI写作免费】

首先&#xff0c;这篇文章是基于笔尖AI写作进行文章创作的&#xff0c;喜欢的宝子&#xff0c;也可以去体验下&#xff0c;解放双手&#xff0c;上班直接摸鱼~ 按照惯例&#xff0c;先介绍下这款笔尖AI写作&#xff0c;宝子也可以直接下滑跳过看正文~ 笔尖Ai写作&#xff1a;…

STM32使用L9110驱动电机自制小风扇

1.1 介绍&#xff1a; 该电机控制模块采用L9110电机控制芯片。该芯片具有两个TTL/CMOS兼容输入端子&#xff0c;并具有抗干扰特性&#xff1a;具有高电流驱动能力&#xff0c;两个输出端子可直接驱动直流电机&#xff0c;每个输出端口可提供750800mA动态电流&#xff0c;其峰值…

【适用全主题】WordPress原创插件:弹窗通知插件 支持内容自定义

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 适用于所有WordPress主题的弹窗插件 一款WordPress原创插件&#xff1a;弹窗通知插件 支持内容自定义 二、效果展示 1.部分代码 代码如下&#xff08;示例&#xff09;&#xff1…