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

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

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

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.hqwc.cn/news/681291.html

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

相关文章

JDBC技术-1

JDBC: Java Database Connectivity Java连接数据库技术 通俗点说&#xff0c;在Java代码中&#xff0c;使用JDBC提供的方法&#xff0c;可以发送字符串类型的SQL语句到数据库管理软件&#xff08;Mysql&#xff0c;Oracle等&#xff09;&#xff0c;并且获取语句执行结…

RK3568平台(基础篇)linux错误码

一.概述 linux应用程序开发过程中&#xff0c;经常会遇到一些错误信息的返回&#xff0c;存在的可能性有&#xff0c;参数有误、非法访问、系统资源限制、设备/文件不存在、访问权限限制等等。对于这类错误&#xff0c;可以通过perror函数输出具体描述&#xff0c;或者通过str…

Linux-笔记 uboot修改设备树

1. FDT介绍 扁平设备树&#xff08;Flattened Device Tree&#xff0c;FDT&#xff09;&#xff0c;也叫平坦设备树&#xff0c;是设备树的一种二进制表示形式&#xff0c;提高了在嵌入式系统中的传输和解析效率&#xff1b; 2. 在U-Boot中使用FDT 2.1. 进入U-Boot 开发板上…

基于无监督学习算法的滑坡易发性评价的实施(k聚类、谱聚类、Hier聚类)

基于无监督学习算法的滑坡易发性评价的实施 1. k均值聚类2. 谱聚类3. Hier聚类4. 基于上述聚类方法的易发性实施本研究中的数据集和代码可从以下链接下载: 数据集实施代码1. k均值聚类 K-Means 聚类是一种矢量量化方法,最初来自信号处理,旨在将 N 个观测值划分为 K 个聚类,…

如何把逻辑地址转换为物理地址

​ 使用系统架构设计师真题说明&#xff08;2021年&#xff09;某计算机系统页面大小为 4K&#xff0c;进程 P1 的页面变换表如下图示&#xff0c;看 P1 要访问数据的逻辑地址为十六进制 1B1AH&#xff0c;那么该逻辑地址经过变换后&#xff0c;其对应的物理地址应为十六进制&…

【记录42】centos 7.6安装nginx教程详细教程

环境&#xff1a;腾讯云centos7.6 需求&#xff1a;安装nginx-1.24.0 1. 切入home文件 cd home 2. 创建nginx文件 mkdir nginx 3. 切入nginx文件 cd nginx 4. 下载nginx安装包 wget https://nginx.org/download/nginx-1.24.0.tar.gz 5. 解压安装包 tar -zxvf nginx-1.24.0.…

使用Processing和PixelFlow库创建交互式流体太极动画

使用Processing和PixelFlow库创建交互式流体太极动画 引言准备工作效果展示代码结构代码解析第一部分&#xff1a;导入库和设置基本参数第二部分&#xff1a;流体类定义MyFluidDataConfig 类详解MyFluidData 类详解my_update 方法详解流体类定义完整代码 第三部分&#xff1a;太…

c++11 lambda 捕获,匿名,返回类型后置

lambda就是即写即用的匿名函数&#xff0c;可以用于解决匹配函数参数的问题 int main(int argc,char *argv[]) {vector<int> v{1,2,3,4,5,6,7,8};for_each(v.begin(),v.end(),[](int a){cout<<a;});return 0; } for_each是固定函数&#xff0c;我们需要他但是又没…

如何使用IdeaJ2023创建一个JavaWeb项目

开篇 简单整理一下创建JavaWeb项目的步骤&#xff0c;希望能对您有所帮助。 步骤图解 步骤一: 创建项目 此时得到的项目是如图所示的普通Java项目&#xff1a; 步骤二: 在项目中增加web文件夹 点击File -> Project Structure 点击Project Setting -> Modules -> …

Android 10.0 Launcher3定制folder文件夹2x2布局之一xml文件配置和解析相关属性

1.前言 在10.0的系统rom产品定制化开发中,在对Launcher3的folder文件夹功能定制中,要求folder文件夹跨行显示,就是 2x2布局显示,默认的都是占1格的,现在要求占4格显示,系统默认是不支持显示4格的,所以接下来需要分析相关的 功能,然后来实现这个功能 2.Launcher3定制fo…

PMP考试不用报班可以自学?

随着近年来PMP证书在国内越来越受欢迎&#xff0c;越来越多的人开始报考PMP考试&#xff0c;甚至不少企业还会通过各种奖励政策来鼓励内部项目骨干去考取PMP证书。许多第一次参加PMP考试的人会有这样的疑问&#xff0c;那就是考PMP证书是否必须参加培训班呢&#xff1f;还是说可…

C程序内存分布及static变量

C程序内存分布及static变量 C语言中程序的内存分布 [&#x1f517;1](https://www.cnblogs.com/miaoxiong/p/11021827.html)[&#x1f517;2](https://blog.csdn.net/chen1083376511/article/details/54930191)c/c编译连接后二进制文件的存储动静态存储方式和存储区动态存储方式…