Files
xinlong-shop-taro/src/contexts/UserContext.tsx
赵忠林 c870e75b3b feat(auth): 实现禁用用户拦截机制防止禁用用户访问
- 新增工具函数 isUserDisabled 判断用户是否被禁用
- 新增 blockDisabledUser 函数拦截禁用用户,清除登录态弹窗提示并跳转登录页
- UserContext 启动时异步校验用户状态,禁用则拦截并退出
- UserContext 的 refreshUser、syncFromStorage、loginUser 接口均增加禁用检查
- 登录相关 API 登录前校验用户状态,禁用时拒绝登录
- 登录页面保存登录态前校验,禁用用户弹窗提示阻断登录流程
- 用户状态定义:status 0 为正常,1 为禁用
- 新增用户管理页支持查询和管理用户状态
- 文档中补充用户状态与禁用机制说明及拦截层级介绍
2026-07-14 23:11:29 +08:00

157 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { createContext, useContext, useState, useEffect, type ReactNode } from 'react'
import Taro from '@tarojs/taro'
import type { User } from '@/api/system/user/model'
import { getUserInfo } from '@/api/layout'
import { saveStorageByLoginUser, clearStorageByLoginUser } from '@/utils/server'
import { isUserDisabled, blockDisabledUser } from '@/utils/auth'
interface UserContextType {
user: User | null
isLoggedIn: boolean
loading: boolean
loginUser: (token: string, userInfo: User) => void
logoutUser: () => void
refreshUser: () => Promise<User | null>
syncFromStorage: () => boolean
}
const UserContext = createContext<UserContextType | undefined>(undefined)
export const UserProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
const init = async () => {
const token = Taro.getStorageSync('access_token')
const userData = Taro.getStorageSync('User')
// 情况1: 有本地token和用户数据先用缓存快速显示再异步校验状态
if (token && userData) {
const parsed = typeof userData === 'string' ? JSON.parse(userData) : userData
setUser(parsed as User)
setLoading(false)
// 异步从服务端校验用户是否被禁用(防止被禁用后仍可使用)
verifyUserStatusFromServer()
return
}
// 情况2: 有token但没有用户数据尝试从接口获取
if (token) {
try {
const userInfo = await getUserInfo()
if (isUserDisabled(userInfo)) {
blockDisabledUser(userInfo)
setUser(null)
setLoading(false)
return
}
setUser(userInfo)
setLoading(false)
return
} catch {
// token无效清除
clearStorageByLoginUser()
}
}
// 无本地token保持未登录状态
setLoading(false)
}
init()
}, [])
/** 异步从服务端拉取最新用户信息,校验是否被禁用 */
const verifyUserStatusFromServer = async () => {
try {
const userInfo = await getUserInfo()
if (isUserDisabled(userInfo)) {
// 用户已被禁用,拦截
blockDisabledUser(userInfo)
setUser(null)
} else {
// 状态正常,更新本地缓存和状态
const token = Taro.getStorageSync('access_token')
if (token) {
saveStorageByLoginUser(token, userInfo)
}
setUser(userInfo)
}
} catch {
// 网络错误或token失效静默处理已有缓存的登录态可用
}
}
const loginUser = (token: string, userInfo: User) => {
if (isUserDisabled(userInfo)) {
blockDisabledUser(userInfo)
return
}
saveStorageByLoginUser(token, userInfo)
setUser(userInfo)
}
const logoutUser = () => {
clearStorageByLoginUser()
setUser(null)
}
/** 从 storage 快速同步用户状态(无需网络请求),供页面 useDidShow 调用 */
const syncFromStorage = (): boolean => {
const token = Taro.getStorageSync('access_token')
const userData = Taro.getStorageSync('User')
if (token && userData) {
const parsed = typeof userData === 'string' ? JSON.parse(userData) : userData
// 快速检查缓存中的 status如果已禁用则拦截
if (isUserDisabled(parsed)) {
blockDisabledUser(parsed)
setUser(null)
return false
}
setUser(parsed as User)
return true
}
if (!token) {
setUser(null)
}
return false
}
const refreshUser = async (): Promise<User | null> => {
if (!Taro.getStorageSync('access_token')) return null
try {
const userInfo = await getUserInfo()
if (isUserDisabled(userInfo)) {
blockDisabledUser(userInfo)
setUser(null)
return null
}
const token = Taro.getStorageSync('access_token')
if (token) {
saveStorageByLoginUser(token, userInfo)
}
setUser(userInfo)
return userInfo
} catch {
return null
}
}
return (
<UserContext.Provider
value={{ user, isLoggedIn: !!user, loading, loginUser, logoutUser, refreshUser, syncFromStorage }}
>
{children}
</UserContext.Provider>
)
}
export const useUserContext = (): UserContextType => {
const context = useContext(UserContext)
if (!context) {
throw new Error('useUserContext must be used within UserProvider')
}
return context
}