diff --git a/backend/shop/__pycache__/views.cpython-312.pyc b/backend/shop/__pycache__/views.cpython-312.pyc index 477e8b8..c2c010c 100644 Binary files a/backend/shop/__pycache__/views.cpython-312.pyc and b/backend/shop/__pycache__/views.cpython-312.pyc differ diff --git a/backend/shop/views.py b/backend/shop/views.py index 25eb913..6c998f4 100644 --- a/backend/shop/views.py +++ b/backend/shop/views.py @@ -11,6 +11,7 @@ from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiPara from .models import ESP32Config, Order, WeChatPayConfig, Service, VCCourse, ServiceOrder, Salesperson, CommissionLog, WeChatUser, Distributor, Withdrawal, CourseEnrollment from .serializers import ESP32ConfigSerializer, OrderSerializer, ServiceSerializer, VCCourseSerializer, ServiceOrderSerializer, WeChatUserSerializer, DistributorSerializer, WithdrawalSerializer, CommissionLogSerializer, CourseEnrollmentSerializer from .utils import get_access_token +from django.db import transaction from django.core.signing import TimestampSigner, BadSignature, SignatureExpired from django.contrib.auth.models import User from wechatpayv3 import WeChatPay, WeChatPayType @@ -998,7 +999,7 @@ def wechat_login(request): session_key = data.get('session_key') unionid = data.get('unionid') - # 3. 处理手机号 (尝试获取并合并 Web 用户) + # 3. 处理手机号与用户合并逻辑 user = None phone_number = None @@ -1013,42 +1014,82 @@ def wechat_login(request): if phone_data.get('errcode') == 0: phone_info = phone_data.get('phone_info') phone_number = phone_info.get('purePhoneNumber') - - if phone_number: - # 查找已存在的用户 (Web 用户或已绑定手机的 MP 用户) - existing_user = WeChatUser.objects.filter(phone_number=phone_number).first() - if existing_user: - user = existing_user - # 如果是 Web 虚拟账号 (openid 以 web_ 开头),更新为真实 OpenID - if user.openid.startswith('web_') or not user.openid: - user.openid = openid - user.session_key = session_key - user.unionid = unionid - user.save() - # 如果已是真实账号但 OpenID 不匹配,可能是不同 AppID,暂不处理(避免覆盖) except Exception as e: print(f"获取手机号失败: {e}") - # 4. 创建或更新用户 (如果未通过手机号找到) - if not user: - defaults = { - 'session_key': session_key, - 'unionid': unionid - } - if phone_number: - defaults['phone_number'] = phone_number + try: + with transaction.atomic(): + # 查找已存在的 OpenID 用户 (小程序用户) + mp_user = WeChatUser.objects.select_for_update().filter(openid=openid).first() - user, created = WeChatUser.objects.update_or_create( - openid=openid, - defaults=defaults - ) - else: - # 如果找到了用户,且 OpenID 匹配(或刚被更新),更新 session_key - if user.openid == openid: + # 查找已存在的手机号用户 (可能是 Web 用户或已绑定的 MP 用户) + phone_user = None + if phone_number: + phone_user = WeChatUser.objects.select_for_update().filter(phone_number=phone_number).first() + + if mp_user and phone_user: + if mp_user != phone_user: + # 【合并场景】: 小程序用户 和 手机号用户 都存在且不同 -> 将手机号用户(Web)合并到小程序用户 + # 注意: 如果 phone_user 也是真实的 MP 用户(有 openid 且不是 web_), 则可能冲突,这里默认保留当前登录的 mp_user + + # 1. 迁移订单 + Order.objects.filter(wechat_user=phone_user).update(wechat_user=mp_user) + # 2. 迁移社区数据 (延迟导入避免循环引用) + from community.models import ActivitySignup, Topic, Reply + ActivitySignup.objects.filter(user=phone_user).update(user=mp_user) + Topic.objects.filter(author=phone_user).update(author=mp_user) + Reply.objects.filter(author=phone_user).update(author=mp_user) + # 3. 迁移分销员 + if hasattr(phone_user, 'distributor') and not hasattr(mp_user, 'distributor'): + dist = phone_user.distributor + dist.user = mp_user + dist.save() + + # 删除旧用户 + phone_user.delete() + user = mp_user + + # 更新手机号 + if not user.phone_number: + user.phone_number = phone_number + else: + # 同一个用户 + user = mp_user + + elif phone_user: + # 【绑定场景】: 只有手机号用户存在 (通常是 Web 用户) -> 升级为小程序用户 + user = phone_user + # 只有当它是 Web 虚拟用户时,才覆盖 OpenID + # 或者如果它就是目标用户,直接更新 + if user.openid.startswith('web_') or not user.openid: + user.openid = openid + elif user.openid != openid: + # 冲突: 手机号已被另一个真实 OpenID 绑定,但当前登录的是新的 OpenID + # 策略: 创建新用户,不合并 (避免安全风险) + user = WeChatUser.objects.create(openid=openid) + + elif mp_user: + # 【更新场景】: 只有小程序用户存在 -> 更新手机号 + user = mp_user + if phone_number: + user.phone_number = phone_number + + else: + # 【新建场景】: 都不存在 -> 创建新用户 + user = WeChatUser.objects.create(openid=openid) + if phone_number: + user.phone_number = phone_number + + # 统一更新会话信息 user.session_key = session_key user.unionid = unionid user.save() - created = False + created = False # 简化处理 + + except Exception as e: + import traceback + traceback.print_exc() + return Response({'error': f'Login failed: {str(e)}'}, status=500) # 生成 Token signer = TimestampSigner() diff --git a/miniprogram/src/pages/user/index.tsx b/miniprogram/src/pages/user/index.tsx index 76c3ebe..ee9d18c 100644 --- a/miniprogram/src/pages/user/index.tsx +++ b/miniprogram/src/pages/user/index.tsx @@ -20,8 +20,70 @@ export default function UserIndex() { try { await Taro.chooseAddress() } catch(e) {} } - const login = () => { - Taro.reLaunch({ url: '/pages/index/index' }) + const login = async () => { + try { + // 1. 获取微信登录 Code + const { code } = await Taro.login() + if (!code) throw new Error('登录失败:无法获取 Code') + + // 2. 调用后端登录 (仅 Code) + const res = await Taro.request({ + url: 'https://market.quant-speed.com/api/wechat/login/', + method: 'POST', + data: { code } + }) + + console.log('code:', code) + + if (res.statusCode === 200 && res.data.token) { + Taro.setStorageSync('token', res.data.token) + Taro.setStorageSync('userInfo', res.data) + setUserInfo(res.data) + Taro.showToast({ title: '登录成功', icon: 'success' }) + } else { + throw new Error(res.data.error || '登录请求失败') + } + } catch (e) { + Taro.showToast({ title: e.message || '登录失败', icon: 'none' }) + } + } + + const getPhoneNumber = async (e) => { + const { code: phoneCode, errMsg } = e.detail + if (errMsg !== "getPhoneNumber:ok") { + Taro.showToast({ title: '获取手机号失败', icon: 'none' }) + return + } + + try { + Taro.showLoading({ title: '登录中...' }) + // 1. 获取登录 Code + const { code: loginCode } = await Taro.login() + + // 2. 调用后端登录 (Code + PhoneCode) + const res = await Taro.request({ + url: 'https://market.quant-speed.com/api/wechat/login/', + method: 'POST', + data: { + code: loginCode, + phone_code: phoneCode + } + }) + + Taro.hideLoading() + + if (res.statusCode === 200 && res.data.token) { + Taro.setStorageSync('token', res.data.token) + Taro.setStorageSync('userInfo', res.data) + setUserInfo(res.data) + Taro.showToast({ title: '授权登录成功', icon: 'success' }) + } else { + throw new Error(res.data.error || '登录失败') + } + } catch(err) { + Taro.hideLoading() + Taro.showToast({ title: err.message || '系统异常', icon: 'none' }) + } } const serviceGroups = [ @@ -65,9 +127,24 @@ export default function UserIndex() { {userInfo?.nickname || '未登录用户'} - ID: {userInfo ? '888888' : '----'} + ID: {userInfo ? (userInfo.phone_number || userInfo.id || '----') : '----'} {!userInfo && ( - + + + + )}