在作商家後臺管理系統時,做爲前端一般會設計到大量的權限控制問題,按照細粒度歸歸類大體能夠分類如下三類javascript
頁面權限前端
模塊權限-頁面區塊(組件)是否顯示java
元件權限-組件內元素是否顯示git
後端會將用戶權限數據同步注入到VM模板中或者前端發送異步請求取到權限數據,數據消費場景通常都散落在代碼的角角落落。github
// 僞代碼 render(){ return {window.permission?<Component/>:null} } render(){ return <Component>{this.props.permission?<Button>刪除</Button>: null}</Component> }
用這種方式實現的代碼,執行上沒有問題,也達到了業務的需求。可是隨着代碼量的遞增,代碼變得難以維護,特別是新接手的同窗,簡直是一場噩夢。express
頁面權限、模塊權限、元件權限三種前端權限表現形式對應不一樣的管理策略。後端
對於傳統的多頁應用,頁面權限控制不須要前端關心,後端路由作一層控制。在SPA架構的前端應用中,咱們的思路是將全部的前端路由配置在後端,對於不一樣角色的用戶,後端把路由列表吐給前端註冊。閉包
對於這兩類權限控制的事就所有須要交給前端處理了,大體思路是將系統中用戶散落的權限統一配置,經過HOC包裝一下React組件,提供劫持渲染和權限透傳的能力。架構
應用的全部權限配置會被統一配置在一個閉包中,權限的值支持後端同步吐出,也支持每次異步獲取(利用Promise實現)app
// 僞代碼 export const AUTH_RULES = { 'isX1': window.isX1 === '', 'isX2': window.isX2 === '', 'isX3': () => { return new Promise((resolve, reject) => { resolve(result); // resolve的參數只能是true或者false }) }, }; registerAuthRules(AUTH_RULES);
權限列表中配置的只是顆粒度最細的單個權限。在實際業務需求中,咱們常須要根據權限格則組合結果,決定是否顯示。好比ComponentA的顯示條件是isX1 && isX2 或者 isX1 || isX3。
這裏須要引入權限規則表達式的概念。How to compute?
第一步:利用詞法分析器解析出表達式中有多少個權限變量。利用esprima能夠輕鬆取到
第二步:計算每一個變量對應的權限值
第三部:計算規則表達式,由於權限規則有多是異步或者的,這裏將每一個格則包裝成Promise對象,利用Promise.all作統一返回,在成功的回調函數中經過New Function的方式計算字符串表達式的結果
// 計算表達式相關代碼 function getExpressionValue(expression, data) { const codes = []; for (const key in data) { if (data.hasOwnProperty(key)) { const value = typeof data[key] === 'string' ? `"${data[key]}"` : data[key]; codes.push(`var ${key} = ${value};`); } } codes.push(`return ${expression};`); return new Function(codes.join(''))(); }
註冊權限規則列表,支持同步規則和異步規則
參數:
rules {Object} 應用權限規則MAP
註冊組件顯示規則,根據組件displayName配置組件所需權限列表
參數:
rules {Object} 組件權限規則MAP
調用查看
export const COMPONENTS_RULES = { ComponentA: 'isX1', ComponentB: 'isX1 && isX2', }; registerComponentRules(COMPONENTS_RULES)
參數:
options {Object} 組件權限規則MAP
options.placeholder {Component} 組件隱藏時的佔位節點;默認爲noscript
options.initialHide {Boolean} 當存在異步權限規則時,組件是否先默認隱藏;默認值爲true
options.rules {Object} 配置組件須要權限規則集合,做爲props屬性$auth傳遞給組件
根據WrappedComponent.displayName判斷組件是否有權限
class Component{ // ... } Component.displayName = 'ComponentA'; const Authed_Component_1 = Auth({ placeholder: <p>無權限的佔位節點</p> })(Component)
class Page{ render(){ const {$auth} = this.props; return ( <div> { $auth.isShowDeleteBtn && <p>刪除</p> } </div> ) } } // 權限校驗條件與權限屬性,組件內容沒有校驗邏輯 const Authed_Page = Auth({ rules: { 'isShowDeleteBtn': 'isVip' } })(Page);
代碼實現[hoc-auth]https://github.com/amibug/hoc...