權限管理是很多管理系統常見的需求,說起來這是一個比較簡單的功能,但是每個人在實現的時候,往往都是以滿足自己的眼前需求爲動機,不太考慮整體的設計,導致使用不友好,拓展不方便,甚至代碼層面也是千奇百怪,使用各種方式的都有。
遙想幾年前,我還是實習生的時候,實現自己的第一個權限方面的需求,採用的是將用戶的角色存儲在localStorage裏面,然後前端代碼根據這個角色來判斷隱藏菜單欄和一些按鈕。後端壓根沒有權限表,只有角色表。
這樣的做法現在來看是很不好的,最關鍵的是相當於將權限寫死在前端了。後來,我做其他項目的時候,曾經採用前後端交互,然後前端在最開始的時候調用權限接口,然後前端再做了一大堆處理接口返回數組的邏輯。現在來看,這樣做還是有些問題,把簡單的事情複雜化。
其實,權限模塊可以做的完全動態化,可拓展,可編輯,使用友好。最關鍵的是,下次再有後端/前端兄弟和你討論如何實現權限功能,你可以按照本文介紹的大致設計告訴他,省了很多撕逼的時間。
本文前端以Vue爲例子來進行說明,後端從字段和設計表方面來說明。
首先,我們在項目裏配置好路由(這一步也可以是一部分前端提前配置,一部分取後端返回的數據渲染路由),這裏我們就全部由前端配置好了。
{
path: '/',
name: 'layout',
redirect: 'dashboard',
component: () => import('@/views/layout/Layout'),
children: [
{
path: 'dashboard',
name: 'dashboard',
component: () => import('@/views/dashboard/index')
},
{
path: 'job',
component: () => import('@/views/task/index'),
children: [
{
path: '',
component: () => import('@/views/task/TaskList'),
},
{
path: 'create-task',
name: 'create-task',
component: () => import('@/views/task/TaskForm'),
}
]
},
{
path: 'config',
component: () => import('@/views/config/index'),
children: [
{
path: 'task-type',
component: () => import('@/views/config/taskType/index'),
},
{
path: 'service',
component: () => import('@/views/config/serviceConfig/index'),
},
{
path: 'auto-scaling',
component: () => import('@/views/config/autoScalingConfig/index')
},
{
path: 'handler',
component: () => import('@/views/config/handlerConfig/index')
},
{
path: 'schedule',
component: () => import('@/views/config/detectConfig/index')
},
{
path: 'template',
component: () => import('@/views/config/templateConfig/index'),
},
{
path: 'context-keys',
component: () => import('@/views/config/contextKeys/index')
},
{
path: 'task-alert',
component: () => import('@/views/config/taskAlert/index')
}
]
},
]
},
{
path: '/others',
component: () => import('@/views/others/index'),
children: [
{
path: 'release',
component: () => import('@/views/others/Release'),
}
]
},
{ path: '*', redirect: '/404', hidden: true },
{ path: '/404', component: () => import('@/views/error/404'), hidden: true },
{ path: '/401', component: () => import('@/views/error/401'), hidden: true },
{
path: '/login',
component: () => import('@/views/login/index'),
name: 'login',
},
我們要明白,路由配置到底是前端配置好,還是取接口返回數據進行渲染,並不是很Matter的問題,因爲我們要看重的是菜單欄和用戶輸入路由進入的時候的權限。
然後,我們需要有一個菜單管理的頁面,在菜單管理頁面,新增菜單的時候,我們把菜單分爲兩部分,一部分是菜單欄的目錄和菜單,也就是頂部或者側邊展示的菜單欄;一部分是後端接口(包括前端的增刪改查按鈕,也有其他的很多接口)。這樣子就完全滿足任意的需求了,因爲後端的接口權限也在這裏直接可以編輯配置。
新增頁面的字段大概如下:
後端根據菜單排序來返回數組的順序,前端直接根據接口返回的數組渲染菜單欄。
路由地址就是前端配置好的路由裏面的path的值,前端的多語言也根據這個字段作爲詞典即可。
是否可見,我這裏的設計是因爲有些沒有子菜單的菜單,但是它也許會有子頁面(通過$router.push方法進入),我的判斷是如果它有children數組而且children的類型爲菜單的可是否見均爲false的話,就意味着在菜單欄裏它是沒有子菜單的。
功能也就是後端接口(包括前端的每個頁面的增刪改查):
上級層級就是我們新增出來的一個個目錄:
這個樹形選擇框是使用的vue-treeselect。挺好的一個vue工具。
這樣子,後端就使用一張菜單表,就可以滿足需求了。根據選擇的層級目錄ID,把它放到正確的children數組裏,不用再去搞亂七八糟的權限表,權限模塊表等等,本來問題就是這麼簡單。
菜單表的list如下:
這裏的Job就是沒有子菜單的菜單,但是它裏面是有子頁面的。
然後我們新建角色表,就可以給角色分配菜單和接口的權限了:
之後我們把角色表和具體的用戶關聯起來即可。這裏,我們既可以分配菜單,也可以分配接口,同時,頁面的具體按鈕可以根據是否勾選了接口來決定是否展示。沒有勾選的,後端不需要返回給前端。
這樣的設計即清晰明瞭,實現起來也簡單,而且完全動態化,可編輯,對管理員來說,交互也比較方便。
至此,後端的設計基本就結束了。我們完成了菜單管理,角色管理。現在前端需要考慮如何動態的渲染菜單欄,以及在用戶輸入地址的時候,如何判斷他是否可以進入。如果不可以進入,我們應該跳轉無權限頁面。