feat: 删除废弃文件
This commit is contained in:
@@ -1,568 +0,0 @@
|
|||||||
// src/contexts/AuthContext.js - Session版本
|
|
||||||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { useToast } from '@chakra-ui/react';
|
|
||||||
|
|
||||||
// 创建认证上下文
|
|
||||||
const AuthContext = createContext();
|
|
||||||
|
|
||||||
// 自定义Hook
|
|
||||||
export const useAuth = () => {
|
|
||||||
const context = useContext(AuthContext);
|
|
||||||
if (!context) {
|
|
||||||
throw new Error('useAuth must be used within an AuthProvider');
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 认证提供者组件
|
|
||||||
export const AuthProvider = ({ children }) => {
|
|
||||||
const [user, setUser] = useState(null);
|
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const toast = useToast();
|
|
||||||
|
|
||||||
// 检查Session状态
|
|
||||||
const checkSession = async () => {
|
|
||||||
try {
|
|
||||||
console.log('🔍 检查Session状态...');
|
|
||||||
|
|
||||||
const response = await fetch(`/api/auth/session`, {
|
|
||||||
method: 'GET',
|
|
||||||
credentials: 'include', // 重要:包含cookie
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Session检查失败');
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
console.log('📦 Session数据:', data);
|
|
||||||
|
|
||||||
if (data.isAuthenticated && data.user) {
|
|
||||||
setUser(data.user);
|
|
||||||
setIsAuthenticated(true);
|
|
||||||
} else {
|
|
||||||
setUser(null);
|
|
||||||
setIsAuthenticated(false);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Session检查错误:', error);
|
|
||||||
setUser(null);
|
|
||||||
setIsAuthenticated(false);
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 初始化时检查Session
|
|
||||||
useEffect(() => {
|
|
||||||
checkSession();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 监听路由变化,检查session(处理微信登录回调)
|
|
||||||
useEffect(() => {
|
|
||||||
const handleRouteChange = () => {
|
|
||||||
// 如果是从微信回调返回的,重新检查session
|
|
||||||
if (window.location.pathname === '/home' && !isAuthenticated) {
|
|
||||||
checkSession();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('popstate', handleRouteChange);
|
|
||||||
return () => window.removeEventListener('popstate', handleRouteChange);
|
|
||||||
}, [isAuthenticated]);
|
|
||||||
|
|
||||||
// 更新本地用户的便捷方法
|
|
||||||
const updateUser = (partial) => {
|
|
||||||
setUser((prev) => ({ ...(prev || {}), ...partial }));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 传统登录方法
|
|
||||||
const login = async (credential, password, loginType = 'email') => {
|
|
||||||
try {
|
|
||||||
setIsLoading(true);
|
|
||||||
console.log('🔐 开始登录流程:', { credential, loginType });
|
|
||||||
|
|
||||||
const formData = new URLSearchParams();
|
|
||||||
formData.append('password', password);
|
|
||||||
|
|
||||||
if (loginType === 'username') {
|
|
||||||
formData.append('username', credential);
|
|
||||||
} else if (loginType === 'email') {
|
|
||||||
formData.append('email', credential);
|
|
||||||
} else if (loginType === 'phone') {
|
|
||||||
formData.append('username', credential);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('📤 发送登录请求到:', `/api/auth/login`);
|
|
||||||
console.log('📝 请求数据:', {
|
|
||||||
credential,
|
|
||||||
loginType,
|
|
||||||
formData: formData.toString()
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await fetch(`/api/auth/login`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
credentials: 'include', // 包含cookie
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('📨 响应状态:', response.status, response.statusText);
|
|
||||||
console.log('📨 响应头:', Object.fromEntries(response.headers.entries()));
|
|
||||||
|
|
||||||
// 获取响应文本,然后尝试解析JSON
|
|
||||||
const responseText = await response.text();
|
|
||||||
console.log('📨 响应原始内容:', responseText);
|
|
||||||
|
|
||||||
let data;
|
|
||||||
try {
|
|
||||||
data = JSON.parse(responseText);
|
|
||||||
console.log('✅ JSON解析成功:', data);
|
|
||||||
} catch (parseError) {
|
|
||||||
console.error('❌ JSON解析失败:', parseError);
|
|
||||||
console.error('📄 响应内容:', responseText);
|
|
||||||
throw new Error(`服务器响应格式错误: ${responseText.substring(0, 100)}...`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok || !data.success) {
|
|
||||||
throw new Error(data.error || '登录失败');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新状态
|
|
||||||
setUser(data.user);
|
|
||||||
setIsAuthenticated(true);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "登录成功",
|
|
||||||
description: "欢迎回来!",
|
|
||||||
status: "success",
|
|
||||||
duration: 3000,
|
|
||||||
isClosable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: true };
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ 登录错误:', error);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "登录失败",
|
|
||||||
description: error.message || "请检查您的登录信息",
|
|
||||||
status: "error",
|
|
||||||
duration: 3000,
|
|
||||||
isClosable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 注册方法
|
|
||||||
const register = async (username, email, password) => {
|
|
||||||
try {
|
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
const formData = new URLSearchParams();
|
|
||||||
formData.append('username', username);
|
|
||||||
formData.append('email', email);
|
|
||||||
formData.append('password', password);
|
|
||||||
|
|
||||||
const response = await fetch(`/api/auth/register`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
credentials: 'include',
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (!response.ok || !data.success) {
|
|
||||||
throw new Error(data.error || '注册失败');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册成功后自动登录
|
|
||||||
setUser(data.user);
|
|
||||||
setIsAuthenticated(true);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "注册成功",
|
|
||||||
description: "欢迎加入价值前沿!",
|
|
||||||
status: "success",
|
|
||||||
duration: 3000,
|
|
||||||
isClosable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: true };
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('注册错误:', error);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "注册失败",
|
|
||||||
description: error.message || "注册失败,请稍后重试",
|
|
||||||
status: "error",
|
|
||||||
duration: 3000,
|
|
||||||
isClosable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 手机号注册
|
|
||||||
const registerWithPhone = async (phone, code, username, password) => {
|
|
||||||
try {
|
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
const response = await fetch(`/api/auth/register/phone`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
credentials: 'include',
|
|
||||||
body: JSON.stringify({
|
|
||||||
phone,
|
|
||||||
code,
|
|
||||||
username,
|
|
||||||
password
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (!response.ok || !data.success) {
|
|
||||||
throw new Error(data.error || '注册失败');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册成功后自动登录
|
|
||||||
setUser(data.user);
|
|
||||||
setIsAuthenticated(true);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "注册成功",
|
|
||||||
description: "欢迎加入价值前沿!",
|
|
||||||
status: "success",
|
|
||||||
duration: 3000,
|
|
||||||
isClosable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: true };
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('手机注册错误:', error);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "注册失败",
|
|
||||||
description: error.message || "注册失败,请稍后重试",
|
|
||||||
status: "error",
|
|
||||||
duration: 3000,
|
|
||||||
isClosable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 邮箱注册
|
|
||||||
const registerWithEmail = async (email, code, username, password) => {
|
|
||||||
try {
|
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
const response = await fetch(`/api/auth/register/email`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
credentials: 'include',
|
|
||||||
body: JSON.stringify({
|
|
||||||
email,
|
|
||||||
code,
|
|
||||||
username,
|
|
||||||
password
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (!response.ok || !data.success) {
|
|
||||||
throw new Error(data.error || '注册失败');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册成功后自动登录
|
|
||||||
setUser(data.user);
|
|
||||||
setIsAuthenticated(true);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "注册成功",
|
|
||||||
description: "欢迎加入价值前沿!",
|
|
||||||
status: "success",
|
|
||||||
duration: 3000,
|
|
||||||
isClosable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: true };
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('邮箱注册错误:', error);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "注册失败",
|
|
||||||
description: error.message || "注册失败,请稍后重试",
|
|
||||||
status: "error",
|
|
||||||
duration: 3000,
|
|
||||||
isClosable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 发送手机验证码
|
|
||||||
const sendSmsCode = async (phone) => {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/auth/send-sms-code`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
credentials: 'include', // 必须包含以支持跨域 session cookie
|
|
||||||
body: JSON.stringify({ phone })
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(data.error || '发送失败');
|
|
||||||
}
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "验证码已发送",
|
|
||||||
description: "请查收短信",
|
|
||||||
status: "success",
|
|
||||||
duration: 3000,
|
|
||||||
isClosable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: true };
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('SMS code error:', error);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "发送失败",
|
|
||||||
description: error.message || "请稍后重试",
|
|
||||||
status: "error",
|
|
||||||
duration: 3000,
|
|
||||||
isClosable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 发送邮箱验证码
|
|
||||||
const sendEmailCode = async (email) => {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/auth/send-email-code`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
credentials: 'include', // 必须包含以支持跨域 session cookie
|
|
||||||
body: JSON.stringify({ email })
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(data.error || '发送失败');
|
|
||||||
}
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "验证码已发送",
|
|
||||||
description: "请查收邮件",
|
|
||||||
status: "success",
|
|
||||||
duration: 3000,
|
|
||||||
isClosable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: true };
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Email code error:', error);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "发送失败",
|
|
||||||
description: error.message || "请稍后重试",
|
|
||||||
status: "error",
|
|
||||||
duration: 3000,
|
|
||||||
isClosable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 登出方法
|
|
||||||
const logout = async () => {
|
|
||||||
try {
|
|
||||||
// 调用后端登出API
|
|
||||||
await fetch(`/api/auth/logout`, {
|
|
||||||
method: 'POST',
|
|
||||||
credentials: 'include'
|
|
||||||
});
|
|
||||||
|
|
||||||
// 清除本地状态
|
|
||||||
setUser(null);
|
|
||||||
setIsAuthenticated(false);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "已登出",
|
|
||||||
description: "您已成功退出登录",
|
|
||||||
status: "info",
|
|
||||||
duration: 2000,
|
|
||||||
isClosable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 跳转到登录页面
|
|
||||||
navigate('/auth/signin');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Logout error:', error);
|
|
||||||
// 即使API调用失败也清除本地状态
|
|
||||||
setUser(null);
|
|
||||||
setIsAuthenticated(false);
|
|
||||||
navigate('/auth/signin');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 检查用户是否有特定权限
|
|
||||||
const hasRole = (role) => {
|
|
||||||
return user && user.role === role;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 检查用户订阅权限
|
|
||||||
const hasSubscriptionPermission = async (featureName) => {
|
|
||||||
try {
|
|
||||||
// 如果用户未登录,返回免费权限
|
|
||||||
if (!isAuthenticated) {
|
|
||||||
const freePermissions = {
|
|
||||||
'related_stocks': false,
|
|
||||||
'related_concepts': false,
|
|
||||||
'transmission_chain': false,
|
|
||||||
'historical_events': 'limited'
|
|
||||||
};
|
|
||||||
return freePermissions[featureName] || false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取用户权限信息
|
|
||||||
const response = await fetch(`/api/subscription/permissions`, {
|
|
||||||
method: 'GET',
|
|
||||||
credentials: 'include',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('获取权限信息失败');
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.success && data.data) {
|
|
||||||
return data.data.permissions[featureName] || false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果API调用失败,返回默认权限
|
|
||||||
const defaultPermissions = {
|
|
||||||
'related_stocks': user?.subscription_type === 'pro' || user?.subscription_type === 'max',
|
|
||||||
'related_concepts': user?.subscription_type === 'pro' || user?.subscription_type === 'max',
|
|
||||||
'transmission_chain': user?.subscription_type === 'max',
|
|
||||||
'historical_events': user?.subscription_type === 'pro' || user?.subscription_type === 'max' ? 'full' : 'limited'
|
|
||||||
};
|
|
||||||
|
|
||||||
return defaultPermissions[featureName] || false;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('检查订阅权限失败:', error);
|
|
||||||
|
|
||||||
// 降级处理:基于用户现有的订阅信息
|
|
||||||
const fallbackPermissions = {
|
|
||||||
'related_stocks': user?.subscription_type === 'pro' || user?.subscription_type === 'max',
|
|
||||||
'related_concepts': user?.subscription_type === 'pro' || user?.subscription_type === 'max',
|
|
||||||
'transmission_chain': user?.subscription_type === 'max',
|
|
||||||
'historical_events': user?.subscription_type === 'pro' || user?.subscription_type === 'max' ? 'full' : 'limited'
|
|
||||||
};
|
|
||||||
|
|
||||||
return fallbackPermissions[featureName] || false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取用户订阅级别
|
|
||||||
const getSubscriptionLevel = () => {
|
|
||||||
if (!isAuthenticated) return 'free';
|
|
||||||
return user?.subscription_type || 'free';
|
|
||||||
};
|
|
||||||
|
|
||||||
// 检查是否需要升级订阅
|
|
||||||
const requiresUpgrade = (requiredLevel) => {
|
|
||||||
const currentLevel = getSubscriptionLevel();
|
|
||||||
const levels = { 'free': 0, 'pro': 1, 'max': 2 };
|
|
||||||
|
|
||||||
const currentLevelValue = levels[currentLevel] || 0;
|
|
||||||
const requiredLevelValue = levels[requiredLevel] || 0;
|
|
||||||
|
|
||||||
return currentLevelValue < requiredLevelValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 刷新session(可选)
|
|
||||||
const refreshSession = async () => {
|
|
||||||
await checkSession();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 提供给子组件的值
|
|
||||||
const value = {
|
|
||||||
user,
|
|
||||||
isAuthenticated,
|
|
||||||
isLoading,
|
|
||||||
updateUser,
|
|
||||||
login,
|
|
||||||
register,
|
|
||||||
registerWithPhone,
|
|
||||||
registerWithEmail,
|
|
||||||
sendSmsCode,
|
|
||||||
sendEmailCode,
|
|
||||||
logout,
|
|
||||||
hasRole,
|
|
||||||
hasSubscriptionPermission,
|
|
||||||
getSubscriptionLevel,
|
|
||||||
requiresUpgrade,
|
|
||||||
refreshSession,
|
|
||||||
checkSession
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AuthContext.Provider value={value}>
|
|
||||||
{children}
|
|
||||||
</AuthContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user