一、前言
你不知道的前端之——中後臺系統的許可權控制,總的來說可以分為兩大類,頁面許可權與按鈕許可權。
如果說程式設計的職業道路就像是在遊戲中打怪升級的話,那麼走這個分支練級的童鞋,首先要面對的第一隻勸退boss就是——許可權控制了。
- 本文適合什麼樣的群眾來閱讀?
本文適合小白玩家,或者手殘黨,將要進軍這分支(中後臺),以及各路大神,操作黨,歐皇黨(坐在家裡,咦,專案完成了!)等童鞋們食用哈,如果在食用的過程中,若感不適,敬請諒解。
- 閱讀完本文後你可以學到什麼?
- 對於前端的許可權控制會有更深的理解。
- 飯後閒時,故事看一看。
- 這篇章怎麼寫的?
是我我那微不足道(6個月)的中後臺系統的經驗和理解,以及一些開源的中後臺系統解決方案中借鑑然後消化完以自己的話描述出來的。
以下章節我將以輕鬆愉快且通俗易懂的例子相輔相成進行講解。
二、概念介紹
玩一款遊戲,是不是得先對遊戲背景、設定、技能、人物等有所瞭解呀?
主要就這三個玩意:
- 1.中後臺系統
- 2.許可權控制
- 3.Vue-router
本文通篇以Vue為基礎講解。
2.1 中後臺系統
As we konw, 中後臺系統又稱中後臺管理系統,既然有管理兩個字,那麼許可權控制就是必不可少的了。
讓我們來看下它的一些特性
- 中後臺系統主要由大量的表單、表格、彈窗、按鈕組成。(開發過的童鞋就知道啦)
- 中後臺系統主要給一些政府或非政府部門、單位、企業使用,也就是說它是to B端的。
- 中後臺系統頁面主要佈局為左側sideBar(選單頁面),頂部header(麵包屑,網站標題、logo、使用者中心),中間AppMain組成(主要頁面顯示)。
鬼畜畫風
目前來說,中後臺系統的開發模式應該都是傳送(前後端分離的模式)了,應該不會有用腿跑圖(遠古級別)的出現了。
2.2 許可權控制
前面章節介紹了中後臺系統的一些基礎認識,接下來講講許可權控制。
2.2.1 許可權控制分類
許可權控制主要可以分為以下兩大類:
- 1.頁面許可權
- 2.按鈕許可權
2.2.2 什麼是許可權控制呢?
既然是管理系統,那麼必然有不同的許可權,舉個例子: 你去圖書館借書的時候,你擁有借閱的許可權,圖書館的工作人員擁有管理圖書和出借的許可權。
在這個案例中,按照遊戲的模式去理解,你有一個技能,而圖書館的工作人員有多個技能,這就是許可權的不同。
迴歸正題,在中後臺系統中,許可權的控制最明顯是對頁面的控制,也就是說在左側的sideBar中,管理員它有三十個選單頁面可以點選,而普通使用者只有十個選單頁面可以點選。
那麼要怎麼做到這一步,進行頁面許可權控制呢?
2.2.3 小怪擋道關卡(從功能點做為切入點)
頁面許可權控制具體有哪些功能要實現呢?以上面章節舉的例子來說:
-
- 管理員有三十個頁面(過路費30金幣)
-
- 普通使用者只有十個頁面(過路費10金幣)
很清晰了是不是?我們只要實現以上兩點,這不就o**k了。hh。
O right,讓我們接著來哈。
很簡單嘛,讓我們用兩個錢袋子(變數)把這些金幣分開存起來,如果是管理員小boss擋路了,我們拿出準備好的那個裝有30金幣的小袋子給它;來的若只是一隻小兵,我們們給它10金幣那個袋子就好了。
具體對應的頁面和路由我們們先不討論,就功能點做個判斷。
我們們順利解決了這第一道關卡,名曰:小怪擋道。接著往下探圖哈。
2.2.4 挖寶石關卡(按鈕許可權一覽)
過了上面那關小怪擋道後,是不是覺得自己的大寶劍飢渴難耐了,忍不住拿出擦亮些。
先別急哈,我們們要致富先挖路。呸!! 不對哈,先挖寶石呀。
假設你進入了一個藏有很多寶石的房間,裡面各個角落埋藏著許多價值連城的寶石,擁有一顆你就可以富可敵國了。是不是按捺不住激動的心、顫抖的手!!!(房間很大,光線不足)
這個時候,房間的機關突然被你觸發了,一陣機關齒輪的聲響過後,你還以為自己應該是掛了,沒成想在牆面上掛著的破二鍋頭瓶子中突然冒出一縷青煙,沒等你有所動作,青煙幻化成一糟老頭,露出它那少了一個門牙的大嘴巴子怪笑著對你說道:‘小子,你是誰?’。正當你要回答時,‘...,我想起來了,來到這裡的人,都是貪婪的人,都想要挖走寶石,小子就且看你運氣如何了,運氣好的能帶走一些,不好的,嘿嘿...都留下來陪我吧!’那糟老頭子歪著漏風的嘴對你嘲諷後就消失了...
你開始迷惑起來,但慾望使你腎上腺素上升,你心跳加速,因為前面不遠處就有一塊很大且泛著藍色光茫的寶石。你蹲下來,準備拿起,兩手一抓,但是寶石就是拿不起來,無論你使出多大的勁。過了不一會,你滿臉通紅,汗流直下,精疲力盡,氣喘吁吁,一屁股做倒在地上,‘呼呼...’的喘著大氣。突然,一陣笑聲傳來,‘哈哈哈,笨蛋,笨蛋...’ 你被這笑聲弄的十分憤怒,不知是哪裡來的力氣,伸手往後一抓,不知拿了一個什麼東西,就往那笑聲處一砸!‘碰!’那笑聲戛然而止,你仰頭倒在地上,大口大口的喘著氣,休息了很久,起身往剛才那笑聲處走去,待你走前一看,地上空空如也,只有一顆綠色的寶石流淌著那說不清道不明的顏色的光,如鴿子蛋大小橫躺在地上。你撿起後,突然明白了什麼:
- 1.剛才隨手一抓的東西是寶石(幸好沒丟)
- 2.為什麼藍色的那塊寶石拿不起來?(好氣)
- 3.為什麼這個能夠拿起的寶石,它的顏色...(嗯*****)
開始解析哈,藍色寶石能夠看見卻拿不來,其實對應的是一個按鈕可以看見,卻不能點選(禁用狀態)。蜜汁綠色寶石,對應的是另一個按鈕,你可以看見且能夠點選(觸發事件)。至於它的顏色嘛,嗯...無法解釋,只有一句話:要想生活過得去,頭上必須帶點綠。
在你撿起這塊寶石不久後,又是一頓‘機關齒輪轉動的聲響’過後,在你的正前面,出現了一束光,順著光前去,你發現原來是一道門。正在你猶豫要不要走過去的時候,你的身子突然被什麼東西撞了一下,你跌倒在門外,還沒等你重新站起來,門‘碰’的一聲關上了!你站起身來,轉頭向門上看去,這扇門是那種有年份的木門,門上貼了一張紙:
- 上批:慾望之門。
- 內容:這道門後關著人們的慾望,那些五彩斑斕的寶石,就是每個人目前的慾望。能夠被你拿起的寶石是通過你的努力能夠獲得的;拿不起的便是你沒有能力卻心生貪婪和慾望想要得到的東西;而最令人恐怖的是那一望無邊的黑暗,那是你無止盡的慾望所生成的最黑暗最深不見底的大恐怖。
解析這張紙的內容,共分為三個觀點,分別對應:
- 1.有點選許可權的按鈕(能看見按鈕且可以觸發事件);
- 2.無點選許可權的按鈕(頁面渲染此按鈕,但你不可以觸發事件);
- 3.深不見底的大恐怖(你沒有檢視和點選許可權的按鈕)
故事好像講多了,哈哈,後面在專門出個完整故事版的專欄。
2.3 頁面許可權
從現在開始,講解程式碼啦!
2.3.1 從前端路由說起
首先你得對路由有所認知——vue-router。
重點掌握:
- 全域性前置守衛
- router.addRoutes
全域性前置守衛
你可以使用 router.beforeEach 註冊一個全域性前置守衛
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
複製程式碼
使用全域性前置守衛去作為一個攔截器這樣的角色,攔截每一次的路由跳轉變化,在去下一個路由之前,判斷使用者是否已登入,token
是否存在。反之,自動跳轉到登入頁面。
addRouters
// 函式簽名
router.addRoutes(routes: Array<RouteConfig>)
// 動態新增更多的路由規則。引數必須是一個符合 routes 選項要求的陣列。
複製程式碼
更多vue-routerd的內容請自行前往官網查閱。
完整登入流程圖
addRouters
可以在整個程式(中後臺系統)執行期間,動態的新增更多的路由頁面(只要引數符合規範)。意味著,可以在使用者登入期間,傳送登入請求給後臺時,後臺驗證賬號密碼等資訊正確,然後生成token
,生成該使用者的許可權表,一起帶回給前端。然後前端接收到響應之後,儲存token
, 使用者許可權表等資訊到session Storage和store(Vuex)中。接著將獲取到的後端使用者許可權表中的頁面許可權與前端的路由元件對映到一起,從而生成動態可訪問的頁面,然後通過addRouters
作用到整個程式的生命週期當中,最後跳轉到使用者可訪問頁面。一個完整的登入流程便結束了它年輕的生命,貢獻了許多默默無聞。
上面這一步便是完成了管理員身份擁有三十個頁面,而普通使用者身份只有十個頁面的功能點。
2.3.2 談一談登入流程(幾個頗為重要,容易坑的點)
1.在全域性前置守衛中新增路由白名單。
// 在白名單中的路由放行
const whiteList = ['/login']
複製程式碼
不然你連登入頁面都進不去。
2.在全域性前置守衛中的邏輯要合理,不然容易出現死迴圈。(說多是都是淚水和坑。)
3.後端使用者許可權表中的頁面許可權與前端的路由元件對映這一步怎麼操作?
在你成功登入後,拿到了後端生成的使用者許可權表資料後,你滿心歡喜,卻不知你依舊稚嫩。
前端的路由元件集合,我一般放在router檔案中的index.js,然後匯出使用:
export const routerMap = {
layout: () => import('@/views/layout/Layout'),
dashboard: () => import('@/views/dashboard/index'),
center: () => import('@/views/center/index'),
...省略更多
}
複製程式碼
我拿到的後端使用者頁面許可權表是下面這個樣子的物件:
{
path: '/dashboard',
name: 'dashboard',
component: 'dashboard', // 替換著一項內容
id: 11,
meta: {
title: '首頁',
icon: 'iconfont iconHome'
}
}
複製程式碼
主要就是替換掉component這個屬性,方法如下:
// 對映,需要遞迴判斷二級選單、三級選單
function generateAsyncRouter(routerMap, serverRouterMap) {
const { length = 0 } = serverRouterMap
for(let i = 0; i < length; i++) {
const curRouter = serverRouterMap[i]
curRouter.component = routerMap[curRouter.component]
const { children = [] } = curRouter
const { length:len } = children
if (children && len) {
generateAsyncRouter(routerMap, children)
}
}
return serverRouterMap
}
複製程式碼
具體使用:
import routerMap from '@/router'
// 省略中間邏輯
// serverRouterMap是後端返回的具體某個使用者的頁面許可權表
const asyncRouterMap = generateAsyncRouter(routerMap, serverRouterMap)
router.addRoutes(asyncRouterMap) // 動態新增可訪問路由表
router.push({ ...to, replace: true }) // hack方法 確保addRoutes已完成
複製程式碼
做完這些步驟,動態可訪問路由頁面的功能點其實就完成了,當然還有在全域性前置守衛中的判斷邏輯,這我就不上程式碼了,主要根據你的業務需要,先判斷
token
,再判斷頁面許可權表,然後放行。如果沒有這些就跳轉到登入頁面。大致的步驟是這樣的。
2.4 按鈕許可權
做完了頁面路由許可權控制其實已經完成了一大半了,但是我們公司需要對許可權的控制更加變態,精確到每一個按鈕的顯示與否、禁用與否。
其實只要在按鈕上加個if判斷不就完事了,但是太麻煩,需要寫太多重複的if判斷邏輯程式碼,可讀性很差,維護起來也十分噁心。
於是乎有了以下的解決方案:
-
- 封裝一個v-auth指令作用在元素上。
-
- 結合此指令進一步封裝按鈕元素。
2.4.1 v-auth 按鈕許可權
通過vue的自定義指令功能,封裝一個按鈕許可權判斷指令。
這個是vue-element-admin(中後臺前端解決方案)開源的一個封裝檔案:
import store from '@/store'
export default {
inserted(el, binding, vnode) {
const { value } = binding // 取出指令的值,指的是v-auth="['admin']"中的'admin'這個值
const roles = store.getters && store.getters.roles // 從store中取出儲存好的後端給的許可權表
if (value && value instanceof Array && value.length > 0) { // 判斷value是否有值,型別是否正確
const permissionRoles = value
// 只要當前許可權名稱在許可權表roles中有一個匹配項就返回true
const hasPermission = roles.some(role => {
return permissionRoles.includes(role)
})
if (!hasPermission) {
// 如果沒有許可權就移除這個按鈕元素
el.parentNode && el.parentNode.removeChild(el)
}
} else {
// 型別錯誤,丟擲異常
throw new Error(`need roles! Like v-permission="['admin','editor']"`)
}
}
}
複製程式碼
以上程式碼我都逐行註釋了,閱讀起來應該很簡單。功能就是實現了按鈕的顯示與否,具體的判斷是否有這個許可權的邏輯,你可以根據你的業務需要去改就好了。
但是,以上程式碼,並不能滿足我按鈕禁用與否,所以我依舊需要進一步改進。
2.4.2 改進後的指令
// 指令部分
inserted(el) {
// 這部分要處理獲取當前頁面當前使用者的所有按鈕許可權表,這個根據實際業務去獲取
// 假設我獲取了當前頁面當前使用者按鈕禁用許可權表
const pageBtnPermisson = {
btn1: 0, // 隱藏按鈕
btn2: 1, // 顯示但禁用
btn3: 2 // 顯示不禁用
}
// 設定樣式,偷懶直接使用了element-ui寫好的button的class
const BTNPRIMARYSTYLE = 'el-button--primary'
const BTNMEDIUMSTYLE = 'el-button--medium'
const BTNBASISSTYLE = 'el-button'
const BTNDISSTYLE = 'is-disabled'
// 公用樣式設定
el.className = `${BTNBASISSTYLE} ${BTNMEDIUMSTYLE} ${BTNPRIMARYSTYLE}`
// 判斷禁用、顯示
const { dataset = {}} = el
const { name = '' } = dataset
if (dataset && name) {
switch (pageBtnPermisson[name]) {
case 0:
el.parentNode && el.parentNode.removeChild(el)
break
case 1:
el.className += ` ${BTNDISSTYLE}`
el.disabled = true
break
case 2: break
}
}
}
複製程式碼
// 頁面部分
<el-row style="margin-top: 33vh;text-align:center;">
<button v-auth data-name="btn1" @click="handleClick">我是按鈕1</button>
<button v-auth data-name="btn2" @click="handleClick">我是按鈕2</button>
<button v-auth data-name="btn3" @click="handleClick">我是按鈕3</button>
</el-row>
複製程式碼
實際效果如下:
跟我們預想的一樣,第一個按鈕被移除,第二個顯示但是禁用,第三個顯示且可用。實測點選我是按鈕2
並不會觸發handleClick
事件,只有點選我是按鈕3
才會觸發。檢視dom元素,並沒有我是按鈕1
的元素存在。所以達到了我們的需求。
2.4.3 原理分析
- 1.通過原生button元件的原生屬性
disabled
做禁用功能(配合樣式)。 - 2.通過v-auth指令去幫我們做一些重複的事情。
- 3.至於獲取當前使用者當前頁面的所有按鈕許可權表的邏輯這部分程式碼,需要你跟後端、產品一起敲定整個頁面+按鈕許可權表如何設計。
三、後話
本文就前端——許可權控制為主題,展開分析整個中後臺管理系統的概念組成原理,包括登入流程,許可權型別劃分,還是乾貨滿滿的,自我感覺良好。
每個專案的需求都不一樣,但是萬變不離其宗,我們們把它破開來,裸著看,就清清楚楚了,我在這裡只是提供了一些思路,僅供參考和閱讀,吃瓜群眾路過點個小心心哈,2019年最後一天感激不盡啊!
如何你有了更好的許可權控制的方案,不妨留言啊,我們一起討論,一起進步,一起成長!
最後附上本人聯絡方式:
- 1.留言哈
- 2.留言哈
- 3.留言你就知道了
- 4.重要的事情說多遍,留言+點贊
開玩笑啦,本人wechat: