一. 前言
本篇基於 有來商城 youlai-mall 微服務專案,通過對vue-element-admin的許可權選單模組理解個性定製其後臺介面,實現對vue-element-admin工程幾乎不做改動的情況下,無縫接入後臺介面實現動態許可權選單的載入。
在進行接下來的工作前,我們需要對原生的vue-element-admin專案改造,移除mock連通後臺介面,具體可參考我這篇文章 vue-element-admin實戰 | 第一篇: 移除mock接入後臺,搭建有來商城youlai-mall前後端分離管理平臺,如果在過程中有遇到問題,歡迎下方留言。
二. 前端調整
至於上文提到的對vue-element-admin幾乎不做改動便可實現我們此篇文章的目的是不是我在扯,決定權給各位,我把對vue-element-admin專案改動的地方通過比對工具比對截圖放上來。
先宣告vue-element-admin此次改動的地方除了一個獲取許可權選單的介面之外,剩餘的改動全在 src/store/modules/permission.js 檔案中。
看到了嗎,可以說僅對vue-element-admin做兩處改動,再加上對getRoutes呼叫後臺介面返回的選單資料的分析,就可以理解和實現動態許可權選單的載入了。
改動程式碼片段 + 註釋說明
import {list as getRoutes} from '@/api/admin/menu'
import Layout from '@/layout'
generateRoutes({commit}, roles) {
return new Promise(resolve => {
// 請求後臺資料替換src/router/index.js的asyncRoutes非同步路由
getRoutes({mode: 3}).then(response => {
// filterAsyncRoutes方法作許可權過濾和資料轉換,roles為登入使用者角色ID集合,如:[1,2]
let accessedRoutes = filterAsyncRoutes(response.data, roles)
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
})
})
}
// 遞迴許可權過濾和資料轉換
export function filterAsyncRoutes(routes, roles) {
const res = []
routes.forEach(route => {
const tmp = {...route}
if (hasPermission(roles, tmp)) {
const component = tmp.component
if (route.component) {
if (component == 'Layout') {
tmp.component = Layout
} else {
// 介面元件字串轉換成元件物件
tmp.component = (resolve) => require([`@/views/${component}`], resolve)
}
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
}
res.push(tmp)
}
})
return res
}
三. 後端介面
1. 介面資料分析
接下來通過後臺介面替換配置在src/router/index.js檔案中asyncRoutes非同步路由。
首先分析下非同步路由的資料結構:
通過對上圖非同步路由的資料觀察和了解,得出以下幾點:
- path在子路由中前面沒有反斜槓'/'
- 根選單的alwaysShow為true
- component元件需通過import完成編譯時匯入,介面只能返回元件路徑字串,所以這裡在介面請求完成後必須有一個元件路徑字串到元件物件的轉換過程
- meta的roles屬性對應的是有該路由訪問許可權角色唯一標識的集合,這裡我使用的是角色ID
2. 介面實現
完成以上的資料分析,接下來就是介面的具體實現了,有關SQL和完整程式碼請點選 有來商城 youlai-mall 拉取即可,以下僅僅貼出關鍵程式碼和分析。
SysMenuMapper
獲取選單列表和對應訪問許可權的角色ID的集合
SysMenuServiceImpl
將選單轉換成路由,遞迴生成父子結構樹
SysMenuController
REST對外提供介面
3. 介面測試
使用介面測試工具測試, http://localhost:9999/youlai-admin/menus?mode=3 ,這裡通過閘道器方式訪問,詳情請知悉有來商城 youlai-mall 專案。
完整返回資料,看看是不是很匹配asyncRoutes非同步路由的資料格式了
{
"code": "00000",
"data": [{
"path": "/admin",
"component": "Layout",
"alwaysShow": true,
"name": "系統管理",
"meta": {
"title": "系統管理",
"icon": "documentation",
"roles": [2, 1]
},
"children": [{
"path": "user",
"component": "admin/user",
"alwaysShow": false,
"name": "使用者管理",
"meta": {
"title": "使用者管理",
"icon": "user",
"roles": [1]
}
}, {
"path": "role",
"component": "admin/role",
"alwaysShow": false,
"name": "角色管理",
"meta": {
"title": "角色管理",
"icon": "peoples",
"roles": [2, 1]
}
}, {
"path": "dept",
"component": "admin/dept",
"alwaysShow": false,
"name": "部門管理",
"meta": {
"title": "部門管理",
"icon": "tree",
"roles": [1, 2]
}
}, {
"path": "menu",
"component": "admin/menu",
"alwaysShow": false,
"name": "選單管理",
"meta": {
"title": "選單管理",
"icon": "tree-table",
"roles": [1, 2]
}
}, {
"path": "dict",
"component": "admin/dict",
"alwaysShow": false,
"name": "字典管理",
"meta": {
"title": "字典管理",
"icon": "education",
"roles": [1, 2]
}
}]
}],
"msg": "一切ok"
}
最後要做的就是將元件(component)路徑字串轉換成元件物件即可,再次貼出在上文permission.js改造的程式碼:
if (component == 'Layout') {
tmp.component = Layout
} else {
// 介面元件字串轉換成元件物件
tmp.component = (resolve) => require([`@/views/${component}`], resolve)
}
有一點需要注意的是上面元件動態匯入不能使用import,如下:
tmp.component = () => import(`@/views/${component}`)
不然會報以下錯誤
Error: Cannot find module '@/views/***'
原因是webpack不支援import動態匯入了,但是之前確實是可以的,所以有些讓人想不通,這裡使用require動態匯入元件即可。
4. 選單許可權測試
參看當前使用者擁有的角色ID為2
使用者管理要求只有角色ID為1才能有其訪問許可權
測試下使用者登入控制檯能否看到“使用者管理”選單
怎麼樣,只擁有角色ID為2的角色是看不到需要角色ID為1才能訪問的“使用者管理”選單的,也就證明了我們成功通過接入後臺介面實現了許可權選單的動態載入的目的。
四. 結語
其實有個問題值得去思考下的。為什麼我們在從後臺獲取全部選單列表的時候需要關聯查詢出有對應訪問許可權的角色ID的集合,而不是說動態傳入當前登入使用者的資訊去查詢該使用者擁有的選單集合呢?這其實是兩種實現方式,區別在於第一種每個使用者都要獲取全部選單資料,而第二種方式針對每個使用者載入其擁有許可權的選單資料。單看的話第二種方式肯定優於第一種,但這裡我們選用的是第一種載入全部選單資料。如果你結合快取去看的話自然就想的通了,所有使用者共享快取的同一份選單資料,還是說快取針對每個使用者存放一份選單快取又或者使用者每次都去請求資料庫。後面計劃把選單資料存放到快取Redis中然後做一個分析比較。
附完整程式碼:
寫了這麼多其實也是想給自己的專案打個廣告,更希望對大家有所幫助,對於技術人來說少走彎路真的很重要,喜歡的朋友給個star,對我來說這份幫助更是自己繼續下去的動力,所以謝謝了。有啥問題下方留言,或直接聯絡我(微訊號:haoxianrui)。