我们react的相关知识应该是差不多了,现在来写一个案例。我们要用到router,redix,RTKQ结合使用去实现我们的权限案例,也就是实现登录注册的效果。
实际上一个网站网站。有不需要权限就可以访问的页面,有需要权限才可以访问的页面。权限就是指我们是否登录。
那么我们首先去实现登录和注册的效果。
1.登录注册逻辑
好好想一想登录注册是一个组件里面的,注册了就显示登录,没有账号就显示注册。那首先就是需要一个state去判断登录还是注册。
登录注册都需要用户输入内容,也就是表单。我们需要去设计表单ui去获取用户输入的账户密码包括注册时候的email。
通过最开始的state判断登录注册需要一个点击事件去主动切换登录和注册。当然表单需要一个按钮添加点击事件去引发我们的注册和登录请求。当然按钮的文本内容也是根据这个state去设置。我们看代码。
import React from 'react'
import { useRegisterMutation, useLoginMutation } from '../../store/api/authApi'
import { useDispatch } from 'react-redux'
import { login, loginout } from '../../store/Slice/authSlice'
import { useNavigate } from 'react-router'
export default function AuthForm() {
const ref1 = React.useRef(null)
const ref2 = React.useRef(null)
const ref3 = React.useRef(null)
const dispatch = useDispatch()
const nav = useNavigate()
const [isLogin, setIsLogin] = React.useState(true)
const [registerFn, { error: regError }] = useRegisterMutation()
const [loginFn, { error: loginError }] = useLoginMutation()
const submit = (event) => {
event.preventDefault()
//获取用户输入的内容
const username = ref1.current.value
const password = ref2.current.value
//处理登录/注册的功能
if (isLogin) {
console.log('登录需要验证的信息', username, password);
loginFn({
identifier: username,
password
}).then((res) => {
if (!res.error) {
console.log('登录成功', res);
dispatch(login({
token: res.data.jwt,
user: res.data.user
}))
nav('/', { replace: true })
//登录成功后需要向系统中添加标识标记用户的登录状态
//登录状态(布尔值,token(jwt),用户信息)
//跳转页面到根目录
}
})
} else {
const email = ref3.current.value
const obj = {
username: username,
password: password,
email: email
}
console.log({ data: obj });
// console.log('注册需要传递的信息', username, password, email);
registerFn({
username,
password,
email
}).then((res) => {
if (!res.error) {
//注册成功
setIsLogin(true)
}
})
}
}
return (
<div>
<p style={{ color: 'red' }}>{regError && regError.data.error.message}</p>
<p style={{ color: 'red' }}>{loginError && loginError.data.error.message}</p>
<h2>{isLogin ? '登录' : '注册'}</h2>
<form action="">
<div>
<input ref={ref1} type="text" name="" id="username" placeholder={'用户名'} />
</div>
{
!isLogin && <input ref={ref3} type="text" name="" id="email" placeholder={'邮箱'} />
}
<div>
<input ref={ref2} type="password" name="" id="password" placeholder={'密码'} />
</div>
<button onClick={submit}>{isLogin ? '登录' : '注册'}</button>
<a href="#" onClick={(e) => {
e.preventDefault()
setIsLogin((preState) => !preState)
}}>{!isLogin ? '已有账号?点击登录' : '没有账号?点击注册'}</a>
</form>
</div>
)
}
通过这个isLog设置好了登录注册表单的样式之后,我们用RTKQ去设置我们的登录注册请求方法。
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
const authApi = createApi({
reducerPath: 'authApi',
baseQuery: fetchBaseQuery({
baseUrl: 'http://localhost:1337/api/'
}),
endpoints(build) {
return {
register: build.mutation({
query(user) {
return {
url: 'auth/local/register',
method: 'post',
body: user //user需要username,password,email
}
}
}),
login: build.mutation({
query(user) {
return {
url: 'auth/local',
method: 'post',
body: user//identifier
}
}
})
}
}
})
export const {
useRegisterMutation,
useLoginMutation
} = authApi
export default authApi
这样我们就可以用钩子数组结构获取发送请求的方法以及请求成功后的数据。然后直接调用方法传注册和登录的body就可以了。这里用的ref去获取表单DOMvalue值。没有受控组件。
然后登录的时候就可以看到控制台成功返回了res,包含jwt以及我们的user数据。
编辑
2.用redux存储登录状态
登录注册写好了并且可以拿到响应数据以及jwt了,那么我们就可以把响应的数据存起来了,我们选择存redux里面。我们用Slice去设置reducer。
import { createSlice } from '@reduxjs/toolkit'
export const authSlice = createSlice({
name: 'auth',
initialState: {
isLog: false,
token: '',
user: null
},
reducers: {
login(state, action) {
state.isLog = true
state.token = action.payload.token
state.user = action.payload.user
},
loginout(state, action) {
state.isLog = false
state.token = null
state.user = null;
}
}
})
export const { login, loginout } = authSlice.actions
然后就可以用useDispatch钩子获取使用reducer方法,以及loginloginout这个自动生成action方法,传token,以及user过来就好了。
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Link } from 'react-router'
import { loginout } from '../../store/Slice/authSlice'
export default function Menu() {
const { isLog, user } = useSelector((state) => state.auth)
const dispatch = useDispatch()
return (
<div>
<ul>
<li><Link to={'/'}>首页</Link></li>
{isLog &&
<>
<li ><Link to={'/profile'}>{user.username}</Link></li>
<li><Link to={'/'} onClick={() => { return (dispatch(loginout())) }}>登出</Link></li>
</>
}
{!isLog &&
<>
<li><Link to={'/auth-form'}>登录/注册</Link></li>
</>
}
</ul>
</div>
)
}
然后导航栏就通过lsLog去设置是否显示个人信息,以及登出(这里只是跳转到了首页并且把loginout()去把state回复登陆前的)。然后我们就实现了表面上的登录注册,也就是所谓的登录后才能查看到个人信息以及登出这些功能组件。
3.真正的添加权限
现在的问题是只是页面上实现了所谓的权限变化切换组件,但实际上还是可以通过链接去访问其他页面。
那么我们现在来解决就是防止直接输入我们的个人信息路径直接展示了这个组件。
其实很简单,无非还是isLog这个redux里面存的状态。
编辑
import React from 'react'
import { useSelector } from 'react-redux'
import { Navigate, useLocation } from 'react-router'
export default function NeedAuth(props) {
const auth = useSelector(state => state.auth)
const location = useLocation()
return (
auth.isLog ? props.children : <Navigate to={'/auth-form'} state={{ preLocation: location }} />
)
}
首先给所有需要权限才能访问的路由设置判断,如果没有登录,那么就直接跳转到登录页面。
编辑
登录了才可以去访问对应路径的组件。这样路径方面就解决了。
4.解决持久化问题
我们一刷新登录状态就会失效,因为组件重新渲染,导致丢失了登录相关状态。因为数据都存到了reducer,也就是state里面就是内存里面,刷新就会丢失。所以我们需要本地存储。
import { createSlice } from '@reduxjs/toolkit'
export const authSlice = createSlice({
name: 'auth',
// initialState: {
// isLog: false,
// token: '',
// user: null
// },
initialState: () => {
const token = localStorage.getItem('token')
if (!token) {
return {
isLog: false,
token: '',//服务器发送到token默认有效期是一个月
user: null
}
}
return {
isLog: true,
token,
user: JSON.parse(localStorage.getItem('user'))
}
},
reducers: {
login(state, action) {
state.isLog = true
state.token = action.payload.token
state.user = action.payload.user
//将数据存储到本地存储里面
localStorage.setItem('token', state.token)
localStorage.setItem('user', JSON.stringify(state.user))
},
loginout(state, action) {
state.isLog = false
state.token = null
state.user = null;
localStorage.removeItem('token')
localStorage.removeItem('user')
}
}
})
export const { login, loginout } = authSlice.actions
首先当我们加工方法获取到token,以及user的时候,我们通过localStorage用keyvalue键值对的方式去存到浏览器的本地存储中。然后我们就可以在定义状态初始化的时候,通过token是不是本地有了,没有就没登录过,那就按照false以及token为‘’user为null。如果本地有token,那就按照本地存的去初始化token,user对象。这样我们就实现了数据的持久化。
5.自动登出
现在还有一个问题,就是token实际上是有期限的,可能服务器那边一个月就给我们失效掉了。就需要重新登录,但是我们客户端不知道,可能仍然在登录,但是和服务器实际上脱钩了却不知道,所以就需要去设置到时间内自动登出
import { createSlice } from '@reduxjs/toolkit'
export const authSlice = createSlice({
name: 'auth',
// initialState: {
// isLog: false,
// token: '',
// user: null
// },
initialState: () => {
const token = localStorage.getItem('token')
if (!token) {
return {
isLog: false,
token: '',//服务器发送到token默认有效期是一个月
user: null,
expirationTime: 0//登录状态失效的时间
}
}
return {
isLog: true,
token,
user: JSON.parse(localStorage.getItem('user')),
expirationTime: +localStorage.getItem('expirationTime')
}
},
reducers: {
login(state, action) {
state.isLog = true
state.token = action.payload.token
state.user = action.payload.user
//获取当前时间戳
const currentTime = Date.now()
//设置登录时间有效时间
// const timeout = 1000 * 60 * 60 * 24 * 7//一周
const timeout = 10000
state.expirationTime = currentTime + timeout //得到了一个失效时期
//将数据存储到本地存储里面
localStorage.setItem('token', state.token)
localStorage.setItem('user', JSON.stringify(state.user))
localStorage.setItem('expirationTime', state.expirationTime + '')
},
loginout(state, action) {
state.isLog = false
state.token = null
state.user = null;
localStorage.removeItem('token')
localStorage.removeItem('user')
localStorage.removeItem('expirationTime')
}
}
})
export const { login, loginout } = authSlice.actions
我们添加一个state属性expirationTime:0,然后在登录的reducer里面设置值。是一个时间戳。我们这里是10秒,然后添加到浏览器存储中。然后赋值到state里面。然后在hook设置钩子函数,获取auth中的expirationTime值,然后减去当前时间戳。然后判断如果小于6秒直接登出,如果不是就减去之后的值设置定时器,时间到了就登出。然后引入到app组件里面就好了。
6.服务器验证
如果我们想要去访问一个新的数据表但是我们把数据库中的public权限关闭,那么我们只能在已认证的情况下才能够有权限对student这个表中的数据发送增删改查,那么就需要让服务器知道我们是有身份认证的这个证明的。
编辑
那上一篇文章包括这里也说了登录的时候服务器响应头中给我们了一个jwt,这个令牌,那么我们就需要在请求头中加上这个令牌就可以了。
编辑
这里就用set方法加上了如果有token的话,就添加上这个请求头,属性名是Authorization,然后值的前面值得注意的是需要加Bearer,翻译是承载。然后后面加空格然后带上token服务器就可以验证我们的登录身份了。
到这里我们就完成了这个案例的练习。其实是很简单的案例,只不过我们通过这个案例练习到的思路是很重要的。实际开发思路就是这样的。就是首先要区分好那些组件是我们必须登录后展示的,我们就需要去设置路由去控制如果没有登录,那么就只能跳转到登录组件。用redux存储登录状态,当我们登录请求发出后返回来的jwt,以及user数据我们都需要去拿到。而且我们还需要自己设置一个isLog布尔值,全程很多组件都用isLog去判断是否显示。然后在组成路由的时候去设置如果没有登录就强制跳转到登录页面。
包括持久化就是把token,user保存到本地,然后通过本地是否有token去初始值state,然后就是自动登出,通过在state添加一个新属性,然后登录的时候,去设置一个时间戳加上我们自定义的时间然后保存到本地,如果没登陆就是0,登录的时候去设置,如果已经登录过了,那么就直接从本地拿出来用就ok了。然后就是设置一个钩子去判断是否过期,我们把时间戳拿出来减去当前时间戳,那么剩下了的值就是剩余的时间,设置定时间就可以了。然后return中的清理函数清空定时器,如果组件卸载或者依赖变化的话。