forum
This commit is contained in:
Binary file not shown.
@@ -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()
|
||||||
|
|||||||
@@ -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' />
|
||||||
|
|||||||
Reference in New Issue
Block a user