iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >React中的权限组件设计问题小结
  • 873
分享到

React中的权限组件设计问题小结

2024-04-02 19:04:59 873人浏览 安东尼
摘要

目录背景所谓的权限控制是什么?实现思路路由权限菜单权限背景 权限管理是中后台系统中常见的需求之一。之前做过基于 Vue 的后台管理系统权限控制,基本思路就是在一些路由钩子里做权限比对

背景

权限管理是中后台系统中常见的需求之一。之前做过基于 Vue 的后台管理系统权限控制,基本思路就是在一些路由钩子里做权限比对和拦截处理。

最近维护的一个后台系统需要加入权限管理控制,这次技术栈是React,我刚开始是在网上搜索一些React路由权限控制,但是没找到比较好的方案或思路。

这时想到ant design pro内部实现过权限管理,因此就专门花时间翻阅了一波源码,并在此基础上逐渐完成了这次的权限管理。

整个过程也是遇到了很多问题,本文主要来做一下此次改造工作的总结

原代码基于 react 16.x、dva 2.4.1 实现,所以本文是参考了ant-design-pro v1内部对权限管理的实现

所谓的权限控制是什么?

一般后台管理系统的权限涉及到两种:

  • 资源权限
  • 数据权限

资源权限一般指菜单、页面、按钮等的可见权限。

数据权限一般指对于不同用户,同一页面上看到的数据不同。

本文主要是来探讨一下资源权限,也就是前端权限控制。这又分为了两部分:

  • 侧边栏菜单
  • 路由权限

在很多人的理解中,前端权限控制就是左侧菜单的可见与否,其实这是不对的。举一个例子,假设用户guest没有路由/setting的访问权限,但是他知道/setting的完整路径,直接通过输入路径的方式访问,此时仍然是可以访问的。这显然是不合理的。这部分其实就属于路由层面的权限控制。

实现思路

关于前端权限控制一般有两种方案:

  • 前端固定路由表和权限配置,由后端提供用户权限标识
  • 后端提供权限和路由信息结构接口,动态生成权限和菜单

我们这里采用的是第一种方案,服务只下发当前用户拥有的角色就可以了,路由表和权限的处理统一在前端处理。

整体实现思路也比较简单:现有权限(currentAuthority)和准入权限(authority)做比较,如果匹配则渲染和准入权限匹配的组件,否则渲染无权限组件(403 页面)

路由权限

既然是路由相关的权限控制,我们免不了先看一下当前的路由表:

{
    "name": "活动列表",
    "path": "/activity-mgmt/list",
    "key": "/activity-mgmt/list",
    "exact": true,
    "authority": [
        "admin"
    ],
    "component": ƒ LoadableComponent(props),
    "inherited": false,
    "hideInBreadcrumb": false
},
{
    "name": "优惠券管理",
    "path": "/coupon-mgmt/coupon-rule-bplist",
    "key": "/coupon-mgmt/coupon-rule-bplist",
    "exact": true,
    "authority": [
        "admin",
        "coupon"
    ],
    "component": ƒ LoadableComponent(props),
    "inherited": true,
    "hideInBreadcrumb": false
},
{
    "name": "营销录入系统",
    "path": "/marketRule-manage",
    "key": "/marketRule-manage",
    "exact": true,
    "component": ƒ LoadableComponent(props),
    "inherited": true,
    "hideInBreadcrumb": false
}

这份路由表其实是我从控制台 copy 过来的,内部做了很多的转换处理,但最终生成的就是上面这个对象。

这里每一级菜单都加了一个authority字段来标识允许访问的角色。component代表路由对应的组件:

import React, { createElement } from "react"
import Loadable from "react-loadable"

"/activity-mgmt/list": {
    component: dynamicWrapper(app, ["activityMgmt"], () => import("../routes/activity-mgmt/list"))
},
// 动态引用组件并注册model
const dynamicWrapper = (app, models, component) => {
  // reGISter models
  models.forEach(model => {
    if (modelNotExisted(app, model)) {
      // eslint-disable-next-line
      app.model(require(`../models/${model}`).default)
    }
  })

  // () => require('module')
  // transfORMed by babel-plugin-dynamic-import-node-sync
  // 需要将routerData塞到props中
  if (component.toString().indexOf(".then(") < 0) {
    return props => {
      return createElement(component().default, {
        ...props,
        routerData: getRouterDataCache(app)
      })
    }
  }
  // () => import('module')
  return Loadable({
    loader: () => {
      return component().then(raw => {
        const Component = raw.default || raw
        return props =>
          createElement(Component, {
            ...props,
            routerData: getRouterDataCache(app)
          })
      })
    },
    // 全局loading
    loading: () => {
      return (
        <div
          style={{
            display: "flex",
            justifyContent: "center",
            alignItems: "center"
          }}
        >
          <Spin size="large" className="global-spin" />
        </div>
      )
    }
  })
}
复制代码

有了路由表这份基础数据,下面就让我们来看下如何通过一步步的改造给原有系统注入权限。

先从src/router.js这个入口开始着手:

// 原src/router.js
import dynamic from "dva/dynamic"
import { Redirect, Route, routerRedux, Switch } from "dva/router"
import PropTypes from "prop-types"
import React from "react"
import NoMatch from "./components/no-match"
import App from "./routes/app"

const { ConnectedRouter } = routerRedux

const RouterConfig = ({ history, app }) => {
  const routes = [
    {
      path: "activity-management",
      models: () => [import("@/models/activityManagement")],
      component: () => import("./routes/activity-mgmt")
    },
    {
      path: "coupon-management",
      models: () => [import("@/models/couponManagement")],
      component: () => import("./routes/coupon-mgmt")
    },
    {
      path: "order-management",
      models: () => [import("@/models/orderManagement")],
      component: () => import("./routes/order-maint")
    },
    {
      path: "merchant-management",
      models: () => [import("@/models/merchantManagement")],
      component: () => import("./routes/merchant-mgmt")
    }
    // ...
  ]

  return (
    <ConnectedRouter history={history}>
      <App>
        <Switch>
          {routes.map(({ path, ...dynamics }, key) => (
            <Route
              key={key}
              path={`/${path}`}
              component={dynamic({
                app,
                ...dynamics
              })}
            />
          ))}
          <Route component={NoMatch} />
        </Switch>
      </App>
    </ConnectedRouter>
  )
}

RouterConfig.propTypes = {
  history: PropTypes.object,
  app: PropTypes.object
}

export default RouterConfig

这是一个非常常规的路由配置,既然要加入权限,比较合适的方式就是包一个高阶组件AuthorizedRoute。然后router.js就可以更替为:

function RouterConfig({ history, app }) {
  const routerData = getRouterData(app)
  const BasicLayout = routerData["/"].component
  return (
    <ConnectedRouter history={history}>
      <Switch>
        <AuthorizedRoute path="/" render={props => <BasicLayout {...props} />} />
      </Switch>
    </ConnectedRouter>
  )
}

来看下AuthorizedRoute的大致实现:

const AuthorizedRoute = ({
  component: Component,
  authority,
  redirectPath,
  {...rest}
}) => {
  if (authority === currentAuthority) {
    return (
      <Route
      {...rest}
      render={props => <Component {...props} />} />
    )
  } else {
    return (
      <Route {...rest} render={() =>
        <Redirect to={redirectPath} />
      } />
    )
  }
}

我们看一下这个组件有什么问题:页面可能允许多个角色访问,用户拥有的角色也可能是多个(可能是字符串,也可呢是数组)。

直接在组件中判断显然不太合适,我们把这部分逻辑抽离出来:


const checkPermissions = (authority, currentAuthority, target, Exception) => {
  console.log("checkPermissions -----> authority", authority)
  console.log("currentAuthority", currentAuthority)
  console.log("target", target)
  console.log("Exception", Exception)

  // 没有判定权限.默认查看所有
  // Retirement authority, return target;
  if (!authority) {
    return target
  }
  // 数组处理
  if (Array.isArray(authority)) {
    // 该菜单可由多个角色访问
    if (authority.indexOf(currentAuthority) >= 0) {
      return target
    }
    // 当前用户同时拥有多个角色
    if (Array.isArray(currentAuthority)) {
      for (let i = 0; i < currentAuthority.length; i += 1) {
        const element = currentAuthority[i]
        // 菜单访问需要的角色权限 < ------ > 当前用户拥有的角色
        if (authority.indexOf(element) >= 0) {
          return target
        }
      }
    }
    return Exception
  }

  // string 处理
  if (typeof authority === "string") {
    if (authority === currentAuthority) {
      return target
    }
    if (Array.isArray(currentAuthority)) {
      for (let i = 0; i < currentAuthority.length; i += 1) {
        const element = currentAuthority[i]
        if (authority.indexOf(element) >= 0) {
          return target
        }
      }
    }
    return Exception
  }

  throw new Error("unsupported parameters")
}

const check = (authority, target, Exception) => {
  return checkPermissions(authority, CURRENT, target, Exception)
}

首先如果路由表中没有authority字段默认都可以访问。

接着分别对authority为字符串和数组的情况做了处理,其实就是简单的查找匹配,匹配到了就可以访问,匹配不到就返回Exception,也就是我们自定义的异常页面。

有一个点一直没有提:用户当前角色权限 currentAuthority 如何获取?这个是在页面初始化时从接口读取,然后存到 store

有了这块逻辑,我们对刚刚的AuthorizedRoute做一下改造。首先抽象一个Authorized组件,对权限校验逻辑做一下封装:

import React from "react"
import CheckPermissions from "./CheckPermissions"

class Authorized extends React.Component {
  render() {
    const { children, authority, noMatch = null } = this.props
    const childrenRender = typeof children === "undefined" ? null : children
    return CheckPermissions(authority, childrenRender, noMatch)
  }
}
export default Authorized

接着AuthorizedRoute可直接使用Authorized组件:

import React from "react"
import { Redirect, Route } from "react-router-dom"
import Authorized from "./Authorized"

class AuthorizedRoute extends React.Component {
  render() {
    const { component: Component, render, authority, redirectPath, ...rest } = this.props
    return (
      <Authorized
        authority={authority}
        noMatch={<Route {...rest} render={() => <Redirect to={{ pathname: redirectPath }} />} />}
      >
        <Route {...rest} render={props => (Component ? <Component {...props} /> : render(props))} />
      </Authorized>
    )
  }
}

export default AuthorizedRoute

这里采用了render props的方式:如果提供了component props就用component渲染,否则使用render渲染。

菜单权限

菜单权限的处理相对就简单很多了,统一集成到SiderMenu组件处理:

export default class SiderMenu extends PureComponent {
  constructor(props) {
    super(props)
  }

  
  getSubMenuOrItem = item => {
    if (item.children && item.children.some(child => child.name)) {
      const childrenItems = this.getNavMenuItems(item.children)
      // 当无子菜单时就不展示菜单
      if (childrenItems && childrenItems.length > 0) {
        return (
          <SubMenu
            title={
              item.icon ? (
                <span>
                  {getIcon(item.icon)}
                  <span>{item.name}</span>
                </span>
              ) : (
                item.name
              )
            }
            key={item.path}
          >
            {childrenItems}
          </SubMenu>
        )
      }
      return null
    }
    return <Menu.Item key={item.path}>{this.getMenuItemPath(item)}</Menu.Item>
  }

  
  getNavMenuItems = menusData => {
    if (!menusData) {
      return []
    }
    return menusData
      .filter(item => item.name && !item.hideInMenu)
      .map(item => {
        // make dom
        const ItemDom = this.getSubMenuOrItem(item)
        return this.checkPermissionItem(item.authority, ItemDom)
      })
      .filter(item => item)
  }

  
  checkPermissionItem = (authority, ItemDom) => {
    const { Authorized } = this.props

    if (Authorized && Authorized.check) {
      const { check } = Authorized
      return check(authority, ItemDom)
    }
    return ItemDom
  }

  render() {
    // ...
    return
      <Sider
        trigger={null}
        collapsible
        collapsed={collapsed}
        breakpoint="lg"
        onCollapse={onCollapse}
        className={siderClass}
      >
        <div className="loGo">
          <Link to="/home" className="logo-link">
            {!collapsed && <h1>冯言冯语</h1>}
          </Link>
        </div>

        <Menu
          key="Menu"
          theme={theme}
          mode={mode}
          {...menuProps}
          onOpenChange={this.handleOpenChange}
          selectedKeys={selectedKeys}
        >
          {this.getNavMenuItems(menuData)}
        </Menu>
      </Sider>
  }
}

这里我只贴了一些核心代码,其中的checkPermissionItem就是实现菜单权限的关键。他同样用到了上文中的check方法来对当前菜单进行权限比对,如果没有权限就直接不展示当前菜单。

到此这篇关于React中的权限组件设计的文章就介绍到这了,更多相关React权限组件内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: React中的权限组件设计问题小结

本文链接: https://www.lsjlt.com/news/154292.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • React中的权限组件设计问题小结
    目录背景所谓的权限控制是什么?实现思路路由权限菜单权限背景 权限管理是中后台系统中常见的需求之一。之前做过基于 Vue 的后台管理系统权限控制,基本思路就是在一些路由钩子里做权限比对...
    99+
    2024-04-02
  • React中的权限组件设计问题怎么解决
    这篇“React中的权限组件设计问题怎么解决”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“React中的权限组件设计问题怎么...
    99+
    2023-07-02
  • MySQL存储过程的权限问题小结
    MySQL的存储过程,没错,看起来好生僻的使用场景。问题源于一个开发同学提交了权限申请的工单,需要开通一些权限。 本来是一个很正常的操作,但在我来看是比较着急且紧迫的,说来惭愧,忙着方向规划和开发的事情,这...
    99+
    2024-04-02
  • React 组件权限控制的实现
    目录前话正文1. 控制方式1.1 直接计算1.2 通用权限Hoc1.3 权限包裹组件2. 控制结果2.1 显隐控制2.2 自定义渲染3. 权限数据3.1 静态权限3.2 动态权限前话...
    99+
    2024-04-02
  • 在React中使用Redux解决的问题小结
    目录在 React 中使用 Redux 解决的问题在 React 项目中加入 Redux 的好处React + Redux 安装 ReduxReact 中 Redux 的工作流程Re...
    99+
    2022-11-13
    React + Redux 案例 React 使用 Redux
  • 一文带你了解小程序中的权限设计
    目录前言1 权限表设计2、创建应用3、页面功能实现4、定义变量5、变量的初始化6、用户注册总结前言 日常我们开发小程序的时候,经常需要考虑权限如何设计,比如在我的页面,管理员可以看到...
    99+
    2024-04-02
  • react组件中过渡动画的问题解决
    目录一、是什么二、如何实现CSSTransitionSwitchTransitionTransitionGroup一、是什么 在日常开发中,页面切换时的转场动画是比较基础的一个场景 ...
    99+
    2024-04-02
  • SQL权限设置的问题实例分析
    这篇文章主要讲解了“SQL权限设置的问题实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“SQL权限设置的问题实例分析”吧! 第一个是对于一个用户的操...
    99+
    2024-04-02
  • 如何解决React中组件通信问题
    这篇文章将为大家详细讲解有关如何解决React中组件通信问题,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。父子组件父 → 子parent组件传给child组件,符合rea...
    99+
    2024-04-02
  • 解决react组件渲染两次的问题
    目录react组件渲染两次react总结之避免不必要的重复渲染类组件PureComponent使用插件seamless-immutable使用插件pure-render-decora...
    99+
    2022-11-13
    react组件渲染 react渲染两次 react组件
  • php中的访问权限如何设置
    今天小编给大家分享一下php中的访问权限如何设置的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。1、文件权限文件权限是指操作系...
    99+
    2023-07-05
  • 关于antd tree和父子组件之间的传值问题(react 总结)
    项目需求:点击产品树节点时获取该节点的所有父节点,同时回填表格的搜索条件,完成搜索功能,搜索结果展示在下方的table中。 写了三个组件: 现在有个业务场景交互:在orderTr...
    99+
    2024-04-02
  • React-Native之截图组件react-native-view-shot的介绍与使用小结
    目录一、现象二、解决三、总结:一、现象 1、需求:把某展示页面进行截取保存到相册、并可进行以海报的形式分享出去; 2、支持iOS和安卓 二、解决 1、安装: npm i --save...
    99+
    2024-04-02
  • 关于mysql中root权限丢失的问题
    刚听一哥们说执行了一条语句:revoke all on *.* from root@localhost;            --呵呵,当时到底...
    99+
    2024-04-02
  • 小程序中如何设计click-scroll组件
    这篇文章给大家分享的是有关小程序中如何设计click-scroll组件的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一. 背景有些业务需求,要求前端展示的内容多时可以通过scro...
    99+
    2024-04-02
  • Go语言中如何处理并发文件的文件系统文件权限和ACL权限管理问题
    在Go语言中处理并发文件的文件系统文件权限和ACL权限管理问题,可以使用os包和syscall包中提供的函数和方法来实现。1. 文件...
    99+
    2023-10-09
    Go语言
  • java中的通用权限管理设计(推荐)
    实现业务系统中的用户权限管理B/S系统中的权限比C/S中的更显的重要,C/S系统因为具有特殊的客户端,所以访问用户的权限检测可以通过客户端实现或通过客户端+服务器检测实现,而B/S中,浏览器是每一台计算机都已具备的,如果不建立一个完整的权限...
    99+
    2023-05-30
    java 通用权限管理 设计
  • Go语言中如何处理并发文件的文件系统文件权限和ACL权限管理问题?
    Go语言中如何处理并发文件的文件系统文件权限和ACL权限管理问题?在Go语言中,使用标准库中的os和os/user包可以轻松地处理文件系统文件权限和ACL权限的管理问题。在处理并发文件时,我们可以通过如下步骤来实现对文件权限的控制。获取文件...
    99+
    2023-10-22
    并发处理 文件系统权限 ACL权限管理
  • Go语言中如何处理并发文件的文件系统访问权限问题?
    Go语言中如何处理并发文件的文件系统访问权限问题?在并发编程中,处理文件系统访问权限是一个重要的问题。在Go语言中,我们可以利用互斥锁(Mutex)和读写锁(RWMutex)来实现对文件的并发访问控制。本文将介绍如何使用互斥锁和读写锁来处理...
    99+
    2023-10-22
    权限 并发 文件系统
  • PHP中数组长度的限制问题
    PHP中数组长度的限制问题 在PHP中,数组是一种非常常用的数据结构,用于存储一组数据。然而,对于数组的长度在PHP中是有一定的限制的。在本文中,我们将探讨PHP中数组长度的限制问题,...
    99+
    2024-03-14
    php数组长度 php限制问题 数组限制
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作