feat(auth): 实现禁用用户拦截机制防止禁用用户访问

- 新增工具函数 isUserDisabled 判断用户是否被禁用
- 新增 blockDisabledUser 函数拦截禁用用户,清除登录态弹窗提示并跳转登录页
- UserContext 启动时异步校验用户状态,禁用则拦截并退出
- UserContext 的 refreshUser、syncFromStorage、loginUser 接口均增加禁用检查
- 登录相关 API 登录前校验用户状态,禁用时拒绝登录
- 登录页面保存登录态前校验,禁用用户弹窗提示阻断登录流程
- 用户状态定义:status 0 为正常,1 为禁用
- 新增用户管理页支持查询和管理用户状态
- 文档中补充用户状态与禁用机制说明及拦截层级介绍
This commit is contained in:
2026-07-14 23:11:29 +08:00
parent dc123d79a2
commit c870e75b3b
8 changed files with 141 additions and 2 deletions

View File

@@ -7,6 +7,7 @@ import type {
SmsCaptchaResult
} from './model';
import {saveStorageByLoginUser, SERVER_API_URL} from "@/utils/server";
import { isUserDisabled } from '@/utils/auth';
/**
* 账号密码登录
@@ -18,6 +19,9 @@ export async function login(data: LoginParam) {
);
if (res.code === 0) {
if (res.data?.user && res.data?.access_token) {
if (isUserDisabled(res.data.user)) {
return Promise.reject(new Error('您的账号已被禁用,请联系管理员'));
}
saveStorageByLoginUser(res.data.access_token, res.data.user)
}
return res.message;
@@ -46,6 +50,9 @@ export async function loginBySms(data: LoginParam) {
);
if (res.code === 0) {
if(res.data?.user){
if (isUserDisabled(res.data.user)) {
return Promise.reject(new Error('您的账号已被禁用,请联系管理员'));
}
saveStorageByLoginUser(`${res.data?.access_token}`, res.data?.user)
}
return res.message;

View File

@@ -3,6 +3,7 @@ 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
@@ -25,11 +26,14 @@ export const UserProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
const token = Taro.getStorageSync('access_token')
const userData = Taro.getStorageSync('User')
// 情况1: 有本地token和用户数据直接使用
// 情况1: 有本地token和用户数据先用缓存快速显示,再异步校验状态
if (token && userData) {
const parsed = typeof userData === 'string' ? JSON.parse(userData) : userData
setUser(parsed as User)
setLoading(false)
// 异步从服务端校验用户是否被禁用(防止被禁用后仍可使用)
verifyUserStatusFromServer()
return
}
@@ -37,6 +41,12 @@ export const UserProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
if (token) {
try {
const userInfo = await getUserInfo()
if (isUserDisabled(userInfo)) {
blockDisabledUser(userInfo)
setUser(null)
setLoading(false)
return
}
setUser(userInfo)
setLoading(false)
return
@@ -46,13 +56,38 @@ export const UserProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
}
}
// 无本地token保持未登录状态(已禁用静默登录)
// 无本地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)
}
@@ -68,6 +103,12 @@ export const UserProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
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
}
@@ -81,6 +122,11 @@ export const UserProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
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)

View File

@@ -3,6 +3,7 @@ import Taro from '@tarojs/taro'
import { View, Text, Input, Image } from '@tarojs/components'
import { storeLogin } from '@/api/shop/shopStore'
import { saveStorageByLoginUser } from '@/utils/server'
import { isUserDisabled } from '@/utils/auth'
import './index.scss'
const StoreLogin = () => {
@@ -55,6 +56,19 @@ const StoreLogin = () => {
if (res?.access_token) {
const token = res.access_token
const user = res.user
// 检查用户是否被禁用
if (isUserDisabled(user)) {
Taro.showModal({
title: '账号已被禁用',
content: '您的账号已被管理员禁用,暂时无法使用。如有疑问请联系管理员。',
showCancel: false,
confirmText: '我知道了',
confirmColor: '#ee0a24',
})
return
}
saveStorageByLoginUser(token, user)
Taro.showToast({ title: '登录成功', icon: 'success' })
setTimeout(() => navigateAfterLogin(), 800)

View File

@@ -5,6 +5,7 @@ import { TenantId } from '@/config/app'
import { getWxOpenId } from '@/api/layout'
import { getUserInfo } from '@/api/layout'
import { saveStorageByLoginUser } from '@/utils/server'
import { isUserDisabled } from '@/utils/auth'
import {
checkAndHandleInviteRelation,
hasPendingInvite,
@@ -201,6 +202,18 @@ const Login = () => {
user = freshUserInfo
}
// 检查用户是否被禁用
if (isUserDisabled(user)) {
Taro.showModal({
title: '账号已被禁用',
content: '您的账号已被管理员禁用,暂时无法使用小程序。如有疑问请联系门店客服。',
showCancel: false,
confirmText: '我知道了',
confirmColor: '#ee0a24',
})
return
}
saveStorageByLoginUser(token, user)
// 绑定 openid + 处理邀请关系

View File

@@ -5,6 +5,7 @@ import { Button, Checkbox } from '@nutui/nutui-react-taro'
import { TenantId } from '@/config/app'
import { getUserInfo, getWxOpenId } from '@/api/layout'
import { saveStorageByLoginUser } from '@/utils/server'
import { isUserDisabled } from '@/utils/auth'
import {
getStoredInviteParams,
parseInviteParams,
@@ -202,6 +203,18 @@ const Register = () => {
return
}
// 检查用户是否被禁用
if (isUserDisabled(user)) {
Taro.showModal({
title: '账号已被禁用',
content: '您的账号已被管理员禁用,暂时无法使用小程序。如有疑问请联系门店客服。',
showCancel: false,
confirmText: '我知道了',
confirmColor: '#ee0a24',
})
return
}
saveStorageByLoginUser(token, user)
// 注册/登录成功后,立即补齐 openidJSAPI 支付必需)

View File

@@ -1,5 +1,6 @@
import Taro from '@tarojs/taro'
import { clearStorageByLoginUser } from '@/utils/server'
import type { User } from '@/api/system/user/model'
/**
* 登录态 / 游客态 标识。
@@ -34,3 +35,31 @@ export function ensureLoggedIn(redirect?: string): boolean {
export function logout() {
clearStorageByLoginUser()
}
/**
* 判断用户是否被禁用status === 1
*/
export function isUserDisabled(user?: User | null): boolean {
return user?.status === 1
}
/**
* 拦截被禁用的用户:清除登录态、弹窗提示、跳转登录页。
* 返回 false 表示用户已被禁用并已处理,调用方应中止后续流程。
*/
export function blockDisabledUser(user?: User | null): boolean {
if (!isUserDisabled(user)) return true
clearStorageByLoginUser()
Taro.showModal({
title: '账号已被禁用',
content: '您的账号已被管理员禁用,暂时无法使用小程序。如有疑问请联系门店客服。',
showCancel: false,
confirmText: '我知道了',
confirmColor: '#ee0a24',
success: () => {
Taro.reLaunch({ url: '/passport/login' })
},
})
return false
}