歡迎關注我的公衆號睿Talk
,獲取我最新的文章:
一、前言
在成熟的電商系統中,權限管理是不可或缺的一個環節。靈活的權限管理有助於管理員對不同的人員分配不同的權限,在滿足業務需要的同時保證敏感數據只對有權限的人開放。筆者最近對系統的權限管理做了一次改造,在此分享一些經驗以供參考。
二、權限管理基礎
權限管理一版分以下 3 個基礎概念:
- 功能點
- 角色
- 用戶
它們之間的關係一句話就能說清楚:一個用戶可以擁有多個角色,而一個角色可以包含多個功能。比如一個員工可以既有收銀員的角色,也可以有庫管員的角色。對於收銀員這個角色,可以有開單收銀、查看訂單、查看會員信息等功能點。
此外還有 2 個概念:
- 功能權限
- 數據權限
它們之間的關係舉例來說明:
想象一個連鎖店的場景,某個門店的管理員具有查看營收的功能權限,和查看自己門店數據的數據權限;高級管理員同樣擁有查看營收的功能權限,和查看所有門店數據的數據權限。
三、前端權限控制
下面我們聚焦到前端領域,聊聊前端應該怎麼做權限設計。前端本質上只有 2 種權限:頁面權限和組件權限。爲了更好理解,我將菜單權限從組件權限中拆出來,形成了以下 3 類的權限:
每一個權限最終都會落到權限點上。權限點可以理解爲一個編碼,有這個權限點就說明有對應的功能權限。權限點的編碼要注意 2 點:
- 全局唯一
- 儘量短小(減少帶寬消耗,因爲一個用戶可能會有很多權限點)
需要控制權限的地方,都要定義一個權限點,然後告訴後端。一個用戶所有的權限點會以數組的形式返回。判斷是否有權限就是從數組中匹配一個元素。下面以 React 爲例,聊聊具體的實現方式。
對於頁面的權限判斷,可以在 React Router 的 onEnter 回調中判斷:
// 編碼映射,下面的 getUrlCodeByName 會用到
export default {
order_list: 'zaq0', // 訂單列表
order_detail: 'xsw1', // 訂單詳情
order_refund_list: 'cde2', // 訂單退款列表
order_refund_detail: 'vfr3', // 訂單退款詳情
order_deduct_modify: 'bgt4', // 訂單修改業績
};
function canAccessUrl(urlName) {
const moduleCode = getUrlCodeByName(urlName); // 權限點一般是一個沒意義的編碼,爲了更易於理解,前端做了一個編碼映射
return accesses.u.indexOf(moduleCode) > -1; // accesses.u 數組是後端返回的所有 url 權限點
}
function routerOnEnterCheck(urlName) {
return function routerOnEnter(nextState, replace) {
if (!canAccessUrl(urlName)) {
replace('/unauthorized');
}
};
}
...
{
path: 'list',
getComponent: loadAsync(() => import(/* webpackChunkName: "order" */ '../../order/List')),
onEnter: routerOnEnterCheck('order_list'),
}
...
對於菜單和組件的權限判斷,大體上長這樣:
// 用以緩存是否有權限訪問組件
const componentAccessCache = {};
/**
* 檢查訪問組件的權限
* 一個組件可能會重複 render 多次,而組件權限的數量可能會超多(上百個),因此將權限緩存起來以提高性能
*/
function canAccessComponent(module, componentName) {
if (!module || !componentName) {
console.error(`canAccessComponent ${module} ${componentName} 缺參數`);
}
const key = `${module}.${componentName}`;
let result = componentAccessCache[key];
if (result !== undefined) {
return result;
}
const moduleCode = getComponentCodeByModuleAndName(module, componentName); // 權限點一般是一個沒意義的編碼,爲了更易於理解,前端做了一個編碼映射
result = accesses.c.indexOf(moduleCode) > -1; // accesses.c 數組是後端返回的所有組件權限點
componentAccessCache[key] = result;
return result;
}
class SomeComponent extends PureComponent {
...
render() {
return (
...
{
canAccessComponent('asset', 'buy_pay') &&
<Button
type="primary"
className="btn-buy"
onClick={() => (buy(num))}
>
立即訂購
</Button>
}
...
)
}
}
權限點的獲取筆者放在了 node 端,通過全局變量的形式注入到頁面中,保證呈現的頁面是由權限點過濾過的。此外接口返回對權限點是一個一維數組,爲了加快前端檢索速度,在 node 端根據編碼規則將權限點分爲 3 類(菜單/頁面/組件),具體細節就不細說了。
四、總結
本文介紹了權限管理的基礎知識,還結合 React 講解了前端權限控制的一些細節。技術方案比較簡單,真正麻煩的是每一個權限點的定義及錄入,以及對現有系統的改造。改造過程中可以分模塊進行迭代,畢竟羅馬不是一天就能建成。