Vue3寫一個後臺管理系統(4)RBAC權限受控體系的實現

一、RBAC 權限控制體系

要實現動態Menu,我們需要先來統一一下認知,明確項目中的權限控制系統。
網上找了張圖,我們可以大致的看下


從圖中,我們可以簡單的這樣理解RBAC 權限控制體系。

  • 用戶:我們登錄後臺管理系統的賬號。舉個例子:張三這個人,我們可以認爲他是一個用戶
  • 角色:用戶的“頭銜”。張三是一個銷售經理,那麼“銷售經理”,我們可以認爲他是一個角色。
  • 權限:每個角色都有不同的權限。“銷售經理”這個角色,可以查看、刪除、編輯客戶資料,那麼張三就可以查看、刪除、編輯客戶資料,這時候如果有個李四,李四是普通的“銷售”的角色,而普通的“銷售”只能查看客戶信息,不能刪除、編輯客戶信息,所以李四隻能查看客戶信息。

那麼明確好了 RBAC 的概念之後,接下來我們就可以來去實現我們的輔助業務了,所謂輔助業務具體指的就是:

  • 員工管理(用戶列表)
    1. 爲用戶分配角色
  • 角色列表
    1. 角色列表展示
    2. 爲角色分配權限
  • 權限列表
    1. 權限列表展示

我們先直接做好的後臺先看看效果,明確下RBAC在我們後臺管理系統中的含義。



我們從上面兩張圖中,可以看到,賬號(test),是一個“測試-角色”的角色,
而測試角色的只能看到下面的菜單(權限列表)



而如果我們用超管的賬號登錄進去,是能看到所有的菜單(權限列表)的


那麼由此呈現我們可以看出,整個權限系統其實分成了兩部分:

  1. 頁面權限:根據不同的 權限數據,展示不同的頁面(就是展示不同的菜單Menu,因爲一個菜單按鈕,是對應一個具體的頁面)
  2. 功能權限:根據不同的 權限數據,一個頁面裏展示不同的 功能按鈕

二、下面我們說下代碼實現的邏輯

  1. 頁面權限實現的核心在於 路由表配置

  2. 路由表配置的核心在於 私有路由表 privateRoutes

  3. 私有路由表 privateRoutes 的核心在於 addRoute API

那麼簡單一句話總結,我們只需要:根據不同的權限數據,利用 addRoute API 生成不同的私有路由表 即可實現 頁面權限 功能

而*實現功能權限的核心在於 根據數據隱藏功能按鈕,那麼隱藏的方式我們可以通過Vue的指令進行控制

三、頁面權限代碼實現

首先我們的路由表需要分成公有路由表私有路由表

  • 私有路由表:就是不同角色擁有不同的路由表
  • 共有路由表:就是每個角色都有的路由表:例如登錄界面、404界面、401界面
    講清了這些下面實現起來也是很簡單的,只是一些細節可能要注意,那麼直接看代碼吧,代碼裏都有註釋
  • 創建每一個私有路由表


其中一個路由表的代碼,其他都是類似的,要注意的是每個路由表的path是要不和服務端返回的path相同的,我們到時候是根據路由的path去篩選數據的,這裏我用到的所有界面都是test-page頁面,但不影響具體大邏輯,大家明白就行

const RightRouter = {
  path: '/manage',
  component: Layout,
  redirect: '/manage/manageList',
  alwaysShow: true, // will always show the root menu
  name: 'manage',
  meta: {
    title: '管理1',
    icon: 'el-icon-s-check'
  },
  children: [
    {
      path: '/manage/manageList',
      component: () => import('@/views/test-page/index.vue'),
      name: 'list1',
      meta: { title: '列表1' }
    },
    {
      path: '/manage/manageList2',
      component: () => import('@/views/test-page/index.vue'),
      name: 'rightSetList',
      meta: { title: '列表2' }
    }

  ]
}

export default RightRouter

  • 把每個路由表合併到privateRoutes
/**
 * 私有路由表
 */
export var privateRoutes = [
  permissions,
  manageList,

]
/**
 * 公開路由表
 */
export var publicRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index')
  },
  {
    path: '/',
    // 注意:帶有路徑“/”的記錄中的組件“默認”是一個不返回 Promise 的函數
    component: layout,
    redirect: '/home',
    children: [
      {
        path: '/home',
        name: 'home',
        component: () => import('@/views/home/index'),
        meta: {title: '首頁', affix: true},//affix=true,tagViews右側沒有關閉按鈕
        hidden: true,//不顯示在側邊欄
      },
      {
        path: '/404',
        name: '404',
        component: () => import('@/views/error-page/404')
      },
      {
        path: '/401',
        name: '401',
        component: () => import('@/views/error-page/401')
      }
    ]
  }
]

const router = createRouter({
  history: createWebHashHistory(),
  // routes: [...publicRoutes, ...privateRoutes]
  routes: publicRoutes

})

export default router

我們先看下接口返回的數據



從接口返回的數據中我們能可以看出,一級菜單和二級菜單都是有一個url字段的,我們就是要根據這個url字段和我門路由表的path字段去做對錶,如果存在,就渲染這個路由,不存在就不去渲染這個路由,所以我們需要先將服務端返回的路由數據,轉化成這個格式的數據



篩選路由的具體方法代碼



/**
 * 根據服務端返回的路由數據,篩選過濾本地的路由數據
 * @param routes asyncRoutes 本地寫的數據
 * @param roles 接口獲取的數據
 */
export function filterPrivateRoutes(routes, roles) {
  const res = []
  routes.forEach(route => {
    const tmp = { ...route }
    //檢查是否符合權限規則:根據自己公司定義的規則
    if (hasPermission(roles, tmp)) {
      if (tmp.children) {
        tmp.children = filterPrivateRoutes(tmp.children, roles)
      }
      res.push(tmp)
    }
  })

  return res
}

export default {
  namespaced: true,
  state: {
    // 路由表:初始擁有靜態路由權限
    routes: publicRoutes
  },
  mutations: {
    /**
     * 增加路由
     */
    setRoutes(state, newRoutes) {
      // 永遠在靜態路由的基礎上增加新路由
      state.routes = [...publicRoutes, ...newRoutes]
    }
  },
  actions: {

}


最後,在在 src/permission 中,獲取路由數據之後調用這些代碼,相關注釋都寫到代碼裏了


// 白名單
const whiteList = ['/login']
/**
 * 路由前置守衛
 */
router.beforeEach(async (to, from, next) => {
       ....................
        const {roles} = await store.dispatch('user/getPermissionData')
        // 處理用戶權限,篩選出需要添加的權限
        const accessRoutes = await store.dispatch('permission/generateRoutes', roles)

        console.log("篩選出需要addRoute的路由",accessRoutes)
        // 利用 addRoute 循環添加
        accessRoutes.forEach(item => {
          router.addRoute(item)
        })
        // router.addRoutes(accessRoutes)
          // hack method to ensure that addRoutes is complete
          // set the replace: true, so the navigation will not leave a history record
        next({...to, replace: true})
    ........................


到這裏動態菜單差不多就講完了,但還有一個問題,就是如果我們更換和賬戶的登錄,只有手動刷新下頁面,左邊菜單纔會改變,不會自動去改變。這是因爲我們退出的時候,沒有重置路由表。所以我們在退出的時候,重置下就行了


/**
 * 重置路由表
 */
export function resetRouter() {
  if (store.getters.hasRoles) {
    const menus = store.getters.roles
    //removeRoute是根據路由的name去刪除路由的,所以我們要對路由的名字進行截取
    // const menus = ['getRoleList','admintorList','adminAuth']
    // console.log("menus==",menus)
    // console.log("router==",router.getRoutes())
    menus.forEach(menu => {
      let url = menu.url
      let i = url.lastIndexOf('/')
      let name = url.substring(i+1,url.length)
      router.removeRoute(name)
    })
  }

}

import router, { resetRouter } from '@/router'

logout(context) {
      resetRouter()
      ...
    }

四、功能權限代碼實現

所以首先我們先去創建這樣一個指令(vue3 自定義指令

  1. 我們期望最終可以通過這樣格式的指令進行功能受控 v-permission="'/adminAuth/admintorList'"

  2. 以此創建對應的自定義指令 directives/permission

import store from '@/store'
import {lowerCase} from '@/utils/index'

function checkPermission(el, binding) {
  // 獲取綁定的值,此處爲權限
  const value = lowerCase(binding.value);
  const auths = store.getters.buttons || [];
  if (!auths.includes(value)) {
    el.parentNode.removeChild(el);
  }
}

export default {
  // 在綁定元素的父組件被掛載後調用
  mounted(el, binding) {
    checkPermission(el, binding)
  },
  // 在包含組件的 VNode 及其子組件的 VNode 更新後調用
  update(el, binding) {
    checkPermission(el, binding)
  }
}

3.在 directives/index 中綁定該指令

import permission from './permission'

export default app => {
  app.directive('permission', permission)
}

4.在頁面中,添加指令

<el-button type="primary" @click="searchEvent"  v-permission="'/adminAuth/admintorList'">查詢</el-button>

五、總結

那麼到這裏我們整個權限受控就算是全部完成了。

整個這一大節中,核心就是 RBAC的權限受控體系 。圍繞着 用戶->角色->權限 的體系是現在在包含權限控制的系統中使用率最廣的一種方式。

那麼怎麼針對於權限控制的方案而言,除了文中提到的這種方案之外,其實還有很多其他的方案,大家可以在我們的話題討論中踊躍發言,多多討論。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章