实现RBAC

一、菜单权限的控制

  • 左边侧边栏的菜单显示是来自于userlnfoStore.menuRoutes,当前进行打印,得到的内容其实就是staticRoutes静态路由表定义的路由数组对象;
  • 最终staticRoutes(静态路由)、allAsyncRoutes(动态路由)、anyRoute(任意路由)需要进行一次整合、拼接,最终合成一个路由数组对象。而这个路由对象的内容最终也会被放到menuRoutes菜单路由对象当中进行左边侧边栏的菜单显示操作。 

 

  • userInfoStore.menuRoutes: 只进行侧边栏菜单的显示
  • store/index.ts里的router的实例中的routes实现的是路由的真正跳转
  • 所以需要修改仓库中的静态、动态、任意路由,进行拼接处理
  • 创建filterAsyncRoutes递归函数,利用routes与allAsyncRoutes进行对比,产生授权以后的路由对象

? 问题,表现形式:
1.以trademark003进行登录,能够确认的是他有商品管理下的品牌管理的权限,children只有1个

2.退出trademark003登录,重新以admin进行登录,按道理说他有商品管理下的所有页面的操作权限,应该有5个children,但是现在却仍旧只有1个
? 原因: 因为我们对 alLAsyncRoutes 进行操作,操作的都是原来的数组对象,没有发生改变

? 解决: 对alAsyncRoutes进行深拷贝,然后再进行操作,这样,每次aLLAsyncRoutes都是一个副本,不再是原对象

userInfo.ts文件 

function filterAsyncRoutes(allAsyncRoutes: RouteRecordRaw[],routesName: string[]
) {// 拿右边比左边(拿allAsyncRoutes比routesName),因为核心算法是递归,右边是有递归的;左边没有递归return allAsyncRoutes.filter((route) => {//routesName是一维数组if (routesName.indexof(route.name as string) === -1) {// 没有在路由字符串中找到此路由名称return false;}// 找到后,继续判断 动态路由的子路由是否在路由字符串中存在if(route.children &&  route.children.length > 0) {//递归函数的特点: 函数调函数自身,需要有跳出机制,否则死循环route.children = filterAsyncRoutes(route.children,routesName)}return true;}
}        // 获取用户信息是在/permission.ts授权路由守卫中使用
async getInfo() {const info = await getuserInfoApi();this.name =.info.name ;this.avatar= info.avatar;this.roles = info.roles;this.authBtnList = info.buttons;console.log(info.routes); // 一个一维数组,里面都是string字符串// 将动态路由进行了递归的条件判断,选择出符合权限的路由对象(调用方法,参数一:动态路由,参数二:接收后端返回的routes字符串数组)const _allAsyncRoutes = filterAsyncRoutes(cloneDeep(allAsyncRoutes),info.routes);  // cloneDeep: 深拷贝的方法// 路由的合并操作,利用动态路由操作//addRoutes的参数只包含动态路由和任意路由,并没有静态路由,但是我们想构建的路由其实是静态、动态、任意三者的合并路由addRoutes([...allAsyncRoutes,anyRoute]);//为什么不加anyRoute(因为路由是需要实现跳转的,菜单的显示仅仅是菜单的显示而已,虽然菜单的显示内容是从路由中来,但是菜单的显示内容并不是路由)this.menuRoutes = [...staticRoutes,..._allAsyncRoutes];
}
  • 需要利用路由中addRoute动态添加路由的功能对授权以后的异步路由内容进行循环遍历,以便动态的将这些路由对象内容添加到当前已经存在的路由routes当中,这样的目的是为了可以进行URL当中的路由的跳转操作,但是不能够进行侧边栏菜单的显示
  • 侧边栏菜单的显示是由menuRoutes进行实现的,而它仅仅实现的是菜单的显示,不能够进行路由的跳转操作

index.ts文件

import { createRouter, createWebHistory } from "vue-router";
import { staticRoutes } from "@/router/routes";const router = createRouter({history: createwebHistory(),routes:staticRoutes,scrollBehavior() {return { top:0, left: 0};},
});//导出路由
export default router;

userInfo.ts文件

function addRoutes( routes:  RouteRecordRaw[]){// router是路由对象,而且router里本身已经有一个属性叫routes,并且routes的内容是静态路由// 说明router已经有了静态路由,所以我们只需要操作动态和异步routes.forEach((route) => {router.addRoute(route);});
}

二、菜单权限路由控制的总结

  • RBAC的层次结构: 用户N => 角色N => 资源N
  • 路由的分类: 静态、动态、任意
  • 动态权限路由: 想要实现addRoute动态注册路由的操作,首先得将后台返回的routes数组与异步路由进行递归的比较,得到的路由是授权以后的路由对象,然后对该数组对象进行循环遍历,利用addRoute实现动态路由的注册操作
  • 需要注意的细节是原路由对象和用户重新登录以后获取到的路由对象是同一对象,所以为了划分出不同的用户的路由对象,可以对动态生成的路由内容进行深拷贝操作。
  • 路由的跳转与菜单的显示是两个不的概念,上述只是实现了路由的跳转操作,但想要控制菜单的显示需要设置的是menuRoutes,所以只需要将静态路由与动态路由(授权控制以后能够操作的动态路由)进行合并即可,因为任意路由不需要显示在菜单上。
  • 光menuRoutes进行菜单的显示也是不行的,一定要利用动态路由进行路由跳转的控制,所以菜单显示与路由跳转是缺一不可的。

三、新增新的功能版块 

  • 如果有一个新的功能模块,前端要不要进行工作的处理? 需要进行哪些工作的处理?
    • 在动态的异步路由中需要进行新的功能版块路由的设置操作,路径是对应到指定的视图页面的 ===> routes
    • 在以往的操作过程中,如果想要实现views视图层,首先得确认请求的二次封装、数据模型、接口的统一管理、公共组件的抽离、store仓库的定义、hook钩子的封装....
    • 需要加增异步路由中所对应的视图页面===> views
  • 准备进行一个ACL权限管理的新模块增加操作 
    • 修改了router/routes.ts中的allAsyncRoutes异步动态路由,将ACL权限管理模块的内容进行设置操作
    • 下载并复制粘贴覆盖了api下面的数据模型和接口的统一管理内容
    • 下载并复制粘贴了views下面的视图页面
  • 如果一个新的功能板块添加,后台要不要进行工作的处理? 要进行哪些工作的处理?
    • 在前端进行了新版块路由以及接口和视图等工作的处理完毕以后,后台的权限管理的数据也需要进行一一对应。如果不对应,那么,我们将无法实现功能版块的权限设置操作,最终用户也无法查看到对应的菜单无法实现对应页面的路由跳转与显示
    • 当前我们进行的是菜单权限管理,所以后台的操作顺序应该是: 先进行菜单资源管理==> 角色授权==> 用户授权操作

四、权限管理页面的可实现性分析

  • 用户管理
    • 带分页的用户列表 (table、 button、pagination、插槽),生命周期钩子函数
    • 添加、修改用户 (dialog对话框,form表单,rules表单校验,button按钮)
    • 删除用户(popconfirm提示确认框)
    • 批量删除 (table里的column列的type为selection多选,@selection-change进行改变,获取到数组,数组里有对象),对selection选中内容进行map循环遍历,返回所有的id数组,可以考虑将其变成字符串。
    • 搜索功能(中转操作): usename和searchUsername两个内容 (思考,为什么需要有两个内容,中转操作),这样做的目的是为了防止在进行搜索的时候,输入框的值发生改变,导致请求的数据不正确
    • 分配角色: checkbox多选的v-model应用,在进行接口操作的时候需要传递选中的角色的id列表
  • 角色管理
    • 带分页的角色列表(table、button、pagination、插槽),生命周期钩子函数
    • 添加、修改角色 (dialog对话框,form表单,rules表单校验,button按钮)
    • 删除角色(popconfirm提示确认框)
    • 批量删除 (table里的column列的type为selection多选,@selection-change进行改变,获取到数组,数组里有对象),对selection选中内容进行map循环遍历,返回所有的id数组,可以考虑将其变成字符串。
    • 搜索功能(中转操作): roleName和searchRoleName两个内容(思考,为什么需要有两个内容,中转操作),这样做的目的是为了防止在进行搜索的时候,输入框的值发生改变,导致请求的数据不正确
    • 分配权限:
      • 显示的是一个tree树形组件,而树形组件中有半选状态,有全选状态,在进行权限选择的时候,最后进行数据提交的时候,需要将所有的半选、全选全部都选中,这样才是合理的。不能只选全选的状态内容。所以需要将半选与全选的内容进行拼接操作作。
      • 树形组件的数据结构是类似于路由的children的嵌套结构,所以需要利用的算法是递归算法
  • 菜单管理
    • table表格的展开模式实现树形的界面效果
    • 表格不带分页,是展开式操作,button、slot、dialog、form、rules

五、按钮级权限管理 

  • vue2当中的自定义指令有生命周期钩子函数,比如bind、inserted、 update、componentUpdate、 unbind,里面有el、binding、vNode、 oldVNode等参数。
  • vue3当中自定义指令的生命周期已经完全发生了变化,created/beforeMount/mounted/beforeUpdate/updated/beforeUnmount/unmounted这些生命周期钩子函数,看起来除了beforeCreate没有了,其它的生命周期钩子函数与页面的生命周期钩子函数是保持一致的。而钩子函数中的参数也有(el, binding,vnode, prevVnode)
  • 可以创建一个directives的目录,并且新建一个has.ts的程序文件,该程序文件中虽然想实现指令的构建,但是这个指令将被创建于插件当中。
  • 只需要在指令中利用条件判断,去判断字符串有没有在对应的数组当中,那么就可以考虑是否将DOM元素进行删除。
  • 因为是插件,插件中有app.directive全局指令的构建,所以任何页面都可以使用v-has进行自定义指令的调用,当前操作的前提是在入口文件中将插件引入并且进行use(has)的使用。

  has.ts文件 

//从store仓库中获取按钮的数组列表
import pinia from "@/stores";
import { useUserInfoStore ]from "@/stores/userInfo";
import type { App } from "vue";
//我们当前并不在组件中,所以没办法使用原型链的方式来挂载,所以我们使用插件的方式来挂载
const userInfoStore= useUserInfoStore(pinia);// 进行一个自定义插件的定义
export default (app: App) => {// 自定义插件当中可以做的事情有很多,比如全局组件、全局指令、全局方法、实例方法(vue2)//全局指令app.directive("has", {mounted(el, binding){if (userInfoStore.authBtnList.indexOf(binding.value) === -1) {// 没有找到匹配的内容,所以元素不应该显示,应该删除el.parentNode && el.parentNode.removeChild(el);}},});
};

  main.ts文件

import { createApp } from "vue";
import pinia from."./stores";
import ElementPlus from "element-plus";
import zhCn from "element-plus/es/locale/lang/zh-cn";
import "element-plus/dist/index.css";
import App from "./App.vue";
import router from "./router";
import "./styles/index.scss";
import ElSvg from "./components/SvgIcon/ElSvg";
import "./permission";
import CategorySelector from "@/components/CategorySelector/index.vue";
import has from "@/directive/has";const app = createApp(App);app.component(CategorySelector.name,CategorySelector);ElSvg(app);app.use(pinia).use(has).use(router).use(ElementPlus, {locale: zhCn,}).mount("#app");

  index.vue页面使用

<el-buttonsize="small"@click="$event = showUpdateDialog(scope.row)"v-has="'btn.Trademark.update'"   //自定义指令使用,判断修改按钮是否正常显示>修改</el-button
>

六、路由守卫

  • 权限与路由守卫之间的关系是什么? 权限操作一定是会使用路由守卫功能的,因为只有在守卫的情况下内容才可以进行限制的访问。
  • 可能根据情况设置白名单与黑名单
  • 具体情况是根据需求判断token以便进行next的放行处理

  permission.ts

import router from "@/router";
import NProgress from "nprogress";  // 进度条
import "nprogress/nprogress.css"
import pinia from "@/stores";
import { useUserInfoStore } from "@/stores/userInfo";
import { ElMessage } from "element-plus";
import getPageTitle from "./utils/get-page-title";NProgress.configure({ showSpinner: false });
const userInfoStore = useuserInfoStore(pinia);// 不用进行token检查的白名单路径数组
const whiteList = ["/login"];// 路由加载前
router.beforeEach(async (to, from, next) => {// 在显示进度条NProgress.start();// 设置整个页面的标题document.title = getPageTitle(to.meta.title as string);const token = userInfoStore.token;// 如果token存在(已经登陆或前面登陆过)if (token) {//如果请求的是登陆路由if (to.path === "/login") {//直接跳转到根路由,并完成进度条next({ path: "/" });NProgress.done();} else {// 请求的不是登陆路由// 是否已经登陆const hasLogin = !!userInfoStore.name;// 如果已经登陆直接放行if (hasLogin) {next();} else {// 如果还没有登陆try {// 异步请求获取用户信息(包含权限数据) ===> 动态注册用户的权限路由 => 当次跳转不可见await userInfoStore.getInfo( );next(to); // 重新跳转去目标路由,能看到动态添加的异步路由,且不会丢失参数                           NProgress.done(); // 结束进度条} catch (error:any) {// 如果请求处理过程中出错// 重置用户信息await userInfostore.reset():// 提示错误信息// ELMessage.error(error.message || 'Has Error') // axios拦截器中已经有提示了// 跳转到登陆页面,并携带原本要跳转的路由路径,用于登陆成功后跳转next(`/login?redirect=${to.path}`);//完成进度条NProgress.done( );}}}} else {// 没有token// 如果目标路径在白名单中(是不需要token的路径)if (whiteList.indexOf(to.path) !== -1){// 放行next();} else {// 如果没在白名单中,跳转到登陆路由携带原目标路径next(`/login?redirect=${to.path}`);// 完成进度条  当次跳转中断了要进行个新的跳转了NProgress.done();}}
});// 路由加载后
router.afterEach(() => {NProgress .done();
});

七、权限总结

  • 预设资源,菜单的资源有没有预设,按钮的资源有没有预设。菜单与按钮的资源是一一对应的,而每个菜单下的按钮是根据这个版块具体的业务流程进行确认的。
  • 角色管理
    • 普通角色在进行新增、修改、删除操作的时候都很简单
    • 但是角色有一个授权功能,也就是给这个角色进行资源权限的分配操作(菜单、按钮不同的资源),需要强化tree树形组件的应用能力
  • 用户管理
    • 用户在进行新增、修改、删除操作的时候也很简单,但是会多出一给用户进行角色分配的操作
    • 用户对于角色授权来讲是一对多的关系
    • 一个用户可以拥有多个角色
    • 一个角色也可以分配给不同的用户,所以最终用户与角色是多对多的关系
    • 所以用户在进行角色分配的时候使用的是checkbox多选框操作

八、面试的时候如何介绍权限 

  • 我们之前项目是有比较完善的权限管理模块的(告诉对方的信息是我做过很多的项目,并且涉及权限管理模块,甚至负责过权限管理模块,因为它是基础模块,所以我的经验是比较丰富的)
  • 采用的模式是RBAC基于角色的权限控制(有专业的术语,让对方深入感受你对于模块的理解,明确你的专业)
  • 项目的权限管理主要包括三大核心版块: 主要包括用户管理、角色管理以及资源管理(相对具体的内容的介绍,表明我确实是处理过对应版块内容的)
  • 这三个版块的基础是资源管理,而资源管理相对是固定的,资源的新增、修改、删除等操作都会影响到整个项目的版本(更具体的描述,不进行代码介绍,强调的模块的业务流程)
  • 资源管理事实上主要针对的是菜单权限与按钮权限,因为菜单对应的就是页面,而按钮对应的是页面中具体按钮的可操作性,所以在进行菜单权限管理的时候对应的需要分配按钮的权限
  • 想要将用户与资源之间建立起关系,其实可以利用角色来进行桥接操作,所以只需要级用户进行一个角色的识别,确认用户拥有多个身份角色即可 (明确三层结构的定位)
  • 用户与角色之间是多对多的对应关系,所以在操作的过程中还是有一定的复杂度的。最终需要通过角色拥有的资源权限分配到用户身上以后产生并集,确认用户最终所拥有的权限
  • 用户在和角色以及资源进行权限明确的情况下,只需要一登录就可以明确他的身份,拥有的菜单与按钮的权限
  • 权限的菜单资源主要实现的是路由当中的addRoute动态注册的功能,涉及到基本的算法是递归,因为路由存在多层嵌套的层次
  • 想要确认一个模块中的按钮权限往往是比较简单的,只需要利用插件、指令、函数、钩子等代码的封装方式就可以,实现简单数组的条件判断,但是按钮的权限操作实现的是硬编码,所以需要与后端开发进行“识别码”的共同约定
  • 在进行权限操作时一定会配合的是路由守卫功能,可以设置白名单、黑名单等内容进行token以及权限的认证判断(我们对于路由守卫也比较了解)
  • RBAC权限管理它的重点和难点是在于后台开发,但是前端涉及的内容也非常的多,所以前后台人员沟通配合是一个关键性要素,而我对于权限管理的业务流程以及操作模式都是比较熟悉的,所以能够更好的与后端开发人员进行紧密的沟通 (协作能力)
  • 因为我们之前的项目还是属于中小型项目,所以权限管理的操作还限于菜单级、按钮级,至于数据级的权限控制并没有过多的涉及,不知道我们当前项目中权限的细度有没有强化到数据级(不光要自己说,还得别人说)

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

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

相关文章

【芯片设计- RTL 数字逻辑设计入门 11 -- 移位运算与乘法】

请阅读【嵌入式开发学习必备专栏 】 文章目录 移位运算与乘法Verilog Codeverilog 拼接运算符&#xff08;{}&#xff09;Testbench CodeVCS 波形仿真 问题小结 移位运算与乘法 已知d为一个8位数&#xff0c;请在每个时钟周期分别输出该数乘1/3/7/8,并输出一个信号通知此时刻输…

【51单片机】实现一个动静态数码管显示项目(前置知识铺垫,代码&图演示)(5)

前言 大家好吖&#xff0c;欢迎来到 YY 滴单片机 系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过单片机的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY…

【python量化交易】qteasy使用教程01 - 安装方法及初始化配置

qteasy教程1 - 安装方法及初始化配置 qteasy教程1 - 安装方法及初始配置qteasy安装前的准备工作1&#xff0c; 创建安装环境2&#xff0c;安装MySQL数据库 (可选)安装pymysql 3&#xff0c;创建tushare账号并获取API token (可选)4&#xff0c;安装TA-lib (可选)WindowsMac OSL…

阿里云游戏服务器收费价格表,一年和1个月报价

阿里云游戏服务器租用价格表&#xff1a;4核16G服务器26元1个月、146元半年&#xff0c;游戏专业服务器8核32G配置90元一个月、271元3个月&#xff0c;阿里云服务器网aliyunfuwuqi.com分享阿里云游戏专用服务器详细配置和精准报价&#xff1a; 阿里云游戏服务器租用价格表 阿…

idea设置terminal为git

要在IntelliJ IDEA中设置终端为Git Bash&#xff0c;请按照以下步骤操作&#xff1a; 打开 Settings&#xff08;设置&#xff09;。点击 Tools&#xff08;工具&#xff09;选项卡。进入 Terminal&#xff08;终端&#xff09;界面。在 Shell Path 下选择 Browse&#xff08;…

C语言:操作符详解

创作不易&#xff0c;给个三连吧&#xff01;&#xff01; 一、算术操作符 C语言中为了方便计算&#xff0c;提供了算数操作符&#xff0c;分别是:,-,*,/,% 由于这些操作符都是有两个操作数&#xff08;位于操作符两边&#xff09;&#xff0c;所以这种操作符也叫做双目操作…

在没有鼠标或键盘的情况下在 Mac 上如何启用蓝牙?

通过这个技巧&#xff0c;小编将向您展示几种无需鼠标或键盘即可在 Mac 上重新启用蓝牙的方法。如果您想开始使用蓝牙配件&#xff0c;但还没有连接&#xff0c;这会很有用。 无需鼠标即可启用蓝牙 蓝牙是iPhone、iPad和 Mac 的标准配置。它确保您可以无线使用各种配件&#…

NC6X单点登录设计文档说明

前言 因为业务场景需要&#xff0c;第三方系统有些工作需要经常到NC系统里做&#xff0c;如果每次去NC系统做业务单据&#xff0c;都需要反复登录&#xff0c;导致客户使用体验不是很好&#xff0c;所以需要开发实现从第三方系统单点登录到NC系统&#xff0c;提高客户满意度。 …

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之ScrollBar组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之ScrollBar组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、ScrollBar组件 鸿蒙&#xff08;HarmonyOS&#xff09;滚动条组件ScrollBar&…

【http】2、http request header Origin 属性、跨域 CORS、同源、nginx 反向代理、预检请求

文章目录 一、Origin 含义二、跨源资源共享&#xff1a;**Cross-Origin Resource Sharing** CORS2.1 跨域的定义2.2 功能概述2.3 场景示例2.3.1 简单请求2.3.2 Preflighted requests&#xff1a;预检请求 2.4 header2.4.1 http request header2.4.1.1 Origin2.4.1.2 Access-Con…

《山雨欲来-知道创宇 2023 年度 APT 威胁分析总结报告》

下载链接: https://pan.baidu.com/s/1eaIOyTk12d9mcuqDGzMYYQ?pwdzdcy 提取码: zdcy

Go 语言 for 的用法

For statements 本文简单翻译了 Go 语言中 for 的三种用法&#xff0c;可快速学习 Go 语言 for 的使用方法&#xff0c;希望本文能为你解开一些关于 for 的疑惑。详细内容可见文档 For statements。 For statements with single condition 在最简单的形式中&#xff0c;只要…