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

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

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

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.rhkb.cn/news/325615.html

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

相关文章

OpenCV下载安装教程(Windows)

一、什么是OpenCV OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个广泛使用的开源计算机视觉库&#xff0c;旨在提供丰富的图像和视频处理功能。它最初由Intel于1999年开发&#xff0c;并演变成为一个全球性的开源项目&#xff0c;得到了众多开发者…

K8s源码分析(二)-K8s调度队列介绍

本文首发在个人博客上&#xff0c;欢迎来踩&#xff01; 本次分析参考的K8s版本是 文章目录 调度队列简介调度队列源代码分析队列初始化QueuedPodInfo元素介绍ActiveQ源代码介绍UnschedulableQ源代码介绍**BackoffQ**源代码介绍队列弹出待调度的Pod队列增加新的待调度的Podpod调…

cmd输入mysql -u root -p无法启动

问题分析&#xff1a;cmd输入mysql -u root -p无法启动 解决方法&#xff1a;配置系统环境变量 1.找到mysql安装文件下的bin文件&#xff1a;&#xff08;复制改文件地址,如下图所示&#xff09; 2.电脑桌面下方直接搜索环境变量并进入&#xff0c;如下图 3.点击环境变量&a…

Python 中的 Lambda 函数:简单、快速、高效

大家好&#xff0c;今天再给大家介绍一个python的一个强大工具Lambda 函数&#xff0c;它允许你快速定义简单的匿名函数。这种函数是“匿名的”&#xff0c;因为它们不需要像常规函数那样被明确命名。 在本文中&#xff0c;我们将通过清晰的解释和实用的示例&#xff0c;深入了…

GoF之代理模式(静态代理+动态代理(JDK动态代理+CGLIB动态代理带有一步一步详细步骤))

1. GoF之代理模式&#xff08;静态代理动态代理(JDK动态代理CGLIB动态代理带有一步一步详细步骤)&#xff09; 文章目录 1. GoF之代理模式&#xff08;静态代理动态代理(JDK动态代理CGLIB动态代理带有一步一步详细步骤)&#xff09;每博一文案2. 代理模式的理解3. 静态代理4. 动…

打印图形(C语言)

一、N-S流程图&#xff1b; 二、运行结果&#xff1b; 三、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>int main() {//初始化变量值&#xff1b;int i, j;//循环打印&#xff1b;for (i 0; i < 5; i){//列&#xff1b;for (j 0; j &…

网络端口占用问题的综合调研与解决方案

原创 Randy 拍码场 问题背景 去年底信息安全团队进行网络权限治理&#xff0c;要求所有应用实例使用静态IP&#xff0c;公网访问策略与静态IP绑定&#xff1b;之后实例重启时偶现“端口被占用”错误。通过分析总结应用日志&#xff0c;共有以下4种错误类型&#xff0c;实质都是…

1-02-02:虚拟化与容器化Docker环境搭建

1.02.02 虚拟化与容器化Docker环境搭建 一. 虚拟化与容器化技术简介1. 虚拟机环境2. docker环境 二. Docker 架构与隔离机制2.1 Docker 架构2.2 Docker 隔离机制2.3 资源限制2.4 Docker应用场景 三. 实战:Docker在Centos7安装与镜像加速 ❤❤❤3.1 docker安装3.2 设置镜像加速 …

AI回答总不满意?你的提问方式可能完全错误!

大家好&#xff0c;我是卷福同学&#xff0c;一个专注AI大模型整活的前阿里程序员&#xff0c;腾讯云社区2023新秀突破作者 向AI提问想写一篇论文&#xff0c;结果AI就生成2000字左右的文章后就完了。小伙伴们是不是也会遇到这类情况呢。今天来教大家AI提示词的技巧&#xff0c…

Kubernetes基础理论介绍

前言 随着企业数字化转型的深入&#xff0c;为云而生的云原生架构和思想已被大量企业所接受。容器云、微服务、DevOps、 Serverless 已成为企业落地云原生的关键技术&#xff0c;而 Kubernetes 作为容器云的核心基础和事实标准&#xff0c;已成为当今互联网企业和传统 IT 企业…

DHCP原理

什么是DHCP DHCP (Dynamic Host Configuration Protocol,动态主机配置协议&#xff09;是由Internet工作任务小组设计开发的&#xff0c;专门用于为TCP/IP网络中的计算机自动分配TCP/IP参数的协议&#xff0c;是一个应用层协议&#xff0c;使用UDP的67和68端口。 DHCP的前身是B…

发布GPT-5的方式可能会与以往不同;开源vocode使用 AI 自动拨打电话;开源gpt智能对话客服工具;AI自动写提示词

✨ 1: vocode 用AI通过声音与用户进行实时交流 Vocode是一个旨在帮助开发者快速构建基于声音的大型语言模型&#xff08;LLM&#xff09;应用程序的开源库。简单来说&#xff0c;如果你想要开发一个能够通过声音与用户进行实时交流的应用&#xff0c;比如电话机器人、语音助手…

一套MySQL读写分离分库分表的架构,被秀到了!

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验&#xff0c; Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主&#xff0c;全网粉丝10万 擅长主流Oracle、My…

FFmpeg 音视频处理工具三剑客(ffmpeg、ffprobe、ffplay)

【导读】FFmpeg 是一个完整的跨平台音视频解决方案&#xff0c;它可以用于音频和视频的转码、转封装、转推流、录制、流化处理等应用场景。FFmpeg 在音视频领域享有盛誉&#xff0c;号称音视频界的瑞士军刀。同时&#xff0c;FFmpeg 有三大利器是我们应该清楚的&#xff0c;它们…

HNU-操作系统OS-2024期中考试

前言 该卷为22计科/智能OS期中考卷。 感谢智能22毕宿同学记忆了考卷考题。 同学评价&#xff1a;总体简单&#xff1b;第1&#xff0c;7概念题较难需要看书&#xff1b;第4&#xff0c;5题原题。 欢迎同学分享答案。 【1】共10分 操作系统的设计目标有哪些&#xff1f; 【…

设计模式之拦截过滤器模式

想象一下&#xff0c;在你的Java应用里&#xff0c;每个请求就像一场冒险旅程&#xff0c;途中需要经过层层安检和特殊处理。这时候&#xff0c;拦截过滤器模式就化身为你最可靠的特工团队&#xff0c;悄无声息地为每一个请求保驾护航&#xff0c;确保它们安全、高效地到达目的…

Garden Planner for Mac v3.8.62注册激活版:园林绿化设计软件

Garden Planner for Mac是一款专为苹果Mac OS平台设计的园林景观设计软件。这款软件的主要功能是帮助用户设计梦想中的花园&#xff0c;包括安排植物、树木、建筑物和其他物体。 Garden Planner for Mac提供了一个包含1200多种植物和物体符号的库&#xff0c;这些符号都可以进行…

贪吃蛇(c实现)

目录 游戏说明&#xff1a; 第一个是又是封面&#xff0c;第二个为提示信息&#xff0c;第三个是游戏运行界面 游戏效果展示&#xff1a; 游戏代码展示&#xff1a; snack.c test.c snack.h 控制台程序的准备&#xff1a; 控制台程序名字修改&#xff1a; 参考&#xff1a…

【Android】Kotlin学习之Kotlin方法的声明和传参

方法声明 普通类的方法 静态类的方法 不需要构建实例对象, 可以通过类名直接访问静态方法 : NumUtil.double(1) companion object 伴生类的方法 使用companion object 在普通类里定义静态方法 参数 括号内传入方法 : 当参数是方法时, 并且是最后一个参数 , 可以使用括号外…

《二十二》Qt 音频编程实战---做一个音频播放器

1.UI界面制作 作为一个音乐播放器&#xff0c;最基础的肯定就是播放、暂停、上一首以及下一首&#xff0c;为了使这个界面好看一点&#xff0c;还加入了音量控制、进度条、歌曲列表等内容&#xff0c;至于这种配色和效果好不好看&#xff0c;我也不知道&#xff0c;个人审美一如…