Antd Pro V4 权限管理详解

Antd Pro V4 权限管理详解

在这篇文章中,你将了解到:(文末有彩蛋!!!)

  • antd-design-pro v4 是如何在用户登录的时候,进行鉴权的
  • 与权限管理有关的代码是如何作用的
  • 如何实现动态菜单 + 动态路由

写这篇文章呢,是因为笔者公司最近要求做一个后台管理系统,要求在前端这里能根据角色映射并动态修改相应的菜单权限,笔者还是太菜了,看遍了网上关于antd pro权限的文章和issue,最后终于搞定,网上关于V4的文章实在是太少了,所以笔者打算把这次踩坑经历分享给大家,以后大家一起在坑里愉快的玩耍。。。

Antd Pro V4 权限管理详解_第1张图片

1.有关权限的代码详解

首先,有关权限的文件有这几个

  • @/components/Authorized
  • @/utils/utils(getAuthorityFromRouter、getRouteAuthority)
  • @/utils/Authorized.js
  • @/utils/authority.js
  • @/layouts/BasicLayout.jsx
  • @/pages/Authorized.jsx
    ok,以上就是一般情况下,我们会涉及到的权限管理文件,下面,我们来逐一详细的分析各个文件的作用

(1)@/components/Authorized
我们首先从入口文件index.jsx进入,可以看到,返回了一个RenderAuthorize变量,这个变量具体是什么呢?我们再看renderAuthorize这个函数

const renderAuthorize = Authorized => currentAuthority => {
  if (currentAuthority) {
    if (typeof currentAuthority === 'function') {
      CURRENT = currentAuthority();
    }

    if (
      Object.prototype.toString.call(currentAuthority) === '[object String]' ||
      Array.isArray(currentAuthority)
    ) {
      CURRENT = currentAuthority;
    }
  } else {
    CURRENT = 'NULL';
  }

  return Authorized;
};

export { CURRENT };
export default Authorized => renderAuthorize(Authorized);  //这个是index.jsx中引入的函数

可以看到,renderAuthorize 这个函数,实际上是返回了一个以*currentAuthority* 作为参数,以*Authorized*作为返回值的函数,并将CURRENT这个变量暴露了出去,至于这个变量的作用(实际上就是当前用户登陆时的角色,只是对其进行了一些处理),下文我们会涉及,现在咱们先继续哈!

然后回到index.jsx,我们可以看到,Authrized作为变量被传入renderAuthorize这个方法

const RenderAuthorize = renderAuthorize(Authorized);

而Authorized呢,实则是一个组件(jsx第一公民优越的地方,能够将组件当作变量使用),
它接收

  • children(希望渲染的子组件)
  • authority(当前路由页面的权限)
  • noMatch(权限不满足时,显示的页面组件)

三个props属性,等下我们会看到,在BasicLayouts.jsx中,antd pro v4实际上是将每个路由组件都包裹上了一层这个组件。

import React from 'react';
import { Result } from 'antd';
import check from './CheckPermissions';

const Authorized = ({
  children,
  authority,
  noMatch = (
    <Result
      status="403"
      title="403"
      subTitle="Sorry, you are not authorized to access this page."
    />
  ),
}) => {
  const childrenRender = typeof children === 'undefined' ? null : children;
  const dom = check(authority, childrenRender, noMatch);
  return <>{dom}</>;  
};

export default Authorized;

然后,我们又看到了check这个方法

import React from 'react';
import { CURRENT } from './renderAuthorize'; // eslint-disable-next-line import/no-cycle

import PromiseRender from './PromiseRender';

/**
 * 通用权限检查方法
 * Common check permissions method
 * @param { 权限判定 | Permission judgment } authority
 * @param { 你的权限 | Your permission description } currentAuthority
 * @param { 通过的组件 | Passing components } target
 * @param { 未通过的组件 | no pass components } Exception
 */
const checkPermissions = (authority, currentAuthority, target, Exception) => {
  // 没有判定权限.默认查看所有
  // Retirement authority, return target;
 。。。此处省略具体的代码,实际上就是对于路由中,authority数组、string、promise、function四种写法的不同处理,然后根据是否符合权限校验返回通过的组件或者未通过的组件
};

export { checkPermissions };

function check(authority, target, Exception) {
  return checkPermissions(authority, CURRENT, target, Exception);
}

export default check;

所以综上所属@/components/Authorized导出的,实际上是一个以Authorized组件为参数的函数,它最后返回的结果仍旧是Authorized,整个文件的目的在于,对路由携带的权限和用户所拥有的权限进行比对,返回通过的组件或者未通过的组件罢了。


(2)@/utils/Authorized.jsx
有了上面的基础呢,我们接下来再看utils下的Authorized.jsx文件

import RenderAuthorize from '@/components/Authorized';
import { getAuthority } from './authority';
/* eslint-disable eslint-comments/disable-enable-pair */

/* eslint-disable import/no-mutable-exports */

let Authorized = RenderAuthorize(getAuthority()); // Reload the rights component

const reloadAuthorized = () => {
  Authorized = RenderAuthorize(getAuthority());
};
/**
 * hard code
 * block need it。
 */

window.reloadAuthorized = reloadAuthorized;
export { reloadAuthorized };
export default Authorized;

我们上文中提到了,@/components/Authorizedz这个组件,返回的实质上是一个以currentAuthority 作为参数,以Authorized组件作为返回值的函数,因此该文件的作用呢,在于以下三点。

  • 通过getAuthority()这个方法,赋予CURRENT这个变量当前用户的角色
  • 向外默认暴露出一个已经通过角色检查的Authorized组件
  • 暴露出一个重新校验当前页面路由权限的方法

./authority.js

import { reloadAuthorized } from './Authorized'; // use localStorage to store the authority info, which might be sent from server in actual project.

export function getAuthority(str) {
  const authorityString =
    typeof str === 'undefined' && localStorage ? localStorage.getItem('antd-pro-authority') : str; // authorityString could be admin, "admin", ["admin"]

  ...实际上就是从本地读取保存的用户角色,然后进行一系列处理

  return authority;
}
export function setAuthority(authority) {
  const proAuthority = typeof authority === 'string' ? [authority] : authority;
  localStorage.setItem('antd-pro-authority', JSON.stringify(proAuthority)); // auto reload

  reloadAuthorized();
}

我们可以看到,这个文件就做了两件事情,一个就是从本地读取当前用户角色,一个是保存角色,而保存角色setAuthority这个函数呢,是在models/login内部调用的,也就是在用户登录的时候,进行保存

reducers: {
    changeLoginStatus(state, { payload }) {
      setAuthority(payload.currentAuthority);
      return { ...state, status: payload.status, type: payload.type };
    },
  },

(3)@/layouts/BasicLayout.jsx
好啦,一路屠龙,我们终于来到antd pro v4权限最关键的地方了,这里也是自定义动态菜单需要修改的地方,不过呢,我们先来讲解一下此处,是如何进行路由鉴权的

const menuDataRender = menuList =>
  menuList.map(item => {
    const localItem = {
      ...item,
      children: item.children ? menuDataRender(item.children) : undefined,
    };
    return Authorized.check(item.authority, localItem, null);
  });

接收的参数: menuList,是默认从src/.umi/core/routes.ts中生成的菜单,而这些菜单是根据config下的routes属性自动生成的。
返回值: 返回一个由通过权鉴的目标组件和没有通过的403组件组成的数组
此处的Authorized.check方法,就是我们在上文中讲解过的checkPermisson方法,在index.jsx中,将check方法绑定在了Authorized这个组件上面

Authorized.Secured = Secured;
Authorized.check = check;
const RenderAuthorize = renderAuthorize(Authorized);
export default RenderAuthorize;

(4)@/pages/Authorized.jsx
这个笔者看下来觉得是有些懵逼的,因为笔者搜索过全局,发现并没有任何一个地方调用了这个组件或者跳转到了这个地方,我去Github上看了别人提出来的issue才明白。(umi作者)

Antd Pro V4 权限管理详解_第2张图片

也有一位外国小哥提出了不同的意见
Antd Pro V4 权限管理详解_第3张图片
他的意思翻译过来呢,就是他认为应该在路由配置的地方,注入Authorized组件。
如果能够创建一个新的Authenticated页面,直接重新跳转到/user/login页面并且使用Authorized组件来展示403页面,会不会我们就可以完全避免在BasicLayout中,再次用Authorized组件进行包裹的逻辑呢?
小哥贴出了自己的代码,大家可以看一下
方案详情代码

笔者对于antd pro V4这个骚操作还是有点疑问,欢迎大家在留言区进行探讨,并指出笔者写作中的不足之处!
Antd Pro V4 权限管理详解_第4张图片

2.动态路由 + 动态菜单

动态路由 + 动态菜单
关于这个主题,既然已经有解决方案了,笔者就不再赘述了,以后可能会随着笔者对于umi框架理解的加深,再跟进一些其它文章,这篇文章中的解决方案是笔者亲手实践,可以实现的,如果大家有疑问的话,可以@笔者,笔者会尽力帮助大家

感谢阅读,欢迎批评指正,希望大家能够在追求卓越中不断进步,让优秀成为一种习惯~

你可能感兴趣的:(react,ant)