From 512aca16d8c6af28f3b934571627a37096bb41da Mon Sep 17 00:00:00 2001 From: zzlgreat Date: Tue, 28 Oct 2025 15:47:50 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=95=B4=E5=90=88register=E7=AB=AF?= =?UTF-8?q?=E5=8F=A3=E8=BF=9B=E5=85=A5login=E7=AB=AF=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/app.py b/app.py index a7165ec7..be2fbb22 100755 --- a/app.py +++ b/app.py @@ -1932,13 +1932,57 @@ def login_with_verification_code(): # 验证码正确,查找用户 user = None + is_new_user = False + if login_type == 'phone': user = User.query.filter_by(phone=credential).first() elif login_type == 'email': user = User.query.filter_by(email=credential).first() + # 如果用户不存在,自动创建新用户 if not user: - return jsonify({'success': False, 'error': '用户不存在'}), 404 + try: + # 生成用户名 + if login_type == 'phone': + # 使用手机号生成用户名 + base_username = f"用户{credential[-4:]}" + elif login_type == 'email': + # 使用邮箱前缀生成用户名 + base_username = credential.split('@')[0] + else: + base_username = "新用户" + + # 确保用户名唯一 + username = base_username + counter = 1 + while User.is_username_taken(username): + username = f"{base_username}_{counter}" + counter += 1 + + # 创建新用户 + user = User(username=username) + + # 设置手机号或邮箱 + if login_type == 'phone': + user.phone = credential + elif login_type == 'email': + user.email = credential + + # 设置默认密码(使用随机密码,用户后续可以修改) + user.set_password(uuid.uuid4().hex) + user.status = 'active' + user.nickname = username + + db.session.add(user) + db.session.commit() + + is_new_user = True + print(f"✅ 自动创建新用户: {username}, {login_type}: {credential}") + + except Exception as e: + print(f"❌ 创建用户失败: {e}") + db.session.rollback() + return jsonify({'success': False, 'error': '创建用户失败'}), 500 # 清除验证码 session.pop(session_key, None) @@ -1955,9 +1999,13 @@ def login_with_verification_code(): # 更新最后登录时间 user.update_last_seen() + # 根据是否为新用户返回不同的消息 + message = '注册成功,欢迎加入!' if is_new_user else '登录成功' + return jsonify({ 'success': True, - 'message': '登录成功', + 'message': message, + 'is_new_user': is_new_user, 'user': { 'id': user.id, 'username': user.username, @@ -1971,6 +2019,7 @@ def login_with_verification_code(): except Exception as e: print(f"验证码登录错误: {e}") + db.session.rollback() return jsonify({'success': False, 'error': '登录失败'}), 500 @@ -2696,6 +2745,8 @@ def wechat_callback(): return redirect('/home?bind=failed') user = None + is_new_user = False + if unionid: user = User.query.filter_by(wechat_union_id=unionid).first() if not user: @@ -2726,6 +2777,9 @@ def wechat_callback(): db.session.add(user) db.session.commit() + is_new_user = True + print(f"✅ 微信扫码自动创建新用户: {username}, openid: {openid}") + # 更新最后登录时间 user.update_last_seen() @@ -2739,11 +2793,15 @@ def wechat_callback(): # Flask-Login 登录 login_user(user, remember=True) - # 清理微信session(仅登录/注册流程清理;绑定流程在上方已处理,不在此处清理) + # 更新微信session状态,供前端轮询检测 if state in wechat_qr_sessions: - # 仅当不是绑定流程,或没有模式信息时清理 - if not wechat_qr_sessions[state].get('mode'): - del wechat_qr_sessions[state] + session_item = wechat_qr_sessions[state] + # 仅处理登录/注册流程,不处理绑定流程 + if not session_item.get('mode'): + # 更新状态和用户信息 + session_item['status'] = 'register_ready' if is_new_user else 'login_ready' + session_item['user_info'] = {'user_id': user.id} + print(f"✅ 微信扫码状态已更新: {session_item['status']}, user_id: {user.id}") # 直接跳转到首页 return redirect('/home') From dd59cb6385a7f705822cce3eedbe242f1d332e3e Mon Sep 17 00:00:00 2001 From: zzlgreat Date: Wed, 29 Oct 2025 07:33:44 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E6=89=AB=E7=A0=81=E7=9A=84=E5=87=A0=E7=A7=8D=E5=85=B6=E4=BB=96?= =?UTF-8?q?=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 41 ++++++++++++++++++++++++--- src/components/Auth/WechatRegister.js | 29 +++++++++++++++++++ src/services/authService.js | 4 +++ 3 files changed, 70 insertions(+), 4 deletions(-) diff --git a/app.py b/app.py index be2fbb22..97d0563c 100755 --- a/app.py +++ b/app.py @@ -2677,8 +2677,19 @@ def wechat_callback(): state = request.args.get('state') error = request.args.get('error') - # 错误处理 - if error or not code or not state: + # 错误处理:用户拒绝授权 + if error: + if state in wechat_qr_sessions: + wechat_qr_sessions[state]['status'] = 'auth_denied' + wechat_qr_sessions[state]['error'] = '用户拒绝授权' + print(f"❌ 用户拒绝授权: state={state}") + return redirect('/auth/signin?error=wechat_auth_denied') + + # 参数验证 + if not code or not state: + if state in wechat_qr_sessions: + wechat_qr_sessions[state]['status'] = 'auth_failed' + wechat_qr_sessions[state]['error'] = '授权参数缺失' return redirect('/auth/signin?error=wechat_auth_failed') # 验证state @@ -2693,14 +2704,28 @@ def wechat_callback(): return redirect('/auth/signin?error=session_expired') try: - # 获取access_token + # 步骤1: 用户已扫码并授权(微信回调过来说明用户已完成扫码+授权) + session_data['status'] = 'scanned' + print(f"✅ 微信扫码回调: state={state}, code={code[:10]}...") + + # 步骤2: 获取access_token token_data = get_wechat_access_token(code) if not token_data: + session_data['status'] = 'auth_failed' + session_data['error'] = '获取访问令牌失败' + print(f"❌ 获取微信access_token失败: state={state}") return redirect('/auth/signin?error=token_failed') - # 获取用户信息 + # 步骤3: Token获取成功,标记为已授权 + session_data['status'] = 'authorized' + print(f"✅ 微信授权成功: openid={token_data['openid']}") + + # 步骤4: 获取用户信息 user_info = get_wechat_userinfo(token_data['access_token'], token_data['openid']) if not user_info: + session_data['status'] = 'auth_failed' + session_data['error'] = '获取用户信息失败' + print(f"❌ 获取微信用户信息失败: openid={token_data['openid']}") return redirect('/auth/signin?error=userinfo_failed') # 查找或创建用户 / 或处理绑定 @@ -2808,7 +2833,15 @@ def wechat_callback(): except Exception as e: print(f"❌ 微信登录失败: {e}") + import traceback + traceback.print_exc() db.session.rollback() + + # 更新session状态为失败 + if state in wechat_qr_sessions: + wechat_qr_sessions[state]['status'] = 'auth_failed' + wechat_qr_sessions[state]['error'] = str(e) + return redirect('/auth/signin?error=login_failed') diff --git a/src/components/Auth/WechatRegister.js b/src/components/Auth/WechatRegister.js index 67f81447..3b1e2076 100644 --- a/src/components/Auth/WechatRegister.js +++ b/src/components/Auth/WechatRegister.js @@ -33,6 +33,8 @@ const getStatusColor = (status) => { case WECHAT_STATUS.EXPIRED: return "orange.600"; // ✅ 橙色文字 case WECHAT_STATUS.LOGIN_SUCCESS: return "green.600"; // ✅ 绿色文字 case WECHAT_STATUS.REGISTER_SUCCESS: return "green.600"; + case WECHAT_STATUS.AUTH_DENIED: return "red.600"; // ✅ 红色文字 + case WECHAT_STATUS.AUTH_FAILED: return "red.600"; // ✅ 红色文字 default: return "gray.600"; } }; @@ -194,6 +196,33 @@ export default function WechatRegister() { }); } } + // 处理用户拒绝授权 + else if (status === WECHAT_STATUS.AUTH_DENIED) { + clearTimers(); + if (isMountedRef.current) { + toast({ + title: "授权已取消", + description: "您已取消微信授权登录", + status: "warning", + duration: 3000, + isClosable: true, + }); + } + } + // 处理授权失败 + else if (status === WECHAT_STATUS.AUTH_FAILED) { + clearTimers(); + if (isMountedRef.current) { + const errorMsg = response.error || "授权过程出现错误"; + toast({ + title: "授权失败", + description: errorMsg, + status: "error", + duration: 5000, + isClosable: true, + }); + } + } } catch (error) { logger.error('WechatRegister', 'checkWechatStatus', error, { sessionId: wechatSessionId }); // 轮询过程中的错误不显示给用户,避免频繁提示 diff --git a/src/services/authService.js b/src/services/authService.js index 53c6ad46..48f95004 100644 --- a/src/services/authService.js +++ b/src/services/authService.js @@ -147,6 +147,8 @@ export const WECHAT_STATUS = { LOGIN_SUCCESS: 'login_success', REGISTER_SUCCESS: 'register_success', EXPIRED: 'expired', + AUTH_DENIED: 'auth_denied', // 用户拒绝授权 + AUTH_FAILED: 'auth_failed', // 授权失败 }; /** @@ -157,6 +159,8 @@ export const STATUS_MESSAGES = { [WECHAT_STATUS.SCANNED]: '扫码成功,请在手机上确认', [WECHAT_STATUS.AUTHORIZED]: '授权成功,正在登录...', [WECHAT_STATUS.EXPIRED]: '二维码已过期', + [WECHAT_STATUS.AUTH_DENIED]: '用户取消授权', + [WECHAT_STATUS.AUTH_FAILED]: '授权失败,请重试', }; export default authService; From 8417ab17be18bb7c9fd6587d76a8eefaef9910a4 Mon Sep 17 00:00:00 2001 From: zzlgreat Date: Wed, 29 Oct 2025 11:20:41 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=89=8B=E6=9C=BA=E5=8F=B7=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E9=80=82=E9=85=8D-=E5=89=8D=E7=AB=AF=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 20 ++++++++++++++++++++ src/components/Auth/AuthFormContent.js | 22 ++++++++++++++-------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/app.py b/app.py index 97d0563c..891d3ea6 100755 --- a/app.py +++ b/app.py @@ -1849,6 +1849,15 @@ def send_verification_code(): if not credential or not code_type: return jsonify({'success': False, 'error': '缺少必要参数'}), 400 + # 清理格式字符(空格、横线、括号等) + if code_type == 'phone': + # 移除手机号中的空格、横线、括号、加号等格式字符 + credential = re.sub(r'[\s\-\(\)\+]', '', credential) + print(f"📱 清理后的手机号: {credential}") + elif code_type == 'email': + # 邮箱只移除空格 + credential = credential.strip() + # 生成验证码 verification_code = generate_verification_code() @@ -1907,6 +1916,17 @@ def login_with_verification_code(): if not credential or not verification_code or not login_type: return jsonify({'success': False, 'error': '缺少必要参数'}), 400 + # 清理格式字符(空格、横线、括号等) + if login_type == 'phone': + # 移除手机号中的空格、横线、括号、加号等格式字符 + original_credential = credential + credential = re.sub(r'[\s\-\(\)\+]', '', credential) + if original_credential != credential: + print(f"📱 登录时清理手机号: {original_credential} -> {credential}") + elif login_type == 'email': + # 邮箱只移除前后空格 + credential = credential.strip() + # 检查验证码 session_key = f'verification_code_{login_type}_{credential}_login' stored_code_info = session.get(session_key) diff --git a/src/components/Auth/AuthFormContent.js b/src/components/Auth/AuthFormContent.js index 793f2293..6de1fa70 100644 --- a/src/components/Auth/AuthFormContent.js +++ b/src/components/Auth/AuthFormContent.js @@ -143,7 +143,10 @@ export default function AuthFormContent() { return; } - if (!/^1[3-9]\d{9}$/.test(credential)) { + // 清理手机号格式字符(空格、横线、括号等) + const cleanedCredential = credential.replace(/[\s\-\(\)\+]/g, ''); + + if (!/^1[3-9]\d{9}$/.test(cleanedCredential)) { toast({ title: "请输入有效的手机号", status: "warning", @@ -156,7 +159,7 @@ export default function AuthFormContent() { setSendingCode(true); const requestData = { - credential: credential.trim(), // 添加 trim() 防止空格 + credential: cleanedCredential, // 使用清理后的手机号 type: 'phone', purpose: config.api.purpose }; @@ -189,13 +192,13 @@ export default function AuthFormContent() { if (response.ok && data.success) { // ❌ 移除成功 toast,静默处理 logger.info('AuthFormContent', '验证码发送成功', { - credential: credential.substring(0, 3) + '****' + credential.substring(7), + credential: cleanedCredential.substring(0, 3) + '****' + cleanedCredential.substring(7), dev_code: data.dev_code }); // ✅ 开发环境下在控制台显示验证码 if (data.dev_code) { - console.log(`%c✅ [验证码] ${credential} -> ${data.dev_code}`, 'color: #16a34a; font-weight: bold; font-size: 14px;'); + console.log(`%c✅ [验证码] ${cleanedCredential} -> ${data.dev_code}`, 'color: #16a34a; font-weight: bold; font-size: 14px;'); } setVerificationCodeSent(true); @@ -205,7 +208,7 @@ export default function AuthFormContent() { } } catch (error) { logger.api.error('POST', '/api/auth/send-verification-code', error, { - credential: credential.substring(0, 3) + '****' + credential.substring(7) + credential: cleanedCredential.substring(0, 3) + '****' + cleanedCredential.substring(7) }); // ✅ 显示错误提示给用户 @@ -247,7 +250,10 @@ export default function AuthFormContent() { return; } - if (!/^1[3-9]\d{9}$/.test(phone)) { + // 清理手机号格式字符(空格、横线、括号等) + const cleanedPhone = phone.replace(/[\s\-\(\)\+]/g, ''); + + if (!/^1[3-9]\d{9}$/.test(cleanedPhone)) { toast({ title: "请输入有效的手机号", status: "warning", @@ -258,13 +264,13 @@ export default function AuthFormContent() { // 构建请求体 const requestBody = { - credential: phone.trim(), // 添加 trim() 防止空格 + credential: cleanedPhone, // 使用清理后的手机号 verification_code: verificationCode.trim(), // 添加 trim() 防止空格 login_type: 'phone', }; logger.api.request('POST', '/api/auth/login-with-code', { - credential: phone.substring(0, 3) + '****' + phone.substring(7), + credential: cleanedPhone.substring(0, 3) + '****' + cleanedPhone.substring(7), verification_code: verificationCode.substring(0, 2) + '****', login_type: 'phone' });