目录规划:(从我接触使用到 尝试增加新功能未果 直至决定研究源码的过程)

  • 怎么使用?

    • 后台管理权限的三大限制入口:按钮元素功能、路由跳转、菜单显示;
  • 组件源码解读

    • 入口的背后是什么?怎么联系起来的?

    • 不同的定制化服务怎么满足的?

  • 根据不同的使用场景,权限从组件层面上是怎么设计的?使用及api设计?

    • 组件讨论历史(由来)、设计组件初稿到终稿的连接、

    • 现有的权限校验方式 及 用户登录状态、权限的保持 常用的几种解决方案:前后端

    • 诚身的小册子

  • 尝试改组件、理解组件、甚至找到组件的漏洞(不合适的地方)

    • issue的各种讨论

    • 最新的一次pr

    • pr兼容上出现的错误

  • v2较v1上的改进

1、怎么使用?

参考资料: 官网组件介绍

1.1 一个简单的例子

Basic use

  1. import RenderAuthorized from 'components/Authorized';// 组件位置
  2. import { Alert } from 'antd';
  3. const Authorized = RenderAuthorized('user');// 传入当前权限,生成权限组件实例
  4. const noMatch = <Alert message="No permission." type="error" showIcon />;
  5. ReactDOM.render(
  6. <div>
  7. <Authorized
  8. authority="admin" // 规定准入权限
  9. noMatch={noMatch} // 当 准入权限与当前权限不一或不符合时, noMatch规定渲染内容
  10. >
  11. <Alert message="user Passed!" type="success" showIcon /> // 符合则渲染children
  12. </Authorized>
  13. </div>,
  14. mountNode,
  15. );

1.2 Authrized组件authority的不同传入方式

假设操作方式还是如上所示,Authorized此时还是已传入当前权限后生成的组件实例
我们讨论下组件Authorized中的 authority 的不同传入方式所产生的不同使用效果:
1、如上传入 String: authority=”admin” ,判断逻辑是当前权限与准入权限是否一致
2、传入数组:authority={[‘admin’,’test’]}, 判断逻辑是 当前权限是否在准入权限内
3、传入方法: authority={functiontest}

  1. const functiontest = ()=>{
  2. return false;// 那么就一直不会进入
  3. }

4、传入promise:authority={promiseAuth}

  1. const promiseAuth = (curToken)=>{
  2. return new Promise((resolve,reject)=>{
  3. token2role(getAuthority()).then(res=>{
  4. try{
  5. if(['admin','test'].includes(res.role)){
  6. resolve()
  7. }
  8. }catch{
  9. reject()
  10. }
  11. })
  12. })
  13. }

1.3 组件的扩展使用

/components/Authorized/index

  1. import Authorized from './Authorized';
  2. import AuthorizedRoute from './AuthorizedRoute';
  3. import Secured from './Secured';
  4. import check from './CheckPermissions.js';
  5. import renderAuthorize from './renderAuthorize';
  6. Authorized.Secured = Secured;
  7. Authorized.AuthorizedRoute = AuthorizedRoute;
  8. Authorized.check = check;
  9. export default renderAuthorize(Authorized);


我们上面使用的都是基础组件Authorized
而看上面代码可知,该组件上还绑定有其他”巨无霸”扩展功能,满足吃客不同姿势的享用
// Authorized.AuthorizedRoute
// Authorized.Secured
// Authorized.check

  1. [1]Authorized.AuthorizedRoute
  2. // authority 准录入权限、重定向页面路由
  3. <AuthorizedRoute
  4. path="/"
  5. render={props => <BasicLayout {...props} />}
  6. authority={['admin', 'user']}
  7. redirectPath="/user/login"
  8. />
  9. [2]Authorized.Secured
  10. // 元素显示与否
  11. const { Secured } = RenderAuthorized('user');
  12. @Secured('admin')
  13. class TestSecuredString extends React.Component {
  14. render() {
  15. <Alert message="user Passed!" type="success" showIcon />;
  16. }
  17. }
  18. [3] Authorized.check
  19. // 函数形式的 Authorized,用于某些不能被 HOC 包裹的组件

1.4 小总结

权限组件,通过比对现有权限与准入权限,决定相关元素的展示
权限组件默认 export RenderAuthorized 函数,

  1. export default renderAuthorize(Authorized);

它接收当前权限作为参数,返回一个权限对象,该对象提供几种如上不同的使用方式;

关系图大致:
antd pro 路由与权限 - 图1

接下来:
Q1: 权限怎么 统一规范 1、菜单的显示 ;2、路由的访问(跳转url) 3、页面元素显示
1、菜单显示
2、路由访问:
3、页面元素
使用 AuthorizedAuthorized.Secured 可以很方便地控制元素/组件的渲染
Q2: 怎么切换权限的? 登录之后怎么将当前权限更新传递下去的?

答约Q1:
菜单显示:/src/common/menujs

  1. * children:[{
  2. * name:'xx',
  3. * authority:'admin', // 在需要设置权限的菜单中设置auth属性
  4. * path:'xxxx'
  5. * }]
  6. * ===》 调用Authorized.check 进行判断

路由配置://src/common/routerjs

  1. '/dashboard/workplace': {
  2. component: dynamicWrapper(app, ['project'],
  3. () => import('../routes/Dashboard/Workplace')),
  4. authority: 'admin', // 配置准入权限
  5. },

元素显示:

  1. // 使用 Authorized 或 Authorized.Secured 可以很方便地控制元素/组件的渲染

答约Q2:
/model/user
登录那里,每次获取到了新的权限可以调用reloadAuthorized 函数注入新的权限,从而改变权限组件实例

/utils/Authorized

  1. let Authorized = RenderAuthorized(getAuthority());// 用于初次
  2. // 每次调用reloadAuthorized 动态改变 权限
  3. const reloadAuthorized = () => {
  4. Authorized = RenderAuthorized(getAuthority());
  5. };

而保持权限的持久化:即不随着刷新而掉身份—- 将权限的标识或者能获取到权限的标识存在localstorage里;
每次的getAuthority()即返回 role标识;

2.组件源码解读

2.1 概要逻辑解析

面来分析他是怎么联系起来的?
我们从/utils/Authorized里的RenderAuthorized开始进入;
import RenderAuthorized from ‘../components/Authorized’;
即这张图的
antd pro 路由与权限 - 图2

绿色标注部分开始分析他的基础用法:

  1. <Authorized authority={['user', 'admin']} noMatch={noMatch}>
  2. <Alert message="Use Array as a parameter passed!" type="success" showIcon />
  3. </Authorized>,

时刻谨记这里的authority有不同的配置方式(string、数组、函数、promise)

export default renderAuthorize(Authorized); 加粗的这个的用法是:
let Authorized = RenderAuthorized(‘admin’) 即接收一个当前权限,生成对应的组件实例;
renderAuthorize 、Authorized是不同的作用,
renderAuthorize不是组件是方法
Authorized是权限组件的基本方法,他上面绑定了一些扩展方法(比如Secured、AuthorizedRoute、check

那么他们做了什么?
首先看:
Authorized
/components/Authorized/Authorized.js
这个组件的用法如上所示,可接收的参数是
antd pro 路由与权限 - 图3

antd pro 路由与权限 - 图4

/components/Authorized/CheckPermissions.js
当是 字符串、数组时,逻辑比较简单,通过比对确定渲染 target还是 Exception即可;
重点看下 是function和promise时:

  1. const bool = authority(currentAuthority)// 拿着当前权限去执行这个函数;判断返回
  2. // 如果是bool则根据bool类型判断 渲染对象
  3. // 如果返回也是一个promise,那么进入PromiseRender组件

2.2 PromiseRender组件

现在重点在PromiseRender组件

promise之所以麻烦是在于,我们不能直接在promise函数里返回得到的未来的值。这样我们会依旧返回一个promise..
比如:

  1. // 我们可能有个token2role函数,拿本地的token去远程获取role角色,这个函数是异步的;
  2. // 我们什么时候能拿到返回的角色?
  3. token2role.then(res=>{
  4. console.log(res.role) //=> 'admin'
  5. })
  6. // [1] 用在权限组件里我们可能会这样:
  7. // utils/Authorized.js
  8. let Authorized = RenderAuthorized(getAuthority())
  9. function getAuthority(){
  10. return token2role.then(res=>{
  11. return res.role
  12. })
  13. }
  14. // 然后会发现我们得到的还是一个promise 即RenderAuthorized(promise)
  15. // 而不是期望的RenderAuthorized('admin')
  16. // 问题就在于我们每次获取let Authorized = RenderAuthorized(getAuthority())都需要他是动态的;
  17. // 即不仅需要getAuthority()动态执行 还需要他返回一个值,特别是当此函数是异步的时候,几乎是违背的;
  18. // 所以当我背着想要从后台获取role的想法走到这时,我找不到路了。。
  19. // 没有模板代码我好累鸭

然后看到了promiserender。
让这个组件拿着我们的promise去执行,根据promise的返回结果来确认渲染对象;
设计:

  1. const promiseAuth = (curToken)=>{
  2. return new Promise((resolve,reject)=>{
  3. token2role(getAuthority()).then(res=>{
  4. try{
  5. if(['admin','test'].includes(res.role)){
  6. resolve()
  7. }
  8. }catch{
  9. reject()
  10. }
  11. })
  12. })
  13. }
  14. // util 传入本地token字符生成组件实例
  15. let Authorized = RenderAuthorized('一大串乱码表示的本地token')
  16. // 组件
  17. <Authorized
  18. authority={promiseAuth}
  19. noMatch={noMatch}
  20. >
  21. <Alert message="Use Array as a parameter passed!" type="success" showIcon />
  22. </Authorized>,
  23. // 经过上面的分析,这个组件最终被传递到promiserender,并且拿到了promiseAuth 函数定义的promise
  24. // promiserender 源码见/components/Authorized/PromiseRender
  25. props.promise
  26. .then(() => {
  27. this.setState({
  28. component: ok,
  29. })
  30. })
  31. .catch(() => {
  32. this.setState({
  33. component: error,
  34. });
  35. });
  36. // 所以输出的效果应该能达到预先定义:
  37. // 当权限符合准入权限时,渲染准入权限,不符合时候渲染exception

试验一下;
事实证明是可以的;
但是AuthorizedRoute却不可以,所以接下来我们走进扩展组件看看

2.3 AuthorizedRoute与promise

AuthorizedRoute
出现的问题是:
我配置的 resolve还是reject都是渲染对应的 ok 及 err
期望:
当ok的时候 进入render;
不ok的时候 ,进入redirectPath
实际:
都进入了重定向;根本没有render

antd pro 路由与权限 - 图5

  1. <AuthorizedRoute
  2. path="/"
  3. render={props => <BasicLayout {...props} />}
  4. authority={promiseAuth}
  5. redirectPath="/login"
  6. />
  7. // 源码:

现在排查到是 basic组件的问题:
从local:8000来未匹配到任何路径,会执行getBaseRedirect 获取当前需要

排查知:
当没有登录时,重定向到login是权限组件起的作用;
那么basic里面的重定向又是什么用呢?
我去掉之后,还是可以:

  • 未登录强制登录未果——权限组件的作用;

首次进入之后,当前路径是 / 未匹配到下游路径,所以显示的是 403页面;
可能这就是需要重定向的原因把。
但是为啥是:

  • 当前路径是否包含 redirect?

  • 不包含: 寻找第一个需要鉴权的路径,跳转到那里;

  • =====-》 不应该啊,正常的应该是跳转到第一个路径吧。。。即dashboard之类的地方

  • =====-》 为啥还需要一次校验?