feat(auth): 添加401未授权统一处理机制
- 在API服务层实现401状态码的统一拦截和处理 - 添加防抖机制,避免短时间内重复处理401错误 - 支持应用层注册自定义的未授权处理器 - 在应用启动时注册401处理器,自动清除登录状态并跳转到登录页 - 同时处理普通请求和流式请求的401响应
This commit is contained in:
@@ -1,8 +1,87 @@
|
||||
import { buildApiUrl } from '@/constants/Api';
|
||||
import AsyncStorage from '@/utils/kvStore';
|
||||
import { Alert } from 'react-native';
|
||||
|
||||
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
||||
|
||||
// 401处理的防抖机制
|
||||
let is401Handling = false;
|
||||
let last401Time = 0;
|
||||
const DEBOUNCE_INTERVAL = 3000; // 3秒内只处理一次401
|
||||
|
||||
// 401处理回调函数(由应用层注册)
|
||||
type UnauthorizedHandler = () => void | Promise<void>;
|
||||
let unauthorizedHandler: UnauthorizedHandler | null = null;
|
||||
|
||||
/**
|
||||
* 注册401未授权处理器
|
||||
* 应该在应用启动时调用,传入退出登录的函数
|
||||
*/
|
||||
export function setUnauthorizedHandler(handler: UnauthorizedHandler) {
|
||||
unauthorizedHandler = handler;
|
||||
console.log('[API] 401处理器已注册');
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一处理401未授权错误
|
||||
* 包含防抖机制,避免多次提示
|
||||
*/
|
||||
async function handle401Unauthorized() {
|
||||
const now = Date.now();
|
||||
|
||||
// 防抖:如果正在处理或者距离上次处理不到3秒,直接返回
|
||||
if (is401Handling || (now - last401Time < DEBOUNCE_INTERVAL)) {
|
||||
console.log('[API] 401处理防抖,跳过重复处理');
|
||||
return;
|
||||
}
|
||||
|
||||
is401Handling = true;
|
||||
last401Time = now;
|
||||
|
||||
try {
|
||||
console.log('[API] 检测到401未授权,开始处理登录过期');
|
||||
|
||||
// 清除本地token
|
||||
await AsyncStorage.removeItem(STORAGE_KEYS.authToken);
|
||||
await setAuthToken(null);
|
||||
|
||||
// 提示用户
|
||||
Alert.alert(
|
||||
'登录已过期',
|
||||
'您的登录状态已过期,请重新登录',
|
||||
[
|
||||
{
|
||||
text: '确定',
|
||||
onPress: () => {
|
||||
console.log('[API] 用户确认登录过期提示');
|
||||
// 调用注册的处理器(如果存在)
|
||||
if (unauthorizedHandler) {
|
||||
try {
|
||||
const result = unauthorizedHandler();
|
||||
if (result instanceof Promise) {
|
||||
result.catch(err => {
|
||||
console.error('[API] 401处理器执行失败:', err);
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[API] 401处理器执行失败:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
{ cancelable: false }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('[API] 处理401错误时发生异常:', error);
|
||||
} finally {
|
||||
// 延迟重置处理标志,确保不会立即再次触发
|
||||
setTimeout(() => {
|
||||
is401Handling = false;
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
let inMemoryToken: string | null = null;
|
||||
|
||||
export async function setAuthToken(token: string | null): Promise<void> {
|
||||
@@ -52,6 +131,12 @@ async function doFetch<T>(path: string, options: ApiRequestOptions = {}): Promis
|
||||
console.log('json', json);
|
||||
|
||||
if (!response.ok) {
|
||||
// 检查是否为401未授权
|
||||
if (response.status === 401) {
|
||||
console.log('[API] 检测到401状态码,触发登录过期处理');
|
||||
await handle401Unauthorized();
|
||||
}
|
||||
|
||||
const errorMessage = (json && (json.message || json.error)) || `HTTP ${response.status}`;
|
||||
const error = new Error(errorMessage);
|
||||
// @ts-expect-error augment
|
||||
@@ -181,7 +266,15 @@ export async function postTextStream(path: string, body: any, callbacks: TextStr
|
||||
try { console.log('[AI_CHAT][stream] done', { status: xhr.status }); } catch { }
|
||||
if (!resolved) {
|
||||
cleanup();
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
if (xhr.status === 401) {
|
||||
// 处理401未授权
|
||||
console.log('[AI_CHAT][stream] 检测到401状态码,触发登录过期处理');
|
||||
handle401Unauthorized().catch(err => {
|
||||
console.error('[AI_CHAT][stream] 处理401错误时发生异常:', err);
|
||||
});
|
||||
const error = new Error('登录已过期');
|
||||
try { callbacks.onError?.(error); } catch { }
|
||||
} else if (xhr.status >= 200 && xhr.status < 300) {
|
||||
try { callbacks.onEnd?.(conversationIdFromHeader); } catch { }
|
||||
} else {
|
||||
const error = new Error(`HTTP ${xhr.status}`);
|
||||
|
||||
Reference in New Issue
Block a user