成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專(zhuān)欄INFORMATION COLUMN

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

3403771864 / 724人閱讀

  背景

       在項(xiàng)目中要求在后臺(tái)系統(tǒng)控制管理權(quán)限。在之前做過(guò)的后臺(tái)管理系統(tǒng)權(quán)限控制是用Vue,這樣的話就可以用路由鉤子里做權(quán)限比對(duì)和攔截處理。但這次我們說(shuō)的是在一個(gè)后臺(tái)系統(tǒng)需要加入權(quán)限管理控制,技術(shù)棧是React。現(xiàn)在我們就看看實(shí)現(xiàn)過(guò)程吧。

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

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

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

  資源權(quán)限

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

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

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

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

  側(cè)邊欄菜單

  路由權(quán)限

  用戶對(duì)于前端權(quán)限控制就是左側(cè)菜單的可見(jiàn)與否,并不是如此。簡(jiǎn)單來(lái)說(shuō),當(dāng)用戶guest沒(méi)有路由/setting的訪問(wèn)權(quán)限,這樣就可以知道/setting的完整路徑,就可以直接訪問(wèn)進(jìn)入。這樣沒(méi)有任何作用啊。這部分其實(shí)就屬于路由層面的權(quán)限控制。

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

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

  前端固定路由表和權(quán)限配置,由后端提供用戶權(quán)限標(biāo)識(shí)

  后端提供權(quán)限和路由信息結(jié)構(gòu)接口,動(dòng)態(tài)生成權(quán)限和菜單

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

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

  路由權(quán)限

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

  {
  "name": "活動(dòng)列表",
  "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": "營(yíng)銷(xiāo)錄入系統(tǒng)",
  "path": "/marketRule-manage",
  "key": "/marketRule-manage",
  "exact": true,
  "component": ? LoadableComponent(props),
  "inherited": true,
  "hideInBreadcrumb": false
  }

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

  這里每一級(jí)菜單都加了一個(gè)authority字段來(lái)標(biāo)識(shí)允許訪問(wèn)的角色。component代表路由對(duì)應(yīng)的組件:

  import React, { createElement } from "react"
  import Loadable from "react-loadable"
  "/activity-mgmt/list": {
  component: dynamicWrapper(app, ["activityMgmt"], () => import("../routes/activity-mgmt/list"))
  },
  // 動(dòng)態(tài)引用組件并注冊(cè)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ù),下面就讓我們來(lái)看下如何通過(guò)一步步的改造給原有系統(tǒng)注入權(quán)限。

  先從src/router.js這個(gè)入口開(kāi)始著手:

   // 原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({</p>
  <p>
  app,</p>
  <p>
  ...dynamics</p>
  <p>
  })}
  />
  ))}
  <Route component={NoMatch} />
  </Switch>
  </App>
  </ConnectedRouter>
  )
  }
  RouterConfig.propTypes = {
  history: PropTypes.object,
  app: PropTypes.object
  }
  export default RouterConfig

  這是一個(gè)非常常規(guī)的路由配置,既然要加入權(quán)限,比較合適的方式就是包一個(gè)高階組件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>
  )
  }

  來(lái)看下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} />
  } />
  )
  }
  }

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

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

  /**
  * 通用權(quán)限檢查方法
  * Common check permissions method
  * @param { 菜單訪問(wèn)需要的權(quán)限 } authority
  * @param { 當(dāng)前角色擁有的權(quán)限 } currentAuthority
  * @param { 通過(guò)的組件 Passing components } target
  * @param { 未通過(guò)的組件 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)
  // 沒(méi)有判定權(quán)限.默認(rèn)查看所有
  // Retirement authority, return target;
  if (!authority) {
  return target
  }
  // 數(shù)組處理
  if (Array.isArray(authority)) {
  // 該菜單可由多個(gè)角色訪問(wèn)
  if (authority.indexOf(currentAuthority) >= 0) {
  return target
  }
  // 當(dāng)前用戶同時(shí)擁有多個(gè)角色
  if (Array.isArray(currentAuthority)) {
  for (let i = 0; i < currentAuthority.length; i += 1) {
  const element = currentAuthority[i]
  // 菜單訪問(wèn)需要的角色權(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)
  }

  首先如果路由表中沒(méi)有authority字段默認(rèn)都可以訪問(wèn)。

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

  有一個(gè)點(diǎn)一直沒(méi)有提:用戶當(dāng)前角色權(quán)限currentAuthority如何獲?。窟@個(gè)是在頁(yè)面初始化時(shí)從接口讀取,然后存到store中

  有了這塊邏輯,我們對(duì)剛剛的AuthorizedRoute做一下改造。首先抽象一個(gè)Authorized組件,對(duì)權(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)限的處理相對(duì)就簡(jiǎ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)無(wú)子菜單時(shí)就不展示菜單
  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)限過(guò)濾
  * @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>馮言馮語(yǔ)</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方法來(lái)對(duì)當(dāng)前菜單進(jìn)行權(quán)限比對(duì),如果沒(méi)有權(quán)限就直接不展示當(dāng)前菜單。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/127834.html

相關(guān)文章

  • 【單頁(yè)面博客從前端到后端】基于 DVA+ANTD 搭建博客前后臺(tái)界面

    摘要:在的的配置中添加自定義主題由腳手架和官網(wǎng)介紹,我們已經(jīng)自己配置并新建好了主題文件。單頁(yè)面博客從前端到后端環(huán)境搭建單頁(yè)面博客從前端到后端基于搭建博客前后臺(tái)界面單頁(yè)面博客從前端到后端基于和的權(quán)限驗(yàn)證與的設(shè)計(jì) 在上篇文章我們已經(jīng)搭建好了基礎(chǔ)的開(kāi)發(fā)環(huán)境,接下來(lái)會(huì)介紹如何引入 DVA 和 ANTD ,以及在引入過(guò)程中需要注意的問(wèn)題。這里只會(huì)詳細(xì)的書(shū)寫(xiě)部分組件,其他的組件都是大同小異。你可以在 g...

    zqhxuyuan 評(píng)論0 收藏0
  • 面試小結(jié)(一)

    摘要:面試問(wèn)到的問(wèn)題繼承的幾種方法,,原形繼承面向?qū)ο蟮膸追N方法五種方式對(duì)象字面量創(chuàng)建實(shí)例對(duì)象構(gòu)造函數(shù)工廠模式用一個(gè)函數(shù),通過(guò)傳遞參數(shù)返回對(duì)象。打包原理打包原理把所有依賴(lài)打包成一個(gè)文件,通過(guò)代碼分割成單元片段并按需加載。 面試問(wèn)到的問(wèn)題:1、繼承的幾種方法; Call,apply,原形繼承; 2、面向?qū)ο蟮膸追N方法; 五種方式: 1)對(duì)象字面量:var obj={}; 2)創(chuàng)建實(shí)例對(duì)象:va...

    xiaodao 評(píng)論0 收藏0
  • 面試小結(jié)(一)

    摘要:面試問(wèn)到的問(wèn)題繼承的幾種方法,,原形繼承面向?qū)ο蟮膸追N方法五種方式對(duì)象字面量創(chuàng)建實(shí)例對(duì)象構(gòu)造函數(shù)工廠模式用一個(gè)函數(shù),通過(guò)傳遞參數(shù)返回對(duì)象。打包原理打包原理把所有依賴(lài)打包成一個(gè)文件,通過(guò)代碼分割成單元片段并按需加載。 面試問(wèn)到的問(wèn)題:1、繼承的幾種方法; Call,apply,原形繼承; 2、面向?qū)ο蟮膸追N方法; 五種方式: 1)對(duì)象字面量:var obj={}; 2)創(chuàng)建實(shí)例對(duì)象:va...

    SnaiLiu 評(píng)論0 收藏0
  • 面試小結(jié)(一)

    摘要:面試問(wèn)到的問(wèn)題繼承的幾種方法,,原形繼承面向?qū)ο蟮膸追N方法五種方式對(duì)象字面量創(chuàng)建實(shí)例對(duì)象構(gòu)造函數(shù)工廠模式用一個(gè)函數(shù),通過(guò)傳遞參數(shù)返回對(duì)象。打包原理打包原理把所有依賴(lài)打包成一個(gè)文件,通過(guò)代碼分割成單元片段并按需加載。 面試問(wèn)到的問(wèn)題:1、繼承的幾種方法; Call,apply,原形繼承; 2、面向?qū)ο蟮膸追N方法; 五種方式: 1)對(duì)象字面量:var obj={}; 2)創(chuàng)建實(shí)例對(duì)象:va...

    shmily 評(píng)論0 收藏0
  • react搭建后臺(tái)管理(react初窺)

    摘要:前言以前一直是用進(jìn)行的開(kāi)發(fā)于是決定年后弄一弄所以年后這段時(shí)間也就一直瞎弄可算是看到成果了本來(lái)是想寫(xiě)一個(gè)類(lèi)似仿今日頭條那樣的項(xiàng)目來(lái)入手后來(lái)又尋思還不如寫(xiě)個(gè)后臺(tái)管理呢。于是乎自己便著手簡(jiǎn)單的搭建了一個(gè)集中設(shè)置的版本。 前言 以前一直是用vue進(jìn)行的開(kāi)發(fā), 于是決定年后弄一弄react, 所以年后這段時(shí)間也就一直瞎弄react, 可算是看到成果了 本來(lái)是想寫(xiě)一個(gè) 類(lèi)似 Vue仿今日頭條 那樣...

    wangjuntytl 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<