React中的权限组件设计问题怎么解决(react,开发技术)

时间:2024-05-08 05:57:32 作者 : 石家庄SEO 分类 : 开发技术
  • TAG :

背景

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

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

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

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

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

所谓的权限控制是什么?

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

  • 资源权限

  • 数据权限

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

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

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

  • 侧边栏菜单

  • 路由权限

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

实现思路

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

  • 前端固定路由表和权限配置,由后端提供用户权限标识

  • 后端提供权限和路由信息结构接口,动态生成权限和菜单

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

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

React中的权限组件设计问题怎么解决

路由权限

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

{"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代表路由对应的组件:

importReact,{createElement}from"react"importLoadablefrom"react-loadable""/activity-mgmt/list":{component:dynamicWrapper(app,["activityMgmt"],()=>import("../routes/activity-mgmt/list"))},//动态引用组件并注册modelconstdynamicWrapper=(app,models,component)=>{//registermodelsmodels.forEach(model=>{if(modelNotExisted(app,model)){//eslint-disable-next-lineapp.model(require(`../models/${model}`).default)}})//()=>require('module')//transformedbybabel-plugin-dynamic-import-node-sync//需要将routerData塞到props中if(component.toString().indexOf(".then(")<0){returnprops=>{returncreateElement(component().default,{...props,routerData:getRouterDataCache(app)})}}//()=>import('module')returnLoadable({loader:()=>{returncomponent().then(raw=>{constComponent=raw.default||rawreturnprops=>createElement(Component,{...props,routerData:getRouterDataCache(app)})})},//全局loadingloading:()=>{return(<divstyle={{display:"flex",justifyContent:"center",alignItems:"center"}}><Spinsize="large"className="global-spin"/></div>)}})}

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

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

//原src/router.jsimportdynamicfrom"dva/dynamic"import{Redirect,Route,routerRedux,Switch}from"dva/router"importPropTypesfrom"prop-types"importReactfrom"react"importNoMatchfrom"./components/no-match"importAppfrom"./routes/app"const{ConnectedRouter}=routerReduxconstRouterConfig=({history,app})=>{constroutes=[{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(<ConnectedRouterhistory={history}><App><Switch>{routes.map(({path,...dynamics},key)=>(<Routekey={key}path={`/${path}`}component={dynamic({app,...dynamics})}/>))}<Routecomponent={NoMatch}/></Switch></App></ConnectedRouter>)}RouterConfig.propTypes={history:PropTypes.object,app:PropTypes.object}exportdefaultRouterConfig

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

functionRouterConfig({history,app}){constrouterData=getRouterData(app)constBasicLayout=routerData["/"].componentreturn(<ConnectedRouterhistory={history}><Switch><AuthorizedRoutepath="/"render={props=><BasicLayout{...props}/>}/></Switch></ConnectedRouter>)}

来看下AuthorizedRoute的大致实现:

constAuthorizedRoute=({component:Component,authority,redirectPath,{...rest}})=>{if(authority===currentAuthority){return(<Route{...rest}render={props=><Component{...props}/>}/>)}else{return(<Route{...rest}render={()=><Redirectto={redirectPath}/>}/>)}}

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

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

/***通用权限检查方法*Commoncheckpermissionsmethod*@param{菜单访问需要的权限}authority*@param{当前角色拥有的权限}currentAuthority*@param{通过的组件Passingcomponents}target*@param{未通过的组件nopasscomponents}Exception*/constcheckPermissions=(authority,currentAuthority,target,Exception)=>{console.log("checkPermissions----->authority",authority)console.log("currentAuthority",currentAuthority)console.log("target",target)console.log("Exception",Exception)//没有判定权限.默认查看所有//Retirementauthority,returntarget;if(!authority){returntarget}//数组处理if(Array.isArray(authority)){//该菜单可由多个角色访问if(authority.indexOf(currentAuthority)>=0){returntarget}//当前用户同时拥有多个角色if(Array.isArray(currentAuthority)){for(leti=0;i<currentAuthority.length;i+=1){constelement=currentAuthority[i]//菜单访问需要的角色权限<------>当前用户拥有的角色if(authority.indexOf(element)>=0){returntarget}}}returnException}//string处理if(typeofauthority==="string"){if(authority===currentAuthority){returntarget}if(Array.isArray(currentAuthority)){for(leti=0;i<currentAuthority.length;i+=1){constelement=currentAuthority[i]if(authority.indexOf(element)>=0){returntarget}}}returnException}thrownewError("unsupportedparameters")}constcheck=(authority,target,Exception)=>{returncheckPermissions(authority,CURRENT,target,Exception)}

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

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

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

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

importReactfrom"react"importCheckPermissionsfrom"./CheckPermissions"classAuthorizedextendsReact.Component{render(){const{children,authority,noMatch=null}=this.propsconstchildrenRender=typeofchildren==="undefined"?null:childrenreturnCheckPermissions(authority,childrenRender,noMatch)}}exportdefaultAuthorized

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

importReactfrom"react"import{Redirect,Route}from"react-router-dom"importAuthorizedfrom"./Authorized"classAuthorizedRouteextendsReact.Component{render(){const{component:Component,render,authority,redirectPath,...rest}=this.propsreturn(<Authorizedauthority={authority}noMatch={<Route{...rest}render={()=><Redirectto={{pathname:redirectPath}}/>}/>}><Route{...rest}render={props=>(Component?<Component{...props}/>:render(props))}/></Authorized>)}}exportdefaultAuthorizedRoute

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

菜单权限

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

exportdefaultclassSiderMenuextendsPureComponent{constructor(props){super(props)}/***getSubMenuorItem*/getSubMenuOrItem=item=>{if(item.children&&item.children.some(child=>child.name)){constchildrenItems=this.getNavMenuItems(item.children)//当无子菜单时就不展示菜单if(childrenItems&&childrenItems.length>0){return(<SubMenutitle={item.icon?(<span>{getIcon(item.icon)}<span>{item.name}</span></span>):(item.name)}key={item.path}>{childrenItems}</SubMenu>)}returnnull}return<Menu.Itemkey={item.path}>{this.getMenuItemPath(item)}</Menu.Item>}/***获得菜单子节点*@memberofSiderMenu*/getNavMenuItems=menusData=>{if(!menusData){return[]}returnmenusData.filter(item=>item.name&&!item.hideInMenu).map(item=>{//makedomconstItemDom=this.getSubMenuOrItem(item)returnthis.checkPermissionItem(item.authority,ItemDom)}).filter(item=>item)}/****@description菜单权限过滤*@param{*}authority*@param{*}ItemDom*@memberofSiderMenu*/checkPermissionItem=(authority,ItemDom)=>{const{Authorized}=this.propsif(Authorized&&Authorized.check){const{check}=Authorizedreturncheck(authority,ItemDom)}returnItemDom}render(){//...return<Sidertrigger={null}collapsiblecollapsed={collapsed}breakpoint="lg"onCollapse={onCollapse}className={siderClass}><divclassName="logo"><Linkto="/home"className="logo-link">{!collapsed&&<h2>冯言冯语</h2>}</Link></div><Menukey="Menu"theme={theme}mode={mode}{...menuProps}onOpenChange={this.handleOpenChange}selectedKeys={selectedKeys}>{this.getNavMenuItems(menuData)}</Menu></Sider>}}

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

 </div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
本文:React中的权限组件设计问题怎么解决的详细内容,希望对您有所帮助,信息来源于网络。
上一篇:vue2模拟vue-element-admin手写角色权限怎么实现下一篇:

7 人围观 / 0 条评论 ↓快速评论↓

(必须)

(必须,保密)

阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18