This commit is contained in:
jeremygan2021
2026-02-12 17:32:22 +08:00
parent 5a7b2032c4
commit abb1fe7c5e
3 changed files with 152 additions and 34 deletions

View File

@@ -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 .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 .serializers import ESP32ConfigSerializer, OrderSerializer, ServiceSerializer, VCCourseSerializer, ServiceOrderSerializer, WeChatUserSerializer, DistributorSerializer, WithdrawalSerializer, CommissionLogSerializer, CourseEnrollmentSerializer
from .utils import get_access_token from .utils import get_access_token
from django.db import transaction
from django.core.signing import TimestampSigner, BadSignature, SignatureExpired from django.core.signing import TimestampSigner, BadSignature, SignatureExpired
from django.contrib.auth.models import User from django.contrib.auth.models import User
from wechatpayv3 import WeChatPay, WeChatPayType from wechatpayv3 import WeChatPay, WeChatPayType
@@ -998,7 +999,7 @@ def wechat_login(request):
session_key = data.get('session_key') session_key = data.get('session_key')
unionid = data.get('unionid') unionid = data.get('unionid')
# 3. 处理手机号 (尝试获取并合并 Web 用户) # 3. 处理手机号与用户合并逻辑
user = None user = None
phone_number = None phone_number = None
@@ -1013,42 +1014,82 @@ def wechat_login(request):
if phone_data.get('errcode') == 0: if phone_data.get('errcode') == 0:
phone_info = phone_data.get('phone_info') phone_info = phone_data.get('phone_info')
phone_number = phone_info.get('purePhoneNumber') 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: except Exception as e:
print(f"获取手机号失败: {e}") print(f"获取手机号失败: {e}")
# 4. 创建或更新用户 (如果未通过手机号找到) try:
if not user: with transaction.atomic():
defaults = { # 查找已存在的 OpenID 用户 (小程序用户)
'session_key': session_key, mp_user = WeChatUser.objects.select_for_update().filter(openid=openid).first()
'unionid': unionid
}
if phone_number:
defaults['phone_number'] = phone_number
user, created = WeChatUser.objects.update_or_create( # 查找已存在的手机号用户 (可能是 Web 用户或已绑定的 MP 用户)
openid=openid, phone_user = None
defaults=defaults if phone_number:
) phone_user = WeChatUser.objects.select_for_update().filter(phone_number=phone_number).first()
else:
# 如果找到了用户,且 OpenID 匹配(或刚被更新),更新 session_key if mp_user and phone_user:
if user.openid == openid: 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.session_key = session_key
user.unionid = unionid user.unionid = unionid
user.save() 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 # 生成 Token
signer = TimestampSigner() signer = TimestampSigner()

View File

@@ -20,8 +20,70 @@ export default function UserIndex() {
try { await Taro.chooseAddress() } catch(e) {} try { await Taro.chooseAddress() } catch(e) {}
} }
const login = () => { const login = async () => {
Taro.reLaunch({ url: '/pages/index/index' }) 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 = [ const serviceGroups = [
@@ -65,9 +127,24 @@ export default function UserIndex() {
</View> </View>
<View className='info-col'> <View className='info-col'>
<Text className='nickname'>{userInfo?.nickname || '未登录用户'}</Text> <Text className='nickname'>{userInfo?.nickname || '未登录用户'}</Text>
<Text className='uid'>ID: {userInfo ? '888888' : '----'}</Text> <Text className='uid'>ID: {userInfo ? (userInfo.phone_number || userInfo.id || '----') : '----'}</Text>
{!userInfo && ( {!userInfo && (
<Button className='btn-login' onClick={login}> / </Button> <View className='login-btns'>
<Button
className='btn-login'
onClick={login}
style={{ marginRight: '10px' }}
>
</Button>
<Button
className='btn-login primary'
openType="getPhoneNumber"
onGetPhoneNumber={getPhoneNumber}
>
</Button>
</View>
)} )}
</View> </View>
<View className='card-bg-effect' /> <View className='card-bg-effect' />