providers.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. 'use client';
  2. import { SessionProvider } from 'next-auth/react';
  3. import { ReactNode, useEffect, useState, createContext, useContext } from 'react';
  4. import { useSession } from 'next-auth/react';
  5. interface ProvidersProps {
  6. children: ReactNode;
  7. }
  8. interface User {
  9. id: string;
  10. email: string;
  11. username: string | null;
  12. isEmailVerified: boolean;
  13. credits: number;
  14. subscriptionCredits?: number;
  15. subscriptionStatus?: string | null;
  16. subscriptionPlan?: string | null;
  17. subscriptionStartDate?: string | null;
  18. subscriptionEndDate?: string | null;
  19. }
  20. interface AuthContextType {
  21. user: User | null;
  22. isLoading: boolean;
  23. refreshUser: () => Promise<void>;
  24. }
  25. const AuthContext = createContext<AuthContextType>({
  26. user: null,
  27. isLoading: true,
  28. refreshUser: async () => {},
  29. });
  30. export const useAuth = () => useContext(AuthContext);
  31. // 全局认证状态管理器
  32. function AuthStateManager({ children }: { children: ReactNode }) {
  33. const { data: session, status } = useSession();
  34. const [user, setUser] = useState<User | null>(null);
  35. const [isLoading, setIsLoading] = useState(true);
  36. const [lastFetch, setLastFetch] = useState<number>(0);
  37. // 缓存时间:5分钟
  38. const CACHE_DURATION = 5 * 60 * 1000;
  39. const fetchUserData = async (force = false) => {
  40. // 如果没有session,直接返回
  41. if (!session?.user) {
  42. setUser(null);
  43. setIsLoading(false);
  44. return;
  45. }
  46. // 检查缓存是否有效(除非强制刷新)
  47. const now = Date.now();
  48. if (!force && user && (now - lastFetch) < CACHE_DURATION) {
  49. setIsLoading(false);
  50. return;
  51. }
  52. try {
  53. setIsLoading(true);
  54. const response = await fetch('/api/auth/me', {
  55. method: 'GET',
  56. credentials: 'include',
  57. });
  58. if (response.ok) {
  59. const userData = await response.json();
  60. setUser(userData.user);
  61. setLastFetch(now);
  62. } else {
  63. setUser(null);
  64. }
  65. } catch (error) {
  66. console.error('获取用户信息失败:', error);
  67. setUser(null);
  68. } finally {
  69. setIsLoading(false);
  70. }
  71. };
  72. const refreshUser = async () => {
  73. await fetchUserData(true);
  74. };
  75. // 当session状态变化时获取用户数据
  76. useEffect(() => {
  77. if (status !== 'loading') {
  78. fetchUserData();
  79. }
  80. }, [session, status]);
  81. return (
  82. <AuthContext.Provider value={{ user, isLoading, refreshUser }}>
  83. {children}
  84. </AuthContext.Provider>
  85. );
  86. }
  87. // 认证状态同步组件
  88. function AuthSyncProvider({ children }: { children: ReactNode }) {
  89. const { data: session, status } = useSession();
  90. const [lastSessionUpdate, setLastSessionUpdate] = useState<string>('');
  91. // 当认证状态变化时触发全局事件
  92. useEffect(() => {
  93. if (status !== 'loading') {
  94. // 创建会话标识符来防止重复触发
  95. const sessionId = session?.user?.email ? `${session.user.email}-${status}` : `none-${status}`;
  96. // 只有当会话状态真正改变时才触发事件
  97. if (sessionId !== lastSessionUpdate) {
  98. console.log('AuthSyncProvider: 认证状态变化', { status, session: session?.user?.email });
  99. // 触发自定义事件通知其他组件认证状态已更新
  100. window.dispatchEvent(new CustomEvent('authStatusChanged', {
  101. detail: { session, status }
  102. }));
  103. setLastSessionUpdate(sessionId);
  104. }
  105. }
  106. }, [session, status, lastSessionUpdate]);
  107. return <>{children}</>;
  108. }
  109. export default function Providers({ children }: ProvidersProps) {
  110. return (
  111. <SessionProvider
  112. refetchInterval={10 * 60} // 10分钟刷新一次
  113. refetchOnWindowFocus={false} // 禁用窗口焦点刷新
  114. >
  115. <AuthSyncProvider>
  116. <AuthStateManager>
  117. {children}
  118. </AuthStateManager>
  119. </AuthSyncProvider>
  120. </SessionProvider>
  121. );
  122. }