Merge branch '1028_bugfix' into feature
* 1028_bugfix: 手机号格式适配-前端修改 添加微信扫码的几种其他状态 整合register端口进入login端口
This commit is contained in:
209
app.py
209
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)
|
||||
@@ -1968,12 +1988,51 @@ def login_with_verification_code():
|
||||
username = f"{base_username}_{counter}"
|
||||
counter += 1
|
||||
|
||||
# 如果用户不存在,自动创建新用户
|
||||
if not user:
|
||||
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, email=credential)
|
||||
user.email_confirmed = True
|
||||
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)
|
||||
|
||||
@@ -1989,10 +2048,13 @@ def login_with_verification_code():
|
||||
# 更新最后登录时间
|
||||
user.update_last_seen()
|
||||
|
||||
# 根据是否为新用户返回不同的消息
|
||||
message = '注册成功,欢迎加入!' if is_new_user else '登录成功'
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '注册成功' if is_new_user else '登录成功',
|
||||
'isNewUser': is_new_user,
|
||||
'message': message,
|
||||
'is_new_user': is_new_user,
|
||||
'user': {
|
||||
'id': user.id,
|
||||
'username': user.username,
|
||||
@@ -2004,6 +2066,59 @@ def login_with_verification_code():
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"验证码登录错误: {e}")
|
||||
db.session.rollback()
|
||||
return jsonify({'success': False, 'error': '登录失败'}), 500
|
||||
|
||||
|
||||
@app.route('/api/auth/register', methods=['POST'])
|
||||
def register():
|
||||
"""用户注册 - 使用Session"""
|
||||
username = request.form.get('username')
|
||||
email = request.form.get('email')
|
||||
password = request.form.get('password')
|
||||
|
||||
# 验证输入
|
||||
if not all([username, email, password]):
|
||||
return jsonify({'success': False, 'error': '所有字段都是必填的'}), 400
|
||||
|
||||
# 检查用户名和邮箱是否已存在
|
||||
if User.is_username_taken(username):
|
||||
return jsonify({'success': False, 'error': '用户名已存在'}), 400
|
||||
|
||||
if User.is_email_taken(email):
|
||||
return jsonify({'success': False, 'error': '邮箱已被使用'}), 400
|
||||
|
||||
try:
|
||||
# 创建新用户
|
||||
user = User(username=username, email=email)
|
||||
user.set_password(password)
|
||||
user.email_confirmed = True # 暂时默认已确认
|
||||
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
# 自动登录
|
||||
session.permanent = True
|
||||
session['user_id'] = user.id
|
||||
session['username'] = user.username
|
||||
session['logged_in'] = True
|
||||
|
||||
# Flask-Login 登录
|
||||
login_user(user, remember=True)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '注册成功',
|
||||
'user': {
|
||||
'id': user.id,
|
||||
'username': user.username,
|
||||
'nickname': user.nickname or user.username,
|
||||
'email': user.email
|
||||
}
|
||||
}), 201
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"验证码登录/注册错误: {e}")
|
||||
@@ -2619,11 +2734,19 @@ def wechat_callback():
|
||||
state = request.args.get('state')
|
||||
error = request.args.get('error')
|
||||
|
||||
print(f"🎯 [CALLBACK] 微信回调被调用!code={code[:10] if code else None}..., state={state[:8] if state else None}..., error={error}")
|
||||
# 错误处理:用户拒绝授权
|
||||
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 error or not code or not state:
|
||||
print(f"❌ [CALLBACK] 参数错误: error={error}, has_code={bool(code)}, has_state={bool(state)}")
|
||||
# 参数验证
|
||||
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
|
||||
@@ -2643,20 +2766,28 @@ def wechat_callback():
|
||||
return redirect('/auth/signin?error=session_expired')
|
||||
|
||||
try:
|
||||
# 获取access_token
|
||||
print(f"🔑 [CALLBACK] 开始获取 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:
|
||||
print(f"❌ [CALLBACK] 获取 access_token 失败")
|
||||
session_data['status'] = 'auth_failed'
|
||||
session_data['error'] = '获取访问令牌失败'
|
||||
print(f"❌ 获取微信access_token失败: state={state}")
|
||||
return redirect('/auth/signin?error=token_failed')
|
||||
|
||||
print(f"✅ [CALLBACK] 获取 access_token 成功, openid={token_data.get('openid', '')[:8]}...")
|
||||
# 步骤3: Token获取成功,标记为已授权
|
||||
session_data['status'] = 'authorized'
|
||||
print(f"✅ 微信授权成功: openid={token_data['openid']}")
|
||||
|
||||
# 获取用户信息
|
||||
print(f"👤 [CALLBACK] 开始获取用户信息...")
|
||||
# 步骤4: 获取用户信息
|
||||
user_info = get_wechat_userinfo(token_data['access_token'], token_data['openid'])
|
||||
if not user_info:
|
||||
print(f"❌ [CALLBACK] 获取用户信息失败")
|
||||
session_data['status'] = 'auth_failed'
|
||||
session_data['error'] = '获取用户信息失败'
|
||||
print(f"❌ 获取微信用户信息失败: openid={token_data['openid']}")
|
||||
return redirect('/auth/signin?error=userinfo_failed')
|
||||
|
||||
print(f"✅ [CALLBACK] 获取用户信息成功, nickname={user_info.get('nickname', 'N/A')}")
|
||||
@@ -2736,23 +2867,31 @@ def wechat_callback():
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
# 更新 wechat_qr_sessions 状态,供前端轮询检测
|
||||
print(f"🔍 [DEBUG] state={state}, state in wechat_qr_sessions: {state in wechat_qr_sessions}")
|
||||
is_new_user = True
|
||||
print(f"✅ 微信扫码自动创建新用户: {username}, openid: {openid}")
|
||||
|
||||
# 更新最后登录时间
|
||||
user.update_last_seen()
|
||||
|
||||
# 设置session
|
||||
session.permanent = True
|
||||
session['user_id'] = user.id
|
||||
session['username'] = user.username
|
||||
session['logged_in'] = True
|
||||
session['wechat_login'] = True # 标记是微信登录
|
||||
|
||||
# Flask-Login 登录
|
||||
login_user(user, remember=True)
|
||||
|
||||
# 更新微信session状态,供前端轮询检测
|
||||
if state in wechat_qr_sessions:
|
||||
session_item = wechat_qr_sessions[state]
|
||||
mode = session_item.get('mode')
|
||||
print(f"🔍 [DEBUG] session_item mode: {mode}, is_new_user: {is_new_user}")
|
||||
# 不是绑定模式才更新为登录状态
|
||||
if not mode:
|
||||
new_status = 'register_success' if is_new_user else 'login_success'
|
||||
session_item['status'] = new_status
|
||||
session_item['user_info'] = {
|
||||
'user_id': user.id,
|
||||
'is_new_user': is_new_user
|
||||
}
|
||||
print(f"✅ [DEBUG] 更新 wechat_qr_sessions 状态: {new_status}, user_id: {user.id}")
|
||||
else:
|
||||
print(f"⚠️ [DEBUG] 跳过状态更新,因为 mode={mode}")
|
||||
# 仅处理登录/注册流程,不处理绑定流程
|
||||
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 '''
|
||||
@@ -2772,10 +2911,16 @@ def wechat_callback():
|
||||
''', 200
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ [CALLBACK] 微信登录失败: {e}")
|
||||
print(f"❌ 微信登录失败: {e}")
|
||||
import traceback
|
||||
print(f"❌ [CALLBACK] 错误堆栈:\n{traceback.format_exc()}")
|
||||
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')
|
||||
|
||||
|
||||
|
||||
@@ -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'
|
||||
});
|
||||
|
||||
@@ -35,6 +35,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";
|
||||
}
|
||||
};
|
||||
@@ -215,6 +217,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: currentSessionId });
|
||||
// 轮询过程中的错误不显示给用户,避免频繁提示
|
||||
|
||||
@@ -147,6 +147,8 @@ export const WECHAT_STATUS = {
|
||||
LOGIN_SUCCESS: 'authorized', // ✅ 与后端保持一致,统一使用 'authorized'
|
||||
REGISTER_SUCCESS: 'authorized', // ✅ 与后端保持一致,统一使用 'authorized'
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user