From e05ea154a24b503718f9ba779f4e9cd43bf36d3c Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Tue, 28 Oct 2025 14:16:30 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=E6=96=87=E6=A1=88=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/services/authService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/authService.js b/src/services/authService.js index 53c6ad46..e3a3945e 100644 --- a/src/services/authService.js +++ b/src/services/authService.js @@ -153,7 +153,7 @@ export const WECHAT_STATUS = { * 状态提示信息映射 */ export const STATUS_MESSAGES = { - [WECHAT_STATUS.WAITING]: '请使用微信扫码', + [WECHAT_STATUS.WAITING]: '使用微信扫一扫登陆', [WECHAT_STATUS.SCANNED]: '扫码成功,请在手机上确认', [WECHAT_STATUS.AUTHORIZED]: '授权成功,正在登录...', [WECHAT_STATUS.EXPIRED]: '二维码已过期', From 356f865f09154cd80b087eb16c95dbfab90b0bf8 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Tue, 28 Oct 2025 18:47:39 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=E5=BE=AE=E4=BF=A1mock=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mocks/handlers/auth.js | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/mocks/handlers/auth.js b/src/mocks/handlers/auth.js index 31f37832..b48a27db 100644 --- a/src/mocks/handlers/auth.js +++ b/src/mocks/handlers/auth.js @@ -136,7 +136,9 @@ export const authHandlers = [ }); // 模拟微信授权 URL(实际是微信的 URL) - const authUrl = `https://open.weixin.qq.com/connect/qrconnect?appid=mock&redirect_uri=&response_type=code&scope=snsapi_login&state=${sessionId}#wechat_redirect`; + // 使用真实的微信 AppID 和真实的授权回调地址(必须与微信开放平台配置的域名一致) + const mockRedirectUri = encodeURIComponent('http://valuefrontier.cn/api/auth/wechat/callback'); + const authUrl = `https://open.weixin.qq.com/connect/qrconnect?appid=wxa8d74c47041b5f87&redirect_uri=${mockRedirectUri}&response_type=code&scope=snsapi_login&state=${sessionId}#wechat_redirect`; console.log('[Mock] 生成微信二维码:', { sessionId, authUrl }); @@ -147,16 +149,16 @@ export const authHandlers = [ session.status = 'scanned'; console.log(`[Mock] 模拟用户扫码: ${sessionId}`); - // 再过2秒自动确认登录 + // 再过5秒自动确认登录(延长时间让用户看到 scanned 状态) setTimeout(() => { const session2 = mockWechatSessions.get(sessionId); if (session2 && session2.status === 'scanned') { - session2.status = 'confirmed'; + session2.status = 'authorized'; // ✅ 使用 'authorized' 状态,与后端保持一致 session2.user = { id: 999, nickname: '微信用户', wechat_openid: 'mock_openid_' + sessionId, - avatar_url: 'https://i.pravatar.cc/150?img=99', + avatar_url: 'https://ui-avatars.com/api/?name=微信用户&size=150&background=4299e1&color=fff', phone: null, email: null, has_wechat: true, @@ -168,6 +170,7 @@ export const authHandlers = [ is_subscription_active: true, subscription_days_left: 0 }; + session2.user_info = { user_id: session2.user.id }; // ✅ 添加 user_info 字段 console.log(`[Mock] 模拟用户确认登录: ${sessionId}`, session2.user); } }, 2000); @@ -185,7 +188,7 @@ export const authHandlers = [ }), // 4. 检查微信扫码状态 - http.post('/api/auth/wechat/check-status', async ({ request }) => { + http.post('/api/auth/wechat/check', async ({ request }) => { await delay(200); // 轮询请求,延迟短一些 const body = await request.json(); @@ -209,18 +212,16 @@ export const authHandlers = [ console.log('[Mock] 检查微信状态:', { session_id, status: session.status }); + // ✅ 返回与后端真实 API 一致的扁平化数据结构 return HttpResponse.json({ - code: 0, - message: '成功', - data: { - status: session.status, - user: session.user - } + status: session.status, + user_info: session.user_info, + expires_in: Math.floor((session.createdAt + 5 * 60 * 1000 - Date.now()) / 1000) }); }), // 5. 微信登录确认 - http.post('/api/auth/wechat/login', async ({ request }) => { + http.post('/api/auth/login/wechat', async ({ request }) => { await delay(NETWORK_DELAY); const body = await request.json(); @@ -228,7 +229,7 @@ export const authHandlers = [ const session = mockWechatSessions.get(session_id); - if (!session || session.status !== 'confirmed') { + if (!session || session.status !== 'authorized') { // ✅ 使用 'authorized' 状态,与前端保持一致 return HttpResponse.json({ success: false, error: '微信登录未确认或已过期' @@ -386,12 +387,12 @@ if (process.env.NODE_ENV === 'development' || process.env.REACT_APP_ENABLE_MOCK setTimeout(() => { const session2 = mockWechatSessions.get(targetSessionId); if (session2 && session2.status === 'scanned') { - session2.status = 'confirmed'; + session2.status = 'authorized'; // ✅ 使用 'authorized' 状态,与自动扫码流程保持一致 session2.user = { id: 999, nickname: '微信测试用户', wechat_openid: 'mock_openid_' + targetSessionId, - avatar_url: 'https://i.pravatar.cc/150?img=99', + avatar_url: 'https://ui-avatars.com/api/?name=微信测试用户&size=150&background=4299e1&color=fff', phone: null, email: null, has_wechat: true, @@ -402,6 +403,7 @@ if (process.env.NODE_ENV === 'development' || process.env.REACT_APP_ENABLE_MOCK is_subscription_active: true, subscription_days_left: 0 }; + session2.user_info = { user_id: session2.user.id }; // ✅ 添加 user_info 字段 console.log(`[Mock API] ✅ 模拟确认登录: ${targetSessionId}`, session2.user); } }, 1000); From b221c2669c47be2b77f65a1b1b33f9a6a34f9c05 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Tue, 28 Oct 2025 19:04:58 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=E5=BE=AE=E4=BF=A1=E7=99=BB?= =?UTF-8?q?=E9=99=86=E9=80=BB=E8=BE=91=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Auth/WechatRegister.js | 105 +++++++++++++++++--------- src/services/authService.js | 4 +- 2 files changed, 73 insertions(+), 36 deletions(-) diff --git a/src/components/Auth/WechatRegister.js b/src/components/Auth/WechatRegister.js index 67f81447..d0049911 100644 --- a/src/components/Auth/WechatRegister.js +++ b/src/components/Auth/WechatRegister.js @@ -15,6 +15,8 @@ import { FaQrcode } from "react-icons/fa"; import { FiAlertCircle } from "react-icons/fi"; import { useNavigate } from "react-router-dom"; import { authService, WECHAT_STATUS, STATUS_MESSAGES } from "../../services/authService"; +import { useAuthModal } from "../../contexts/AuthModalContext"; +import { useAuth } from "../../contexts/AuthContext"; import { logger } from "../../utils/logger"; // 配置常量 @@ -45,6 +47,10 @@ const getStatusText = (status) => { }; export default function WechatRegister() { + // 获取关闭弹窗方法 + const { closeModal } = useAuthModal(); + const { refreshSession } = useAuth(); + // 状态管理 const [wechatAuthUrl, setWechatAuthUrl] = useState(""); const [wechatSessionId, setWechatSessionId] = useState(""); @@ -58,6 +64,7 @@ export default function WechatRegister() { const timeoutRef = useRef(null); const isMountedRef = useRef(true); // 追踪组件挂载状态 const containerRef = useRef(null); // 容器DOM引用 + const sessionIdRef = useRef(null); // 存储最新的 sessionId,避免闭包陷阱 const navigate = useNavigate(); const toast = useToast(); @@ -90,6 +97,7 @@ export default function WechatRegister() { /** * 清理所有定时器 + * 注意:不清理 sessionIdRef,因为 startPolling 时也会调用此函数 */ const clearTimers = useCallback(() => { if (pollIntervalRef.current) { @@ -124,14 +132,14 @@ export default function WechatRegister() { } showSuccess( - status === WECHAT_STATUS.LOGIN_SUCCESS ? "登录成功" : "注册成功", - "正在跳转..." + status === WECHAT_STATUS.LOGIN_SUCCESS ? "登录成功" : "欢迎回来!" ); - // 延迟跳转,让用户看到成功提示 - setTimeout(() => { - navigate("/home"); - }, 1000); + // 刷新 AuthContext 状态 + await refreshSession(); + + // 关闭认证弹窗,留在当前页面 + closeModal(); } else { throw new Error(response?.error || '登录失败'); } @@ -139,17 +147,27 @@ export default function WechatRegister() { logger.error('WechatRegister', 'handleLoginSuccess', error, { sessionId }); showError("登录失败", error.message || "请重试"); } - }, [navigate, showSuccess, showError]); + }, [showSuccess, showError, closeModal, refreshSession]); /** * 检查微信扫码状态 + * 使用 sessionIdRef.current 避免闭包陷阱 */ const checkWechatStatus = useCallback(async () => { - // 检查组件是否已卸载 - if (!isMountedRef.current || !wechatSessionId) return; + // 检查组件是否已卸载,使用 ref 获取最新的 sessionId + if (!isMountedRef.current || !sessionIdRef.current) { + logger.debug('WechatRegister', 'checkWechatStatus 跳过', { + isMounted: isMountedRef.current, + hasSessionId: !!sessionIdRef.current + }); + return; + } + + const currentSessionId = sessionIdRef.current; + logger.debug('WechatRegister', '检查微信状态', { sessionId: currentSessionId }); try { - const response = await authService.checkWechatStatus(wechatSessionId); + const response = await authService.checkWechatStatus(currentSessionId); // 安全检查:确保 response 存在且包含 status if (!response || typeof response.status === 'undefined') { @@ -158,6 +176,7 @@ export default function WechatRegister() { } const { status } = response; + logger.debug('WechatRegister', '微信状态', { status }); // 组件卸载后不再更新状态 if (!isMountedRef.current) return; @@ -167,23 +186,14 @@ export default function WechatRegister() { // 处理成功状态 if (status === WECHAT_STATUS.LOGIN_SUCCESS || status === WECHAT_STATUS.REGISTER_SUCCESS) { clearTimers(); // 停止轮询 + sessionIdRef.current = null; // 清理 sessionId - // 显示"扫码成功,登录中"提示 - if (isMountedRef.current) { - toast({ - title: "扫码成功", - description: "正在登录,请稍候...", - status: "info", - duration: 2000, - isClosable: false, - }); - } - - await handleLoginSuccess(wechatSessionId, status); + await handleLoginSuccess(currentSessionId, status); } // 处理过期状态 else if (status === WECHAT_STATUS.EXPIRED) { clearTimers(); + sessionIdRef.current = null; // 清理 sessionId if (isMountedRef.current) { toast({ title: "授权已过期", @@ -195,11 +205,12 @@ export default function WechatRegister() { } } } catch (error) { - logger.error('WechatRegister', 'checkWechatStatus', error, { sessionId: wechatSessionId }); + logger.error('WechatRegister', 'checkWechatStatus', error, { sessionId: currentSessionId }); // 轮询过程中的错误不显示给用户,避免频繁提示 // 但如果错误持续发生,停止轮询避免无限重试 if (error.message.includes('网络连接失败')) { clearTimers(); + sessionIdRef.current = null; // 清理 sessionId if (isMountedRef.current) { toast({ title: "网络连接失败", @@ -211,12 +222,17 @@ export default function WechatRegister() { } } } - }, [wechatSessionId, handleLoginSuccess, clearTimers, toast]); + }, [handleLoginSuccess, clearTimers, toast]); /** * 启动轮询 */ const startPolling = useCallback(() => { + logger.debug('WechatRegister', '启动轮询', { + sessionId: sessionIdRef.current, + interval: POLL_INTERVAL + }); + // 清理旧的定时器 clearTimers(); @@ -227,7 +243,9 @@ export default function WechatRegister() { // 设置超时 timeoutRef.current = setTimeout(() => { + logger.debug('WechatRegister', '二维码超时'); clearTimers(); + sessionIdRef.current = null; // 清理 sessionId setWechatStatus(WECHAT_STATUS.EXPIRED); }, QR_CODE_TIMEOUT); }, [checkWechatStatus, clearTimers]); @@ -254,10 +272,17 @@ export default function WechatRegister() { throw new Error(response.message || '获取二维码失败'); } + // 同时更新 ref 和 state,确保轮询能立即读取到最新值 + sessionIdRef.current = response.data.session_id; setWechatAuthUrl(response.data.auth_url); setWechatSessionId(response.data.session_id); setWechatStatus(WECHAT_STATUS.WAITING); + logger.debug('WechatRegister', '获取二维码成功', { + sessionId: response.data.session_id, + authUrl: response.data.auth_url + }); + // 启动轮询检查扫码状态 startPolling(); } catch (error) { @@ -293,21 +318,26 @@ export default function WechatRegister() { return () => { isMountedRef.current = false; clearTimers(); + sessionIdRef.current = null; // 清理 sessionId }; }, [clearTimers]); /** * 备用轮询机制 - 防止丢失状态 - * 每3秒检查一次,仅在获取到二维码URL且状态为waiting时执行 + * 每3秒检查一次,在 waiting 和 scanned 状态下都保持运行 + * ⚠️ 已临时注释,测试主轮询是否足够可靠 */ + /** useEffect(() => { - // 只在有auth_url、session_id且状态为waiting时启动备用轮询 - if (wechatAuthUrl && wechatSessionId && wechatStatus === WECHAT_STATUS.WAITING) { - logger.debug('WechatRegister', '备用轮询:启动备用轮询机制'); + // 在有auth_url、session_id且状态为waiting或scanned时启动备用轮询 + if (wechatAuthUrl && wechatSessionId && + (wechatStatus === WECHAT_STATUS.WAITING || wechatStatus === WECHAT_STATUS.SCANNED)) { + logger.debug('WechatRegister', '备用轮询:启动备用轮询机制', { status: wechatStatus }); backupPollIntervalRef.current = setInterval(() => { try { - if (wechatStatus === WECHAT_STATUS.WAITING && isMountedRef.current) { + if ((wechatStatus === WECHAT_STATUS.WAITING || wechatStatus === WECHAT_STATUS.SCANNED) && + isMountedRef.current && wechatAuthUrl) { logger.debug('WechatRegister', '备用轮询:检查微信状态'); // 添加 .catch() 静默处理异步错误,防止被 ErrorBoundary 捕获 checkWechatStatus().catch(error => { @@ -329,7 +359,7 @@ export default function WechatRegister() { } }; }, [wechatAuthUrl, wechatSessionId, wechatStatus, checkWechatStatus]); - + */ /** * 测量容器尺寸并计算缩放比例 */ @@ -397,7 +427,7 @@ export default function WechatRegister() { textAlign="center" mb={3} // 12px底部间距 > - 微信扫码 + 微信登陆 {/* ========== 二维码区域 ========== */} @@ -414,19 +444,26 @@ export default function WechatRegister() { bg="gray.50" boxShadow="sm" // ✅ 添加轻微阴影 > - {wechatStatus === WECHAT_STATUS.WAITING ? ( + {wechatStatus !== WECHAT_STATUS.NONE ? ( /* 已获取二维码:显示iframe */