背景
近期做了一个 spa的单独项目中,有个需求就是希望根据登录人来看下,这个人是不是有权限进入当前页面。虽然服务端做了进行接口的权限,但是每一个路由加载的时候,都要去请求这个接口太浪费了。
需要考虑的
-
登录授权,用户没有登录只能访问登录页面,如果处于登录状态则跳转到当前用户的默认首页
-
路由授权,当前登录用户的角色,如果对一个 URL 没有权限访问,则会呈现403
-
数据授权,当访问一个没有权限的API,则跳转到 403 页面
-
操作授权,当页面中某个按钮或者区域没有权限访问则在页面中隐藏
期望
在路由层面拦截掉,当前这个人对应路由没有权限,面抛出 403,且原组件也不会加载,当然也不会有任何请求
准备工作
react router4 、react-router-config、recompose、状态数据管理库(本文用的 rematch )、hoc(高阶组件)
登录授权方案
router 早期版本有进入路由的钩子函数可以实现这一点,但是 router4 去掉了采取了新的方式。举个例子,就好比登录来说。贴一段伪代码
// app.js
<Router>
<Switch>
{ user.userId
? <AuthorizedLayout routes={routes} />
: <UnauthorizedLayout />
}
</Switch>
</Router>
// AuthorizedLayout.js
<Slider routes={this.state.routes}>
<Switch>
<Route path='/' exact render={() => (
<Redirect to='/Home />
)} />
{renderRoutes(this.state.routes)}
</Switch>
</Slider>
// UnauthorizedLayout .js 就是你的 login 组件
复制代码
路由授权方案
利用高阶组件内部判断 if 返回什么 else 返回什么。 我在下面贴下我怎么实现的。 routes.js
/* 和服务端约定的数据格式:[
{url: 'xxx', authuserId: [],authRoleGroup }
]
该路由对应的所有角色或者角色组
*/
import { Icon } from 'assets'
import { WithAuthRouter } from '@components'
import Home from './home'
// 这里也可以用异步加载看个人喜好
const routes = [
{ path: '/Home', /
exact: true,
root: true,
icon: Icon.COLLABORATION,
title: '主页',
component: WithAuthRouter(Home)
}
复制代码
WithAuthRouter.js
这里我在路由层已经把redux 的props 放到路由里面去了。只要被改函数包过的组件都可以不仅可以拿到 路由信息,还可以拿到 redux 的信息 这样我们的组件就不需要 connect, z这样我们的普通组件就比较纯粹了
同时简单介绍一下我用的几个 recompose 的 api:
-
compose:
从右往左执行函数组合(右侧函数的输出作为左侧函数的输入 -
onlyUpdateForKeys:
指定更新键值,否则禁止更新组件 -
branch:
理解为 if else 返回什么组件 -
renderComponent:
获取组件并返回该组件的高阶组件 -
renderNothing:
返回 null
import React from 'react'
import {connect} from 'react-redux'
import { compose, onlyUpdateForKeys, branch, renderComponent } from 'recompose'
import Exception from 'ant-design-pro/lib/Exception'
// 校验路由权限组件
const isNotAuth = props => {
// route 本路由表,user 全局登录信息、menu 服务端返回的菜单列表
const { route: { path = null }, user = {}, menu = [] } = props
const { userId = null, roleMap = [] } = user
if (userId && roleMap.length) {
const result = menu.find(i => i.menuUrl === path)
if (result) {
return !result.userIdList.includes(userId) && !result.userIdList.filter(i => roleMap.includes(i)).length > 0
}
return true
}
return true
}
const NotFound = () => (
<Exception type='403' actions />
)
const renderWhileIsNotAuth = branch(
isNotAuth,
renderComponent(NotFound)
)
const mapState = state => ({
user: state.user,
menu: state.menu,
inited: state.inited
})
const getUserAndMenu = compose(
connect(mapState),
onlyUpdateForKeys(['user', 'menu'])
)
export default compose(
getUserAndMenu,
renderWhileIsNotAuth
)
复制代码
数据授权
我这里用的 业务原因仅仅是判断登录失效所以和服务端约定返回失效的code码在请求方发里统一处理掉我这里用的的 axios 因为有个拦截器所以在拦截器里做了
axios.interceptors.response.use(config => {
const { data, status } = config
if (status && status === 200) {
// 图片上传接口返回的是result
const { success, response, errorResponse, result } = data
if (success) {
return response || result
} else {
message.error(errorResponse.msg)
if (errorResponse && errorResponse.code === 9100) {
window.location.reload()
}
return null
}
}
return config
}, err => {
console.log(err)
message.error('发生了未知错误')
Promise.reject(err)
})
复制代码
操作授权
也是利用高阶组件
// 鉴权组件
import { branch, renderNothing } from 'recompose'
const withAuth = branch(
props => {
const getAuth = () => {
return something
}
return !getAuth()
},
renderNothing
)
export default withAuth
// 调用
const AuthBtn = Auth(Button)
<AuthBtn
{...someProps}
>
xxx
</AuthBtn>
复制代码
结语
所以综上看出可以看出核心都在高阶组件,后续我会贴出项目地址, 现在先把核心方法放出来,写的比较仓促欢迎留言指正。