Vue路由權(quán)限控制分析

本文已獲作者授權(quán)轉(zhuǎn)載
作者:卑微前端?
原文:https://juejin.im/post/6892299215871934472
前言
本人在公司主要負責(zé)中后臺系統(tǒng)的開發(fā),其中路由和權(quán)限校驗算是非常重要且最為基本的一環(huán)。實際開發(fā)項目中,關(guān)于登錄和路由權(quán)限的控制參照了vue-element-admin這個明星項目,并在此基礎(chǔ)上基于業(yè)務(wù)進行了整合,接下來我會以這個項目為例,仔細地剖析整個路由和權(quán)限校驗的過程,也算是對這個知識點的一些總結(jié)。
項目總體目錄結(jié)構(gòu)
進入今天主題之前,我們先來梳理下整個項目,src 目錄下的。
api: 接口請求 assets: 靜態(tài)資源 components: 通用組件 directive: 自定義指令 filters: 自定義過濾器 icons: 圖標(biāo) layout: 布局組件(頁面架構(gòu)核心) router: 路由配置(路由權(quán)限核心模塊) store: 狀態(tài)管理 styles: 樣式文件 utils: 工具方法 views: 頁面組件 permission.js 權(quán)限管理
對這項目感興趣的同學(xué)可以自行,有針對性地學(xué)習(xí),除了路由權(quán)限校驗的功能以外,也包含了很多有意思的功能,相信能夠?qū)W到不少東西。
路由權(quán)限控制邏輯
路由處理流程圖

路由處理源碼分析
我們先找到 permission.js 文件,此處定義全局路由守衛(wèi),也是路由權(quán)限中非常關(guān)鍵的核心代碼。為方便大家閱讀,只摘取了跟路由相關(guān)的代碼
import?router?from?'./router'
import?store?from?'./store'
import?{?Message?}?from?'element-ui'
import?NProgress?from?'nprogress'?//?progress?bar
import?'nprogress/nprogress.css'?//?progress?bar?style
import?{?getToken?}?from?'@/utils/auth'?//?get?token?from?cookie
?NProgress.configure({?showSpinner:?false?})?//?NProgress?Configuration
?const?whiteList?=?['/login',?'/auth-redirect']?//?白名單配置
?router.beforeEach(async(to,?from,?next)?=>?{
?//?start?progress?bar
?NProgress.start()
?//?有token
?if?(hasToken)?{
?if?(to.path?===?'/login')
?//?如果當(dāng)前路徑為/login,重定向到首頁
?next({?path:?'/'?})
?NProgress.done()?//?hack:?https://github.com/PanJiaChen/vue-element-admin/pull/2939
?}?else?{
?//?determine?whether?the?user?has?obtained?his?permission?roles?through?getInfo
?const?hasRoles?=?store.getters.roles?&&?store.getters.roles.length?>?0
?if?(hasRoles)?{
?next()
?}?else?{
?try?{
?//?獲取用戶信息
?const?{?roles?}?=?await?store.dispatch('user/getInfo')
?//?根據(jù)用戶的角色,動態(tài)生成路由
?const?accessRoutes?=?await?store.dispatch('permission/generateRoutes',?roles)
?//?動態(tài)添加路由?(將基本的路由信息跟動態(tài)路由進行合并)
?router.addRoutes(accessRoutes)
?//?繼續(xù)訪問
?next({?...to,?replace:?true?})
?}?catch?(error)?{
?//?刪除token
?await?store.dispatch('user/resetToken')
?Message.error(error?||?'Has?Error')
?//?重定向到登錄頁面
?next(`/login?redirect=${to.path}`)
?NProgress.done()
?}
?}
?}
?}?else?{
?//?沒有token
?if?(whiteList.indexOf(to.path)?!==?-1)?{
?//?如果在白名單中,則不需要進行任何校驗,直接放行
?next()
?}?else?{
?//?如果不存在于白名單中,則重定向到登錄頁面.
?next(`/login?redirect=${to.path}`)
?NProgress.done()
?}
?}
})
?router.afterEach(()?=>?{
?//?finish?progress?bar
?NProgress.done()
})
注意到,代碼中的/login?redirect=${jto.path}, 這里的 redirect 參數(shù)主要是用于,在用戶登錄成功后進行跳轉(zhuǎn)的頁面路徑。具體功能在/views/login/index.vue 文件下
watch:?{
?$route:?{
?handler:?function(route)?{
?const?query?=?route.query
?if?(query)?{?//路由查詢參數(shù)
?this.redirect?=?query.redirect
?this.otherQuery?=?this.getOtherQuery(query)
?}
?},
?immediate:?true
?}
},
// methods下的:
handleLogin()?{?//?登錄函數(shù)
?this.$refs.loginForm.validate(valid?=>?{
?if?(valid)?{?//?賬號密碼校驗成功后
?this.$store.dispatch('user/login',?this.loginForm)
?.then(()?=>?{
?//?直接跳轉(zhuǎn)到this.redirect?路徑的頁面
?this.$router.push({?path:?this.redirect?||?'/',?query:?this.otherQuery?})
?this.loading?=?false
?})
?}?else?{
?//?..
?}
?})
},
動態(tài)路由配置
我們先來看看路由的定義,在/src/router/index.js 文件下
export?const?constantRoutes?=?[?//?用來定義普通的路由配置,不需要訪問權(quán)限的
?//?路由配置對象
]
export?const?asyncRoutes?=?[?//?通過路由元信息meta.roles來設(shè)置訪問權(quán)限,一般來說是個數(shù)組
?{
?path:?'/permission',
?component:?Layout,
?redirect:?'/permission/page',
?alwaysShow:?true,?//?will?always?show?the?root?menu
?name:?'Permission',
?meta:?{
?title:?'Permission',
?icon:?'lock',
?roles:?['admin',?'editor']?//?通過roles設(shè)置路由的權(quán)限
?},
?//?...
?}
]
動態(tài)添加路由時,本質(zhì)上就是根據(jù)用戶的角色信息在 asyncRoutes 路由配置數(shù)組中進行路由篩選,找到相對應(yīng)的路由,與 constantRoutes 合并生成最新的路由。
動態(tài)添加路由邏輯圖

動態(tài)路由生成.png
動態(tài)路由源碼分析
代碼入口: permission.js
const?accessRoutes?=?await?store.dispatch('permission/generateRoutes',?roles)
permission/generateRoutes 方法入口文件:/strore/modules/permissions.js
import?{?asyncRoutes,?constantRoutes?}?from?'@/router'
const?state?=?{
?routes:?[],
?addRoutes:?[]
}
?const?mutations?=?{
?SET_ROUTES:?(state,?routes)?=>?{
?state.addRoutes?=?routes
?state.routes?=?constantRoutes.concat(routes)
?}
}
?const?actions?=?{
?generateRoutes({?commit?},?roles)?{
?return?new?Promise(resolve?=>?{
?let?accessedRoutes
?if?(roles.includes('admin'))?{
?//?如果包含了admin,則說明是admin,具有所有模塊的訪問權(quán)限
?accessedRoutes?=?asyncRoutes?||?[]
?}?else?{
?//?如果不是管理員,則需要根據(jù)用戶角色roles和異步路由進行篩選
?accessedRoutes?=?filterAsyncRoutes(asyncRoutes,?roles)
?}
?//?將最終的結(jié)果存放到vuex中
?commit('SET_ROUTES',?accessedRoutes)
?//?resolve出去
?resolve(accessedRoutes)
?})
?}
}
對異步路由進行篩選,并將最終的結(jié)果存放到 vuex 中,并將結(jié)果 resolve 出去
export?function?hasPermission(roles,?route)?{
?if?(route.meta?&&?route.meta.roles)?{?//?如果存在meta.roles
?//?只要meta.roles中存在與用戶角色列表中相同的值,則說明具有訪問權(quán)限
?return?roles.some(role?=>?route.meta.roles.includes(role))
?}?else?{
?//?不存在meta或者是不存在meta.roles,則說明是通用模塊,直接放行
?return?true
?}
}
?export?function?filterAsyncRoutes(routes,?roles)?{
?const?res?=?[]
?routes.forEach(route?=>?{
?const?tmp?=?{?...route?}
?if?(hasPermission(roles,?tmp))?{?//?相對路由數(shù)組的每一項進行訪問權(quán)限的判斷
?if?(tmp.children)?{
?//?如果存在children,則遞歸調(diào)用篩選函數(shù)
?tmp.children?=?filterAsyncRoutes(tmp.children,?roles)
?}
?//?將處理好的路由配置放入到res中
?res.push(tmp)
?}
?})
?return?res
}
最后回到/permission.js 文件中
const?accessRoutes?=?await?store.dispatch('permission/generateRoutes',?roles)
//?這里的accessRoutes就是篩選之后的路由,
//?最后通過route.addRoutes將constRoutes和accessRoutes進行合并,生成最終的訪問路由
router.addRoutes(accessRoutes)
擴展-按鈕權(quán)限
路由權(quán)限控制基本流程已經(jīng)分析完,接下來我們也來看看項目里的按鈕權(quán)限控制的實現(xiàn),實現(xiàn)也比較簡單。
基本用法
"['admin','editor']">
import?store?from?'@/store'
?function?checkPermission(el,?binding)?{
?const?{?value?}?=?binding
?//?從store中拿到我們訪問接口后,取到用戶角色信息
?const?roles?=?store.getters?&&?store.getters.roles
?if?(value?&&?value?instanceof?Array)?{?//?判斷傳入的值是不是數(shù)組,規(guī)范化傳值
?if?(value.length?>?0)?{
?const?permissionRoles?=?value
?//?只要傳入的permissionRoles中,包含了roles其中的一個值即可,則代表有權(quán)限
?const?hasPermission?=?roles.some(role?=>?{
?return?permissionRoles.includes(role)
?})
?//?沒有權(quán)限則進行刪除,不展示。
?//?v-permission具體實現(xiàn)可以根據(jù)業(yè)務(wù)場景進行修改
?if?(!hasPermission)?{
?el.parentNode?&&?el.parentNode.removeChild(el)
?}
?}
?}?else?{
?throw?new?Error(`need?roles!?Like?v-permission="['admin','editor']"`)
?}
}
?export?default?{
?inserted(el,?binding)?{
?checkPermission(el,?binding)
?},
?update(el,?binding)?{
?checkPermission(el,?binding)
?}
}
總結(jié)
存在 token 如果是管理員則對所有模塊具有訪問權(quán)限 非管理員,需要對異步路由進行篩選,通過遍歷異步路由,并通過 meta.roles 與用戶信息比較,判斷用戶是否具有訪問權(quán)限 存在用戶角色信息,則說明該用戶的最終可訪問的路由已經(jīng)生成,可以直接放行 不存在用戶信息 調(diào)用獲取用戶信息接口,獲取到用戶信息, 將用戶信息存放到 vuex 中 判斷用戶角色 將最終的可訪問路由存放到 vuex 中,最后通過 router.addRoutes,整合最后的路由配置列表 不存在 token 如果訪問路由在白名單下,則直接進行訪問 訪問路由不存在白名單下,則重定向到登錄頁面 path: /login?redirect=/xxx,登錄成功后則跳轉(zhuǎn)到/xxx 對應(yīng)的頁面
專注分享當(dāng)下最實用的前端技術(shù)。關(guān)注前端達人,與達人一起學(xué)習(xí)進步!
長按關(guān)注"前端達人"

