目录规划:(从我接触使用到 尝试增加新功能未果 直至决定研究源码的过程)
怎么使用?
- 后台管理权限的三大限制入口:按钮元素功能、路由跳转、菜单显示;
组件源码解读
入口的背后是什么?怎么联系起来的?
不同的定制化服务怎么满足的?
根据不同的使用场景,权限从组件层面上是怎么设计的?使用及api设计?
组件讨论历史(由来)、设计组件初稿到终稿的连接、
现有的权限校验方式 及 用户登录状态、权限的保持 常用的几种解决方案:前后端
诚身的小册子
尝试改组件、理解组件、甚至找到组件的漏洞(不合适的地方)
issue的各种讨论
最新的一次pr
pr兼容上出现的错误
v2较v1上的改进
1、怎么使用?
1.1 一个简单的例子
Basic use
import RenderAuthorized from 'components/Authorized';// 组件位置
import { Alert } from 'antd';
const Authorized = RenderAuthorized('user');// 传入当前权限,生成权限组件实例
const noMatch = <Alert message="No permission." type="error" showIcon />;
ReactDOM.render(
<div>
<Authorized
authority="admin" // 规定准入权限
noMatch={noMatch} // 当 准入权限与当前权限不一或不符合时, noMatch规定渲染内容
>
<Alert message="user Passed!" type="success" showIcon /> // 符合则渲染children
</Authorized>
</div>,
mountNode,
);
1.2 Authrized组件authority的不同传入方式
假设操作方式还是如上所示,Authorized此时还是已传入当前权限后生成的组件实例
我们讨论下组件Authorized中的 authority 的不同传入方式所产生的不同使用效果:
1、如上传入 String: authority=”admin” ,判断逻辑是当前权限与准入权限是否一致
2、传入数组:authority={[‘admin’,’test’]}, 判断逻辑是 当前权限是否在准入权限内
3、传入方法: authority={functiontest}
const functiontest = ()=>{
return false;// 那么就一直不会进入
}
4、传入promise:authority={promiseAuth}
const promiseAuth = (curToken)=>{
return new Promise((resolve,reject)=>{
token2role(getAuthority()).then(res=>{
try{
if(['admin','test'].includes(res.role)){
resolve()
}
}catch{
reject()
}
})
})
}
1.3 组件的扩展使用
/components/Authorized/index
import Authorized from './Authorized';
import AuthorizedRoute from './AuthorizedRoute';
import Secured from './Secured';
import check from './CheckPermissions.js';
import renderAuthorize from './renderAuthorize';
Authorized.Secured = Secured;
Authorized.AuthorizedRoute = AuthorizedRoute;
Authorized.check = check;
export default renderAuthorize(Authorized);
我们上面使用的都是基础组件Authorized
而看上面代码可知,该组件上还绑定有其他”巨无霸”扩展功能,满足吃客不同姿势的享用
// Authorized.AuthorizedRoute
// Authorized.Secured
// Authorized.check
[1]Authorized.AuthorizedRoute
// authority 准录入权限、重定向页面路由
<AuthorizedRoute
path="/"
render={props => <BasicLayout {...props} />}
authority={['admin', 'user']}
redirectPath="/user/login"
/>
[2]Authorized.Secured
// 元素显示与否
const { Secured } = RenderAuthorized('user');
@Secured('admin')
class TestSecuredString extends React.Component {
render() {
<Alert message="user Passed!" type="success" showIcon />;
}
}
[3] Authorized.check
// 函数形式的 Authorized,用于某些不能被 HOC 包裹的组件
1.4 小总结
权限组件,通过比对现有权限与准入权限,决定相关元素的展示
权限组件默认 export RenderAuthorized 函数,
export default renderAuthorize(Authorized);
它接收当前权限作为参数,返回一个权限对象,该对象提供几种如上不同的使用方式;
关系图大致:
接下来:
Q1: 权限怎么 统一规范 1、菜单的显示 ;2、路由的访问(跳转url) 3、页面元素显示
1、菜单显示
2、路由访问:
3、页面元素
使用 Authorized 或 Authorized.Secured 可以很方便地控制元素/组件的渲染
Q2: 怎么切换权限的? 登录之后怎么将当前权限更新传递下去的?
答约Q1:
菜单显示:/src/common/menujs
* children:[{
* name:'xx',
* authority:'admin', // 在需要设置权限的菜单中设置auth属性
* path:'xxxx'
* }]
* ===》 调用Authorized.check 进行判断
路由配置://src/common/routerjs
'/dashboard/workplace': {
component: dynamicWrapper(app, ['project'],
() => import('../routes/Dashboard/Workplace')),
authority: 'admin', // 配置准入权限
},
元素显示:
// 使用 Authorized 或 Authorized.Secured 可以很方便地控制元素/组件的渲染
答约Q2:
/model/user
登录那里,每次获取到了新的权限可以调用reloadAuthorized 函数注入新的权限,从而改变权限组件实例
/utils/Authorized
let Authorized = RenderAuthorized(getAuthority());// 用于初次
// 每次调用reloadAuthorized 动态改变 权限
const reloadAuthorized = () => {
Authorized = RenderAuthorized(getAuthority());
};
而保持权限的持久化:即不随着刷新而掉身份—- 将权限的标识或者能获取到权限的标识存在localstorage里;
每次的getAuthority()即返回 role标识;
2.组件源码解读
2.1 概要逻辑解析
面来分析他是怎么联系起来的?
我们从/utils/Authorized里的RenderAuthorized开始进入;
import RenderAuthorized from ‘../components/Authorized’;
即这张图的
绿色标注部分开始分析他的基础用法:
<Authorized authority={['user', 'admin']} noMatch={noMatch}>
<Alert message="Use Array as a parameter passed!" type="success" showIcon />
</Authorized>,
时刻谨记这里的authority有不同的配置方式(string、数组、函数、promise)
export default renderAuthorize(Authorized); 加粗的这个的用法是:
let Authorized = RenderAuthorized(‘admin’) 即接收一个当前权限,生成对应的组件实例;
而renderAuthorize 、Authorized是不同的作用,
renderAuthorize不是组件是方法。
Authorized是权限组件的基本方法,他上面绑定了一些扩展方法(比如Secured、AuthorizedRoute、check)
那么他们做了什么?
首先看:
Authorized
/components/Authorized/Authorized.js
这个组件的用法如上所示,可接收的参数是
/components/Authorized/CheckPermissions.js
当是 字符串、数组时,逻辑比较简单,通过比对确定渲染 target还是 Exception即可;
重点看下 是function和promise时:
const bool = authority(currentAuthority)// 拿着当前权限去执行这个函数;判断返回
// 如果是bool则根据bool类型判断 渲染对象
// 如果返回也是一个promise,那么进入PromiseRender组件
2.2 PromiseRender组件
现在重点在PromiseRender组件
promise之所以麻烦是在于,我们不能直接在promise函数里返回得到的未来的值。这样我们会依旧返回一个promise..
比如:
// 我们可能有个token2role函数,拿本地的token去远程获取role角色,这个函数是异步的;
// 我们什么时候能拿到返回的角色?
token2role.then(res=>{
console.log(res.role) //=> 'admin'
})
// [1] 用在权限组件里我们可能会这样:
// utils/Authorized.js
let Authorized = RenderAuthorized(getAuthority())
function getAuthority(){
return token2role.then(res=>{
return res.role
})
}
// 然后会发现我们得到的还是一个promise 即RenderAuthorized(promise)
// 而不是期望的RenderAuthorized('admin')
// 问题就在于我们每次获取let Authorized = RenderAuthorized(getAuthority())都需要他是动态的;
// 即不仅需要getAuthority()动态执行 还需要他返回一个值,特别是当此函数是异步的时候,几乎是违背的;
// 所以当我背着想要从后台获取role的想法走到这时,我找不到路了。。
// 没有模板代码我好累鸭
然后看到了promiserender。
让这个组件拿着我们的promise去执行,根据promise的返回结果来确认渲染对象;
设计:
const promiseAuth = (curToken)=>{
return new Promise((resolve,reject)=>{
token2role(getAuthority()).then(res=>{
try{
if(['admin','test'].includes(res.role)){
resolve()
}
}catch{
reject()
}
})
})
}
// util 传入本地token字符生成组件实例
let Authorized = RenderAuthorized('一大串乱码表示的本地token')
// 组件
<Authorized
authority={promiseAuth}
noMatch={noMatch}
>
<Alert message="Use Array as a parameter passed!" type="success" showIcon />
</Authorized>,
// 经过上面的分析,这个组件最终被传递到promiserender,并且拿到了promiseAuth 函数定义的promise
// promiserender 源码见/components/Authorized/PromiseRender
props.promise
.then(() => {
this.setState({
component: ok,
})
})
.catch(() => {
this.setState({
component: error,
});
});
// 所以输出的效果应该能达到预先定义:
// 当权限符合准入权限时,渲染准入权限,不符合时候渲染exception
试验一下;
事实证明是可以的;
但是AuthorizedRoute却不可以,所以接下来我们走进扩展组件看看
2.3 AuthorizedRoute与promise
AuthorizedRoute
出现的问题是:
我配置的 resolve还是reject都是渲染对应的 ok 及 err
期望:
当ok的时候 进入render;
不ok的时候 ,进入redirectPath
实际:
都进入了重定向;根本没有render
<AuthorizedRoute
path="/"
render={props => <BasicLayout {...props} />}
authority={promiseAuth}
redirectPath="/login"
/>
// 源码:
现在排查到是 basic组件的问题:
从local:8000来未匹配到任何路径,会执行getBaseRedirect 获取当前需要
排查知:
当没有登录时,重定向到login是权限组件起的作用;
那么basic里面的重定向又是什么用呢?
我去掉之后,还是可以:
- 未登录强制登录未果——权限组件的作用;
首次进入之后,当前路径是 / 未匹配到下游路径,所以显示的是 403页面;
可能这就是需要重定向的原因把。
但是为啥是:
当前路径是否包含 redirect?
不包含: 寻找第一个需要鉴权的路径,跳转到那里;
=====-》 不应该啊,正常的应该是跳转到第一个路径吧。。。即dashboard之类的地方
=====-》 为啥还需要一次校验?