欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

React中的權(quán)限組件設(shè)計問題小結(jié)

 更新時間:2022年07月13日 11:27:40   作者:前端森林  
這篇文章主要介紹了React中的權(quán)限組件設(shè)計,整個過程也是遇到了很多問題,本文主要來做一下此次改造工作的總結(jié),對React權(quán)限組件相關(guān)知識感興趣的朋友一起看看吧

背景

權(quán)限管理是中后臺系統(tǒng)中常見的需求之一。之前做過基于 Vue 的后臺管理系統(tǒng)權(quán)限控制,基本思路就是在一些路由鉤子里做權(quán)限比對和攔截處理。

最近維護(hù)的一個后臺系統(tǒng)需要加入權(quán)限管理控制,這次技術(shù)棧是React,我剛開始是在網(wǎng)上搜索一些React路由權(quán)限控制,但是沒找到比較好的方案或思路。

這時想到ant design pro內(nèi)部實(shí)現(xiàn)過權(quán)限管理,因此就專門花時間翻閱了一波源碼,并在此基礎(chǔ)上逐漸完成了這次的權(quán)限管理。

整個過程也是遇到了很多問題,本文主要來做一下此次改造工作的總結(jié)。

原代碼基于 react 16.x、dva 2.4.1 實(shí)現(xiàn),所以本文是參考了ant-design-pro v1內(nèi)部對權(quán)限管理的實(shí)現(xiàn)

所謂的權(quán)限控制是什么?

一般后臺管理系統(tǒng)的權(quán)限涉及到兩種:

  • 資源權(quán)限
  • 數(shù)據(jù)權(quán)限

資源權(quán)限一般指菜單、頁面、按鈕等的可見權(quán)限。

數(shù)據(jù)權(quán)限一般指對于不同用戶,同一頁面上看到的數(shù)據(jù)不同。

本文主要是來探討一下資源權(quán)限,也就是前端權(quán)限控制。這又分為了兩部分:

  • 側(cè)邊欄菜單
  • 路由權(quán)限

在很多人的理解中,前端權(quán)限控制就是左側(cè)菜單的可見與否,其實(shí)這是不對的。舉一個例子,假設(shè)用戶guest沒有路由/setting的訪問權(quán)限,但是他知道/setting的完整路徑,直接通過輸入路徑的方式訪問,此時仍然是可以訪問的。這顯然是不合理的。這部分其實(shí)就屬于路由層面的權(quán)限控制。

實(shí)現(xiàn)思路

關(guān)于前端權(quán)限控制一般有兩種方案:

  • 前端固定路由表和權(quán)限配置,由后端提供用戶權(quán)限標(biāo)識
  • 后端提供權(quán)限和路由信息結(jié)構(gòu)接口,動態(tài)生成權(quán)限和菜單

我們這里采用的是第一種方案,服務(wù)只下發(fā)當(dāng)前用戶擁有的角色就可以了,路由表和權(quán)限的處理統(tǒng)一在前端處理。

整體實(shí)現(xiàn)思路也比較簡單:現(xiàn)有權(quán)限(currentAuthority)和準(zhǔn)入權(quán)限(authority)做比較,如果匹配則渲染和準(zhǔn)入權(quán)限匹配的組件,否則渲染無權(quán)限組件(403 頁面)

路由權(quán)限

既然是路由相關(guān)的權(quán)限控制,我們免不了先看一下當(dāng)前的路由表:

{
    "name": "活動列表",
    "path": "/activity-mgmt/list",
    "key": "/activity-mgmt/list",
    "exact": true,
    "authority": [
        "admin"
    ],
    "component": ? LoadableComponent(props),
    "inherited": false,
    "hideInBreadcrumb": false
},
{
    "name": "優(yōu)惠券管理",
    "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": "營銷錄入系統(tǒng)",
    "path": "/marketRule-manage",
    "key": "/marketRule-manage",
    "exact": true,
    "component": ? LoadableComponent(props),
    "inherited": true,
    "hideInBreadcrumb": false
}

這份路由表其實(shí)是我從控制臺 copy 過來的,內(nèi)部做了很多的轉(zhuǎn)換處理,但最終生成的就是上面這個對象。

這里每一級菜單都加了一個authority字段來標(biāo)識允許訪問的角色。component代表路由對應(yīng)的組件:

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

"/activity-mgmt/list": {
    component: dynamicWrapper(app, ["activityMgmt"], () => import("../routes/activity-mgmt/list"))
},
// 動態(tài)引用組件并注冊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>
      )
    }
  })
}
復(fù)制代碼

有了路由表這份基礎(chǔ)數(shù)據(jù),下面就讓我們來看下如何通過一步步的改造給原有系統(tǒng)注入權(quán)限。

先從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

這是一個非常常規(guī)的路由配置,既然要加入權(quán)限,比較合適的方式就是包一個高階組件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的大致實(shí)現(xiàn):

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} />
      } />
    )
  }
}

我們看一下這個組件有什么問題:頁面可能允許多個角色訪問,用戶擁有的角色也可能是多個(可能是字符串,也可呢是數(shù)組)。

直接在組件中判斷顯然不太合適,我們把這部分邏輯抽離出來:

/**
 * 通用權(quán)限檢查方法
 * Common check permissions method
 * @param { 菜單訪問需要的權(quán)限 } authority
 * @param { 當(dāng)前角色擁有的權(quán)限 } currentAuthority
 * @param { 通過的組件 Passing components } target
 * @param { 未通過的組件 no pass components } Exception
 */
const checkPermissions = (authority, currentAuthority, target, Exception) => {
  console.log("checkPermissions -----> authority", authority)
  console.log("currentAuthority", currentAuthority)
  console.log("target", target)
  console.log("Exception", Exception)

  // 沒有判定權(quán)限.默認(rèn)查看所有
  // Retirement authority, return target;
  if (!authority) {
    return target
  }
  // 數(shù)組處理
  if (Array.isArray(authority)) {
    // 該菜單可由多個角色訪問
    if (authority.indexOf(currentAuthority) >= 0) {
      return target
    }
    // 當(dāng)前用戶同時擁有多個角色
    if (Array.isArray(currentAuthority)) {
      for (let i = 0; i < currentAuthority.length; i += 1) {
        const element = currentAuthority[i]
        // 菜單訪問需要的角色權(quán)限 < ------ > 當(dāng)前用戶擁有的角色
        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字段默認(rèn)都可以訪問。

接著分別對authority為字符串和數(shù)組的情況做了處理,其實(shí)就是簡單的查找匹配,匹配到了就可以訪問,匹配不到就返回Exception,也就是我們自定義的異常頁面。

有一個點(diǎn)一直沒有提:用戶當(dāng)前角色權(quán)限 currentAuthority 如何獲取?這個是在頁面初始化時從接口讀取,然后存到 store

有了這塊邏輯,我們對剛剛的AuthorizedRoute做一下改造。首先抽象一個Authorized組件,對權(quán)限校驗(yàn)邏輯做一下封裝:

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渲染。

菜單權(quán)限

菜單權(quán)限的處理相對就簡單很多了,統(tǒng)一集成到SiderMenu組件處理:

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

  /**
   * get SubMenu or Item
   */
  getSubMenuOrItem = item => {
    if (item.children && item.children.some(child => child.name)) {
      const childrenItems = this.getNavMenuItems(item.children)
      // 當(dāng)無子菜單時就不展示菜單
      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>
  }

  /**
   * 獲得菜單子節(jié)點(diǎn)
   * @memberof SiderMenu
   */
  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)
  }

  /**
   *
   * @description 菜單權(quán)限過濾
   * @param {*} authority
   * @param {*} ItemDom
   * @memberof SiderMenu
   */
  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就是實(shí)現(xiàn)菜單權(quán)限的關(guān)鍵。他同樣用到了上文中的check方法來對當(dāng)前菜單進(jìn)行權(quán)限比對,如果沒有權(quán)限就直接不展示當(dāng)前菜單。

到此這篇關(guān)于React中的權(quán)限組件設(shè)計的文章就介紹到這了,更多相關(guān)React權(quán)限組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 利用React高階組件實(shí)現(xiàn)一個面包屑導(dǎo)航的示例

    利用React高階組件實(shí)現(xiàn)一個面包屑導(dǎo)航的示例

    這篇文章主要介紹了利用React高階組件實(shí)現(xiàn)一個面包屑導(dǎo)航的示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • React項目中className運(yùn)用及問題解決

    React項目中className運(yùn)用及問題解決

    這篇文章主要為大家介紹了React項目中className運(yùn)用及問題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • React Hook useState useEffect componentDidMount componentDidUpdate componentWillUnmount問題

    React Hook useState useEffect componentD

    這篇文章主要介紹了React Hook useState useEffect componentDidMount componentDidUpdate componentWillUnmount問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • 一文詳解ReactNative狀態(tài)管理redux-toolkit使用

    一文詳解ReactNative狀態(tài)管理redux-toolkit使用

    這篇文章主要為大家介紹了ReactNative狀態(tài)管理redux-toolkit使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • 解決React報錯Cannot?find?namespace?context

    解決React報錯Cannot?find?namespace?context

    這篇文章主要為大家介紹了React報錯Cannot?find?namespace?context分析解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • React中的useState如何改變值不重新渲染的問題

    React中的useState如何改變值不重新渲染的問題

    這篇文章主要介紹了React中的useState如何改變值不重新渲染的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • React自定義視頻全屏按鈕實(shí)現(xiàn)全屏功能

    React自定義視頻全屏按鈕實(shí)現(xiàn)全屏功能

    這篇文章主要介紹了React自定義視頻全屏按鈕實(shí)現(xiàn)全屏功能,通過繪制全屏按鈕,并綁定點(diǎn)擊事件,編寫點(diǎn)擊事件,通過實(shí)例代碼給大家詳細(xì)講解,需要的朋友可以參考下
    2022-11-11
  • 淺談React和Redux的連接react-redux

    淺談React和Redux的連接react-redux

    本篇文章主要介紹了淺談React和Redux的連接react-redux,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-12-12
  • react mobx 基本用法示例小結(jié)

    react mobx 基本用法示例小結(jié)

    mobx是一個輕量級的狀態(tài)管理器,所以很簡單(單一全局?jǐn)?shù)據(jù)使用class)類有g(shù)et 數(shù)據(jù)方法,本文通過示例代碼介紹react mobx 基本用法,感興趣的朋友有一起看看
    2023-11-11
  • React實(shí)現(xiàn)單向數(shù)據(jù)流的方法

    React實(shí)現(xiàn)單向數(shù)據(jù)流的方法

    本文主要介紹了React實(shí)現(xiàn)單向數(shù)據(jù)流的方法
    2023-04-04

最新評論