基於vue的簡單許可權管理實現總結

失落憶發表於2020-03-09

前言

總結一種比較常見的許可權控制方法,主要使用vue-router的addRoutes根據使用者許可權動態新增路由實現。本文主要在前端對許可權進行配置。

完整demo地址

主要知識點

  • vue
  • vue-router
  • vuex
  • vue-cli

基本專案專案結構

使用vue-cli生成專案

  • 目錄結構
├─public
│      favicon.ico
│      index.html
│      
└─src
    │  App.vue
    │  main.js
    │  
    ├─assets
    │      logo.png
    │      
    ├─components
    │      HelloWorld.vue
    │      
    ├─router
    │      index.js
    │      
    ├─store
    │  │  index.js
    │  │  
    │  └─modules
    │          permission.js
    │          user.js
    │          
    └─views
            About.vue
            Home.vue
            Login.vue
│  babel.config.js
│  package.json
複製程式碼
  • 主要檔案
  1. src/router/index.js
  2. src/store/user.js
  3. src/store/permission.js

許可權設計基本原理

  • 基本原理
根據後臺返回的使用者角色資訊在前端配置當前使用者可以訪問的路由,然後通過addRoutes動態新增。
複製程式碼
  • 後臺返回的使用者資訊
{
    username:"張三",
    role:["admin","kefu"] //使用者的角色
}
複製程式碼

實現

配置router(src/router/index.js)

  1. routes

配置不需要許可權的基礎路由如登陸頁面,404頁面等。如:

export const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('../views/Home.vue'),
  },
  {
    path: '/404',
    name: '404',
    component: () => import('../views/404.vue'),
    hidden: true, // 為true時在頁面導航中隱藏
  }
]
複製程式碼
  1. asyncRoutes

配置需要許可權的路由,將可訪問路由的角色資訊配置到路由的meta屬性中,如:

export const asyncRoutes = [
    {
        path: '/page1',
        name: 'Page1',
        component: () => import('../views/RolePage1.vue'),
        meta: { roles: ['admin'] } // 表示擁有admin角色的使用者可以訪問當前路由
        meta: { roles: ['admin','kefu'] } // 表示擁有admin和kefu角色的使用者可以訪問當前路由
    },
    { path: '*', redirect: '/404', hidden: true } // 配置重定向404頁面
]
複製程式碼
  1. 注意

如果需要配置重定向404頁面的話,需要配置在asyncRoutes的最後

  1. 完整示例
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)
/**
 * routes中設定基礎路由
 * asyncRoutes中設定需要根據角色許可權動態載入的頁面路由
 */
export const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('../views/Home.vue'),
  },
  {
    path: '/404',
    name: '404',
    component: () => import('../views/404.vue'),
    hidden: true, // 為true時在頁面導航中隱藏
  }
]

export const asyncRoutes = [
  {
    path: '/page1',
    name: 'Page1',
    component: () => import('../views/RolePage1.vue'),
    meta: { roles: ['super', 'admin'] }
  },
  {
    path: '/page2',
    name: 'Page2',
    component: () => import('../views/RolePage2.vue'),
    meta: { roles: ['super', 'kefu'] }
  },
  {
    path: '/page3',
    name: 'Page3',
    component: () => import('../views/RolePage3.vue'),
    meta: { roles: ['super', 'kefu'] }
  },
  { path: '*', redirect: '/404', hidden: true }
]


export default new VueRouter({
  routes
})

複製程式碼

編寫permission的module(src/store/modules/permission)

  1. 作用

根據使用者角色和配置的路由生成當前使用者可以訪問的路由配置

  1. 主要函式

getAsyncRoutes獲取生成的路由,hasPermission判斷是否有,GenerateRoutes根據角色和asyncRoutes生成需要新增的路由

  • getAsyncRoutes
/**根據角色獲取路由配置,並儲存使用者角色 */
getAsyncRoutes({ commit }, roles) {
  let filterRoutes = GenerateRoutes(asyncRoutes, roles)
  let res = routes.concat(filterRoutes)
  commit('SET_ROUTES', res)
  return res
}
複製程式碼
  • GenerateRoutes
/**
 * 根據角色和配置生成當前使用者的路由
 * @param {array} routes 配置的路由
 * @param {array} roles 使用者角色
 */
let GenerateRoutes = (routes, roles) => {
  let res = []
  routes.forEach(route => {
    const tmp = { ...route }
    if (hasPermission(roles, tmp)) {
      if (tmp.children) {
        tmp.children = GenerateRoutes(tmp.children, roles)
      }
      res.push(tmp)
    }
  })
  return res
}
複製程式碼
  • hasPermission
/**
 * 通過meta中的roles資訊判斷使用者是否有許可權
 * @param roles
 * @param route
 */
function hasPermission(roles, route) {
  if (route.meta && route.meta.roles) {
    return roles.some(role => route.meta.roles.includes(role))
  } else {
    return true
  }
}
複製程式碼
  1. 完整示例
import { routes, asyncRoutes } from '../../router/index.js'

/**
 * 通過meta中的roles資訊判斷使用者是否有許可權
 * @param roles
 * @param route
 */
function hasPermission(roles, route) {
  if (route.meta && route.meta.roles) {
    return roles.some(role => route.meta.roles.includes(role))
  } else {
    return true
  }
}

/**
 * 根據角色和配置生成當前使用者的路由
 * @param {array} routes 配置的路由
 * @param {array} roles 使用者角色
 */
let GenerateRoutes = (routes, roles) => {
  let res = []
  routes.forEach(route => {
    const tmp = { ...route }
    if (hasPermission(roles, tmp)) {
      if (tmp.children) {
        tmp.children = GenerateRoutes(tmp.children, roles)
      }
      res.push(tmp)
    }
  })
  return res
}
const permission = {
  state: {
    roles: [],
    routes: routes // 用於配置頁面導航等
  },
  mutations: {
    SET_ROLES: (state, roles) => {
      state.roles = roles
    },
    SET_ROUTES: (state, routes) => {
      state.routes = routes
    }
  },
  actions: {
    /**根據角色獲取路由配置 */
    getAsyncRoutes({ commit }, roles) {
      commit('SET_ROLES', roles) // 儲存roles資訊到store中
      let filterRoutes = GenerateRoutes(asyncRoutes, roles)
      let res = routes.concat(filterRoutes)
      commit('SET_ROUTES', res)
      return res
    }
  }
}
export default permission
複製程式碼

編寫全域性路由鉤子

  1. 邏輯流程圖

基於vue的簡單許可權管理實現總結
2. 實現

const whiteList = ['/login', '/404'] // 免登入白名單

router.beforeEach(async (to, from, next) => {
  let token = localStorage.getItem('JWT_TOKEN')
  // 判斷登入狀態
  if (token) {
    if (to.path === '/login') {
      next({ path: '/' })
    } else {
      let hasRoles = store.state.user.roles && store.state.user.roles.length > 0
      if (!hasRoles) {
        let userInfo = await store.dispatch('getUser')
        let routes = await store.dispatch('getAsyncRoutes', userInfo.roles)
        router.addRoutes(routes)
        next({ ...to, replace: true }) // 保證路由已掛載
      } else {
        next()
      }
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) { // 在免登入白名單,直接進入
      next()
    } else {
      next(`/login?redirect=${to.path}`) // 否則全部重定向到登入頁
    }
  }
})
複製程式碼
  1. 注意
  • 需要判斷store中是否已經儲存了roles,否則會死迴圈
  • 新增新的路由後使用next({ ...to, replace: true })保證路由已掛載完成

相關文章