在作商家后台管理系统时,做为前端一般会设计到大量的权限控制问题,按照细粒度归归类大体能够分类如下三类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...