enhance: 优化

This commit is contained in:
Junhui Chen 2025-08-05 23:12:42 +08:00
parent 3bc8dda46f
commit ec83f9ce34
5 changed files with 93 additions and 33 deletions

View File

@ -10,7 +10,7 @@ import { fetchApi } from '@/lib/server-api-util';
import { webSocketManager, WebSocketStatus } from '@/lib/websocket-util'; import { webSocketManager, WebSocketStatus } from '@/lib/websocket-util';
import { TransitionPresets } from '@react-navigation/bottom-tabs'; import { TransitionPresets } from '@react-navigation/bottom-tabs';
import * as Notifications from 'expo-notifications'; import * as Notifications from 'expo-notifications';
import { Tabs } from 'expo-router'; import { Tabs, useRouter } from 'expo-router';
import * as SecureStore from 'expo-secure-store'; import * as SecureStore from 'expo-secure-store';
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -32,12 +32,16 @@ export default function TabLayout() {
const isMounted = useRef(true); const isMounted = useRef(true);
const [token, setToken] = useState(''); const [token, setToken] = useState('');
const [wsStatus, setWsStatus] = useState<WebSocketStatus>('disconnected'); const [wsStatus, setWsStatus] = useState<WebSocketStatus>('disconnected');
const [notificationPermissionRequested, setNotificationPermissionRequested] = useState(false);
const sendNotification = async (item: PollingData) => { const sendNotification = async (item: PollingData) => {
// 请求通知权限 // 只在需要发送通知时才请求权限
const granted = await requestNotificationPermission(); if (!notificationPermissionRequested) {
if (!granted) { const granted = await requestNotificationPermission();
console.log('用户拒绝了通知权限'); setNotificationPermissionRequested(granted);
return; if (!granted) {
console.log('用户拒绝了通知权限');
return;
}
} }
// 调度本地通知 // 调度本地通知
@ -60,7 +64,18 @@ export default function TabLayout() {
const notificationListener = Notifications.addNotificationResponseReceivedListener(response => { const notificationListener = Notifications.addNotificationResponseReceivedListener(response => {
const data = response.notification.request.content.data; const data = response.notification.request.content.data;
console.log('通知被点击,数据:', data); console.log('通知被点击,数据:', data);
pollingData?.filter((item) => item.id !== data.id); setPollingData(prev => prev.filter((item) => item.id !== data.id));
// 根据通知数据导航到指定页面
if (data.screen === 'ask') {
router.push({
pathname: '/ask',
params: {
sessionId: data.id,
extra: data.extra
}
});
}
}); });
// 清理监听器 // 清理监听器
@ -93,6 +108,7 @@ export default function TabLayout() {
return () => { return () => {
if (pollingInterval.current) { if (pollingInterval.current) {
clearInterval(pollingInterval.current); clearInterval(pollingInterval.current);
pollingInterval.current = null;
} }
}; };
}, []); }, []);
@ -103,7 +119,9 @@ export default function TabLayout() {
const response = await fetchApi<PollingData[]>("/notice/push/message", { const response = await fetchApi<PollingData[]>("/notice/push/message", {
method: "POST" method: "POST"
}); });
setPollingData((prev) => ([...prev, ...response])); if (response && Array.isArray(response)) {
setPollingData((prev) => ([...prev, ...response]));
}
} catch (error) { } catch (error) {
console.error('获取轮询数据时出错:', error); console.error('获取轮询数据时出错:', error);
} }
@ -117,35 +135,50 @@ export default function TabLayout() {
} else { } else {
tokenValue = (await SecureStore.getItemAsync('token')) || ''; tokenValue = (await SecureStore.getItemAsync('token')) || '';
} }
setToken(tokenValue); // 只在获取到新token时更新状态 // 只在获取到新token时更新状态
if (tokenValue !== token) {
setToken(tokenValue);
}
return tokenValue; return tokenValue;
}; };
// 初始化时获取一次token
useEffect(() => {
getAuthToken();
}, []);
useEffect(() => { useEffect(() => {
const checkAuthStatus = async () => { const checkAuthStatus = async () => {
try { try {
if (token) { if (token) {
// 启动轮询 // 启动轮询
startPolling(5000); const cleanup = startPolling(5000);
// 将清理函数保存到ref中以便在组件卸载时调用
return cleanup;
} }
} catch (error) { } catch (error) {
console.error('获取推送消息出错:', error); console.error('获取推送消息出错:', error);
} }
}; };
checkAuthStatus();
const cleanupPolling = checkAuthStatus();
return () => { return () => {
// 清理函数 // 清理函数
if (pollingInterval.current) { if (pollingInterval.current) {
clearInterval(pollingInterval.current); clearInterval(pollingInterval.current);
pollingInterval.current = null;
}
if (typeof cleanupPolling === 'function') {
cleanupPolling();
} }
isMounted.current = false; isMounted.current = false;
}; };
}, [token]); }, [token, startPolling]);
// 本地推送 // 本地推送
useEffect(() => { useEffect(() => {
pollingData?.map((item) => { pollingData?.forEach((item) => {
sendNotification(item) sendNotification(item)
}) })
}, [pollingData]) }, [pollingData])
@ -156,10 +189,11 @@ export default function TabLayout() {
if (token) { if (token) {
if (tokenInterval.current) { if (tokenInterval.current) {
clearInterval(tokenInterval.current); clearInterval(tokenInterval.current);
tokenInterval.current = null;
} }
return; return;
} }
if (!tokenInterval.current) return;
// 设置轮询 // 设置轮询
tokenInterval.current = setInterval(async () => { tokenInterval.current = setInterval(async () => {
if (isMounted.current) { if (isMounted.current) {
@ -167,6 +201,7 @@ export default function TabLayout() {
// 如果获取到token清除定时器 // 如果获取到token清除定时器
if (currentToken && tokenInterval.current) { if (currentToken && tokenInterval.current) {
clearInterval(tokenInterval.current); clearInterval(tokenInterval.current);
tokenInterval.current = null;
} }
} }
}, 5000); }, 5000);
@ -175,6 +210,7 @@ export default function TabLayout() {
return () => { return () => {
if (tokenInterval.current) { if (tokenInterval.current) {
clearInterval(tokenInterval.current); clearInterval(tokenInterval.current);
tokenInterval.current = null;
} }
}; };
}, [token]); // 添加token作为依赖 }, [token]); // 添加token作为依赖

View File

@ -37,9 +37,10 @@ export default function AskScreen() {
const fadeAnimChat = useRef(new Animated.Value(0)).current; const fadeAnimChat = useRef(new Animated.Value(0)).current;
const { t } = useTranslation(); const { t } = useTranslation();
const { sessionId, newSession } = useLocalSearchParams<{ const { sessionId, newSession, extra } = useLocalSearchParams<{
sessionId: string; sessionId: string;
newSession: string; newSession: string;
extra: string;
}>(); }>();
// 创建一个可复用的滚动函数 // 创建一个可复用的滚动函数

View File

@ -22,7 +22,6 @@ const Login = ({ updateUrlParam, setError, setShowPassword, showPassword }: Logi
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [rememberMe, setRememberMe] = useState(false);
const handleLogin = async () => { const handleLogin = async () => {
if (!email) { if (!email) {
@ -44,12 +43,18 @@ const Login = ({ updateUrlParam, setError, setShowPassword, showPassword }: Logi
method: 'POST', method: 'POST',
body: JSON.stringify(body), body: JSON.stringify(body),
}, true, false); }, true, false);
login({ ...res, email: res?.account }, res.access_token || '');
const userInfo = await fetchApi<User>("/iam/user-info"); // 确保用户数据和令牌存在
if (userInfo?.nickname) { if (res && res.access_token) {
router.replace('/ask'); login({ ...res, email: res?.account || email }, res.access_token);
const userInfo = await fetchApi<User>("/iam/user-info");
if (userInfo?.nickname) {
router.replace('/ask');
} else {
router.replace('/user-message');
}
} else { } else {
router.replace('/user-message'); throw new Error(t('auth.login.loginError', { ns: 'login' }));
} }
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : t('auth.login.loginError', { ns: 'login' }); const errorMessage = error instanceof Error ? error.message : t('auth.login.loginError', { ns: 'login' });

View File

@ -31,9 +31,8 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
if (Platform.OS === 'web') { if (Platform.OS === 'web') {
token = localStorage.getItem('token') || ""; token = localStorage.getItem('token') || "";
} else { } else {
await SecureStore.getItemAsync('token').then((token) => { const storedToken = await SecureStore.getItemAsync('token');
token = token || ""; token = storedToken || "";
})
} }
if (token) { if (token) {
// 验证当前 token 是否有效 // 验证当前 token 是否有效
@ -61,7 +60,8 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
} catch (refreshError) { } catch (refreshError) {
// 刷新 token 失败,才进行登出操作 // 刷新 token 失败,才进行登出操作
// console.error("Token refresh failed, logging out", refreshError); // console.error("Token refresh failed, logging out", refreshError);
logout(); // 只有在确实需要登出时才调用logout()
// 如果是首次启动应用没有token是正常的不需要登出
} }
} }
} }

View File

@ -29,9 +29,8 @@ export const API_ENDPOINT = Constants.expoConfig?.extra?.API_ENDPOINT || "http:/
// 更新 access_token 的逻辑 - 用于React组件中 // 更新 access_token 的逻辑 - 用于React组件中
export const useAuthToken = async<T>(message: string | null) => { export const useAuthToken = async<T>(message: string | null, login: (user: User, jwt: string) => void) => {
try { try {
const { login } = useAuth();
const response = await fetch(`${API_ENDPOINT}/v1/iam/access-token-refresh`); const response = await fetch(`${API_ENDPOINT}/v1/iam/access-token-refresh`);
const apiResponse: ApiResponse<T> = await response.json(); const apiResponse: ApiResponse<T> = await response.json();
@ -62,13 +61,32 @@ export const refreshAuthToken = async<T>(message: string | null): Promise<User>
let cookie = ""; let cookie = "";
let userId = ""; let userId = "";
if (Platform.OS === 'web') { if (Platform.OS === 'web') {
cookie = localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user') || "")?.refresh_token || "" : ""; const userStr = localStorage.getItem('user');
userId = localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user') || "")?.user_id || "" : ""; if (userStr) {
try {
const userObj = JSON.parse(userStr);
cookie = userObj?.refresh_token || "";
userId = userObj?.user_id || "";
} catch (e) {
console.error("Failed to parse user data from localStorage", e);
}
}
} else { } else {
await SecureStore.getItemAsync('user').then((user: string | null) => { const userStr = await SecureStore.getItemAsync('user');
cookie = JSON.parse(user || "")?.refresh_token || ""; if (userStr) {
userId = JSON.parse(user || "")?.user_id || ""; try {
}) const userObj = JSON.parse(userStr);
cookie = userObj?.refresh_token || "";
userId = userObj?.user_id || "";
} catch (e) {
console.error("Failed to parse user data from SecureStore", e);
}
}
}
// 如果没有必要的数据,抛出错误
if (!cookie || !userId) {
throw new Error("Missing refresh token or user ID");
} }
// 退出刷新会重新填充数据 // 退出刷新会重新填充数据