博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
vue基于d2-admin的RBAC权限管理解决方案
阅读量:5035 次
发布时间:2019-06-12

本文共 9018 字,大约阅读时间需要 30 分钟。

前两篇关于vue权限路由文章的填坑,说了一堆理论,是时候操作一波了。

选择是因为element-ui的相关开源项目里,d2-admin的结构和代码是让我感到最舒服的,而且基于d2-admin实现RBAC权限管理也很方便,对d2-admin没有大的侵入性的改动。

1681e95158068d71?w=300&h=300&f=png&s=14234

1681eb3f92305be3?w=1914&h=943&f=png&s=223322 1681eb458e31685a?w=1913&h=945&f=png&s=181129 1681eb50ed9e67d7?w=1902&h=932&f=png&s=153826
1681eb4be99bc910?w=1911&h=943&f=png&s=203551 1681eb7406b3c67f?w=1909&h=937&f=png&s=172969 1681eb7913a3e73c?w=1908&h=940&f=png&s=209004

相关概念

不了解RBAC,可以看这里

权限模型

  • 实现了RBAC模型权限控制
  • 菜单与路由独立管理,完全由后端返回
  • user存储用户
  • admin标识用户是否为系统管理员
  • role存储角色信息
  • roleUser存储用户与角色的关联关系
  • menu存储菜单信息,类型分为菜单功能,一个菜单下可以有多个功能,菜单类型的permission字段标识访问这个菜单需要的功能权限,功能类型的permission字段相当于此功能的别称,所以菜单类型的permission字段为其某个功能类型子节点的permission
  • permission存储角色与功能的关联关系
  • interface存储接口信息
  • functionInterface存储功能与接口关联关系,通过查找用户所属角色,再查找相关角色所具备的功能权限,再通过相关功能就可以查出用户所能访问的接口
  • route存储前端路由信息,通过permission字段过滤出用户所能访问的路由

运行流程及相关API

使用d2admin的原有登录逻辑,全局路由守卫中判断是否已经拉取权限信息,获取后标识为已获取。

const token = util.cookies.get('token')    if (token && token !== 'undefined') {      //拉取权限信息      if (!isFetchPermissionInfo) {        await fetchPermissionInfo();        isFetchPermissionInfo = true;        next(to.path, true)      } else {        next()      }    } else {      // 将当前预计打开的页面完整地址临时存储 登录后继续跳转      // 这个 cookie(redirect) 会在登录后自动删除      util.cookies.set('redirect', to.fullPath)      // 没有登录的时候跳转到登录界面      next({        name: 'login'      })    }
//标记是否已经拉取权限信息let isFetchPermissionInfo = falselet fetchPermissionInfo = async () => {  //处理动态添加的路由  const formatRoutes = function (routes) {    routes.forEach(route => {      route.component = routerMapComponents[route.component]      if (route.children) {        formatRoutes(route.children)      }    })  }  try {    let userPermissionInfo = await userService.getUserPermissionInfo()    permissionMenu = userPermissionInfo.accessMenus    permissionRouter = userPermissionInfo.accessRoutes    permission.functions = userPermissionInfo.userPermissions    permission.roles = userPermissionInfo.userRoles    permission.interfaces = util.formatInterfaces(userPermissionInfo.accessInterfaces)    permission.isAdmin = userPermissionInfo.isAdmin == 1  } catch (ex) {    console.log(ex)  }  formatRoutes(permissionRouter)  let allMenuAside = [...menuAside, ...permissionMenu]  let allMenuHeader = [...menuHeader, ...permissionMenu]  //动态添加路由  router.addRoutes(permissionRouter);  // 处理路由 得到每一级的路由设置  store.commit('d2admin/page/init', [...frameInRoutes, ...permissionRouter])  // 设置顶栏菜单  store.commit('d2admin/menu/headerSet', allMenuHeader)  // 设置侧边栏菜单  store.commit('d2admin/menu/fullAsideSet', allMenuAside)  // 初始化菜单搜索功能  store.commit('d2admin/search/init', allMenuHeader)  // 设置权限信息  store.commit('d2admin/permission/set', permission)  // 加载上次退出时的多页列表  store.dispatch('d2admin/page/openedLoad')  await Promise.resolve()}

后端需要返回的权限信息包括权限过滤后的角色编码集合,功能编码集合,接口信息集合,菜单列表,路由列表,以及是否系统管理员标识。格式如下

{  "statusCode": 200,  "msg": "",  "data": {    "userName": "MenuManager",    "userRoles": [      "R_MENUADMIN"    ],    "userPermissions": [      "p_menu_view",      "p_menu_edit",      "p_menu_menu"    ],    "accessMenus": [      {        "title": "系统",        "path": "/system",        "icon": "cogs",        "children": [          {            "title": "系统设置",            "icon": "cogs",            "children": [              {                "title": "菜单管理",                "path": "/system/menu",                "icon": "th-list"              }            ]          },          {            "title": "组织架构",            "icon": "pie-chart",            "children": [              {                "title": "部门管理",                "icon": "html5"              },              {                "title": "职位管理",                "icon": "opencart"              }            ]          }        ]      }    ],    "accessRoutes": [      {        "name": "System",        "path": "/system",        "component": "layoutHeaderAside",        "componentPath": "layout/header-aside/layout",        "meta": {          "title": "系统设置",          "cache": true        },        "children": [          {            "name": "MenuPage",            "path": "/system/menu",            "component": "menu",            "componentPath": "pages/sys/menu/index",            "meta": {              "title": "菜单管理",              "cache": true            }          },          {            "name": "RoutePage",            "path": "/system/route",            "component": "route",            "componentPath": "pages/sys/route/index",            "meta": {              "title": "路由管理",              "cache": true            }          },          {            "name": "RolePage",            "path": "/system/role",            "component": "role",            "componentPath": "pages/sys/role/index",            "meta": {              "title": "角色管理",              "cache": true            }          },          {            "name": "UserPage",            "path": "/system/user",            "component": "user",            "componentPath": "pages/sys/user/index",            "meta": {              "title": "用户管理",              "cache": true            }          },          {            "name": "InterfacePage",            "path": "/system/interface",            "component": "interface",            "meta": {              "title": "接口管理"            }          }        ]      }    ],    "accessInterfaces": [      {        "path": "/menu/:id",        "method": "get"      },      {        "path": "/menu",        "method": "get"      },      {        "path": "/menu/save",        "method": "post"      },      {        "path": "/interface/paged",        "method": "get"      }    ],    "isAdmin": 0,    "avatarUrl": "https://api.adorable.io/avatars/85/abott@adorable.png"  }}

设置菜单

将固定菜单(/menu/header/menu/aside)与后端返回的权限菜单(accessMenus)合并后,存入相应的vuex store模块中

...let allMenuAside = [...menuAside, ...permissionMenu]let allMenuHeader = [...menuHeader, ...permissionMenu]...// 设置顶栏菜单store.commit('d2admin/menu/headerSet', allMenuHeader)// 设置侧边栏菜单store.commit('d2admin/menu/fullAsideSet', allMenuAside)// 初始化菜单搜索功能store.commit('d2admin/search/init', allMenuHeader)

处理路由

默认使用routerMapComponents 的方式处理后端返回的权限路由

//处理动态添加的路由const formatRoutes = function (routes) {    routes.forEach(route => {        route.component = routerMapComponents[route.component]        if (route.children) {        formatRoutes(route.children)        }    })}...formatRoutes(permissionRouter)//动态添加路由router.addRoutes(permissionRouter);// 处理路由 得到每一级的路由设置store.commit('d2admin/page/init', [...frameInRoutes, ...permissionRouter])

路由处理方式及区别可看

设置权限信息

将角色编码集合,功能编码集合,接口信息集合,以及是否系统管理员标识存入相应的vuex store模块中

...permission.functions = userPermissionInfo.userPermissionspermission.roles = userPermissionInfo.userRolespermission.interfaces = util.formatInterfaces(userPermissionInfo.accessInterfaces)permission.isAdmin = userPermissionInfo.isAdmin == 1...// 设置权限信息store.commit('d2admin/permission/set', permission)

接口权限控制以及loading配置

支持使用角色编码,功能编码以及接口权限进行控制,如下

export function getMenuList() {    return request({        url: '/menu',        method: 'get',        interfaceCheck: true,        permission:["p_menu_view"],        loading: {            type: 'loading',            options: {                fullscreen: true,                lock: true,                text: '加载中...',                spinner: 'el-icon-loading',                background: 'rgba(0, 0, 0, 0.8)'            }        },        success: {            type: 'message',            options: {                message: '加载菜单成功',                type: 'success'            }        }    })}

interfaceCheck: true表示使用接口权限进行控制,如果vuex store中存储的接口信息与当前要请求的接口想匹配,则可发起请求,否则请求将被拦截。

permission:["p_menu_view"]表示使用角色编码和功能编码进行权限校验,如果vuex store中存储的角色编码或功能编码与当前表示的编码相匹配,则可发起请求,否则请求将被拦截。

源码位置在libs/permission.js,可根据自己需求进行修改

loading配置相关源码在libs/loading.js,根据自己需求进行配置,success也是如此,源码在libs/loading.js。 照此思路可以自行配置其它功能,比如请求失败等。

页面元素权限控制

使用指令v-permission

批量编辑

参数可为functionrole,表明以功能编码或角色编码进行校验,为空则使用两者进行校验。

修饰符all,表示必须全部匹配指令值中所有的编码。

源码位置在plugin/permission/index.js,根据自己实际需求进行修改。

使用v-if+全局方法:

添加
data() {    return {      canAdd: this.hasPermissions(["p_menu_edit"])    };  },

默认同时使用角色编码与功能编码进行校验,有一项匹配即可。

类似的方法还要hasFunctionshasRoles

源码位置在plugin/permission/index.js,根据自己实际需求进行修改。

不要使用v-if="hasPermissions(['p_menu_edit'])"这种方式,会导致方法多次执行

也可以直接在组件中从vuex store读取权限信息进行校验。

开发建议

  • 页面级别的组件放到pages/目录下,并且在routerMapCompnonents/index.js中以key-value的形式导出

  • 不需要权限控制的固定菜单放到menu/aside.jsmenu/header.js

  • 不需要权限控制的路由放到router/routes.js frameIn

  • 需要权限控制的菜单与路由通过界面的管理功能进行添加,确保菜单的path与路由的path相对应,路由的name与页面组件的name一致才能使keep-alive生效,路由的componentrouterMapCompnonents/index.js中能通过key匹配到。

  • 开发阶段菜单与路由的添加可由开发人员自行维护,并维护一份清单,上线后将清单交给相关的人去维护即可。

如果觉得麻烦,不想菜单与路由由后端返回,可以在前端维护一份菜单和路由(路由中的component还是使用字符串,参考mock/permissionMenuAndRouter.js),并且在菜单和路由上面维护相应的权限编码,一般都是使用功能编码。后端就不需要返回菜单和路由信息了,但是其他权限信息,比如角色编码,功能编码等还是需要的。通过后端返回的功能编码列表,在前端过滤出用户具备权限的菜单和路由,过滤处理后后的菜单与路由格式与之前由后端返回的格式一致,然后将处理后的菜单与路由当做后端返回的一样处理即可。

数据mock与代码生成

数据mock使用修改而来的,数据真实来源于后端,相比其他工具,支持数据持久化,存储使用的是json文件,不需要安装数据库。简单的配置即可自动生成增删改查的接口。

后端使用中间件控制访问权限,比如:

.get('/menu', PermissionCheck(), controllers.menu.getMenuList)

PermissionCheck默认使用接口进行校验,校验用户所能访问的API中是否匹配当前API,支持使用功能编码与角色编码进行校验PermissionCheck(["p_menu_edit"],["r_menu_admin"],true),第一个参数为功能编码,第二个为角色编码,第三个为是否使用接口进行校验。

更多详细用法可看

前端代码生成还在开发中...

转载于:https://www.cnblogs.com/jaycewu/p/10228936.html

你可能感兴趣的文章
redhat 7 源码安装 mysql5.5.49
查看>>
技术项目,问题
查看>>
git常见问题
查看>>
Android官方技术文档翻译——ApplicationId 与 PackageName
查看>>
js随机数的取整
查看>>
Feign使用Hystrix无效原因及解决方法
查看>>
Sam做题记录
查看>>
[bzoj] 2453 维护数列 || 单点修改分块
查看>>
IIS版本变迁
查看>>
【eclipse jar包】在编写java代码时,为方便编程,常常会引用别人已经实现的方法,通常会封装成jar包,我们在编写时,只需引入到Eclipse中即可。...
查看>>
软件工程APP进度更新
查看>>
Python 使用正则替换 re.sub
查看>>
测试用例(一)
查看>>
邮件中的样式问题
查看>>
AJAX 状态值与状态码详解
查看>>
php面向对象编程(oop)基础知识示例解释
查看>>
关于根据Build Platform或者OS 加载x86或者x64 dll的问题
查看>>
程序员高效开发的几个技巧
查看>>
hexo 搭建博客
查看>>
建造者模式(屌丝专用)
查看>>