forum
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -311,8 +311,8 @@ class OrderAdmin(ModelAdmin):
|
||||
|
||||
@admin.register(WeChatUser)
|
||||
class WeChatUserAdmin(ModelAdmin):
|
||||
list_display = ('nickname', 'is_star', 'title', 'avatar_display', 'gender_display', 'province', 'city', 'created_at')
|
||||
search_fields = ('nickname', 'openid')
|
||||
list_display = ('nickname', 'phone_number', 'is_star', 'title', 'avatar_display', 'gender_display', 'province', 'city', 'created_at')
|
||||
search_fields = ('nickname', 'openid', 'phone_number')
|
||||
list_filter = ('is_star', 'gender', 'province', 'city', 'created_at')
|
||||
readonly_fields = ('openid', 'unionid', 'session_key', 'created_at', 'updated_at')
|
||||
|
||||
@@ -329,7 +329,7 @@ class WeChatUserAdmin(ModelAdmin):
|
||||
|
||||
fieldsets = (
|
||||
('基本信息', {
|
||||
'fields': ('user', 'nickname', 'avatar_url', 'gender')
|
||||
'fields': ('user', 'nickname', 'phone_number', 'avatar_url', 'gender')
|
||||
}),
|
||||
('专家认证', {
|
||||
'fields': ('is_star', 'title'),
|
||||
|
||||
43
backend/shop/utils.py
Normal file
43
backend/shop/utils.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import requests
|
||||
from django.core.cache import cache
|
||||
from .models import WeChatPayConfig
|
||||
|
||||
def get_access_token(config=None):
|
||||
"""
|
||||
获取微信接口调用凭证 (client_credential)
|
||||
"""
|
||||
# 尝试从缓存获取
|
||||
cache_key = 'wechat_access_token'
|
||||
if config:
|
||||
cache_key = f'wechat_access_token_{config.app_id}'
|
||||
|
||||
token = cache.get(cache_key)
|
||||
if token:
|
||||
return token
|
||||
|
||||
if not config:
|
||||
# 优先查找指定 AppID
|
||||
config = WeChatPayConfig.objects.filter(app_id='wxdf2ca73e6c0929f0').first()
|
||||
if not config:
|
||||
config = WeChatPayConfig.objects.filter(is_active=True).first()
|
||||
|
||||
if not config or not config.app_id or not config.app_secret:
|
||||
return None
|
||||
|
||||
url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={config.app_id}&secret={config.app_secret}"
|
||||
try:
|
||||
response = requests.get(url, timeout=10)
|
||||
data = response.json()
|
||||
|
||||
if 'access_token' in data:
|
||||
token = data['access_token']
|
||||
expires_in = data.get('expires_in', 7200)
|
||||
# 缓存 Token,留出 200 秒缓冲时间
|
||||
cache.set(cache_key, token, expires_in - 200)
|
||||
return token
|
||||
else:
|
||||
print(f"获取 AccessToken 失败: {data}")
|
||||
except Exception as e:
|
||||
print(f"获取 AccessToken 异常: {str(e)}")
|
||||
|
||||
return None
|
||||
@@ -10,6 +10,7 @@ from django.http import HttpResponse
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiExample
|
||||
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.core.signing import TimestampSigner, BadSignature, SignatureExpired
|
||||
from django.contrib.auth.models import User
|
||||
from wechatpayv3 import WeChatPay, WeChatPayType
|
||||
@@ -953,9 +954,13 @@ def get_current_wechat_user(request):
|
||||
|
||||
@extend_schema(
|
||||
summary="微信小程序登录",
|
||||
description="支持通过 code 登录,以及可选的 phone_code 用于直接获取手机号并合并 Web 用户账号",
|
||||
request={
|
||||
'application/json': {
|
||||
'properties': {'code': {'type': 'string', 'description': 'wx.login获取的code'}},
|
||||
'properties': {
|
||||
'code': {'type': 'string', 'description': 'wx.login获取的code'},
|
||||
'phone_code': {'type': 'string', 'description': 'getPhoneNumber获取的code (可选)'}
|
||||
},
|
||||
'required': ['code']
|
||||
}
|
||||
},
|
||||
@@ -964,14 +969,21 @@ def get_current_wechat_user(request):
|
||||
@api_view(['POST'])
|
||||
def wechat_login(request):
|
||||
code = request.data.get('code')
|
||||
phone_code = request.data.get('phone_code')
|
||||
|
||||
if not code:
|
||||
return Response({'error': 'Code is required'}, status=400)
|
||||
|
||||
config = WeChatPayConfig.objects.filter(is_active=True).first()
|
||||
# 1. 获取配置 (优先使用指定 AppID)
|
||||
target_app_id = 'wxdf2ca73e6c0929f0'
|
||||
config = WeChatPayConfig.objects.filter(app_id=target_app_id).first()
|
||||
if not config:
|
||||
config = WeChatPayConfig.objects.filter(is_active=True).first()
|
||||
|
||||
if not config or not config.app_id or not config.app_secret:
|
||||
return Response({'error': 'WeChat config missing'}, status=500)
|
||||
|
||||
# 换取 OpenID
|
||||
# 2. 换取 OpenID
|
||||
url = f"https://api.weixin.qq.com/sns/jscode2session?appid={config.app_id}&secret={config.app_secret}&js_code={code}&grant_type=authorization_code"
|
||||
try:
|
||||
res = requests.get(url, timeout=10)
|
||||
@@ -985,26 +997,70 @@ def wechat_login(request):
|
||||
openid = data.get('openid')
|
||||
session_key = data.get('session_key')
|
||||
unionid = data.get('unionid')
|
||||
|
||||
# 3. 处理手机号 (尝试获取并合并 Web 用户)
|
||||
user = None
|
||||
phone_number = None
|
||||
|
||||
if phone_code:
|
||||
access_token = get_access_token(config)
|
||||
if access_token:
|
||||
try:
|
||||
phone_url = f"https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={access_token}"
|
||||
phone_res = requests.post(phone_url, json={'code': phone_code}, timeout=5)
|
||||
phone_data = phone_res.json()
|
||||
|
||||
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}")
|
||||
|
||||
# 创建或更新用户
|
||||
user, created = WeChatUser.objects.update_or_create(
|
||||
openid=openid,
|
||||
defaults={
|
||||
# 4. 创建或更新用户 (如果未通过手机号找到)
|
||||
if not user:
|
||||
defaults = {
|
||||
'session_key': session_key,
|
||||
'unionid': unionid
|
||||
}
|
||||
)
|
||||
if phone_number:
|
||||
defaults['phone_number'] = phone_number
|
||||
|
||||
user, created = WeChatUser.objects.update_or_create(
|
||||
openid=openid,
|
||||
defaults=defaults
|
||||
)
|
||||
else:
|
||||
# 如果找到了用户,且 OpenID 匹配(或刚被更新),更新 session_key
|
||||
if user.openid == openid:
|
||||
user.session_key = session_key
|
||||
user.unionid = unionid
|
||||
user.save()
|
||||
created = False
|
||||
|
||||
# 生成 Token
|
||||
signer = TimestampSigner()
|
||||
token = signer.sign(openid)
|
||||
token = signer.sign(user.openid)
|
||||
|
||||
return Response({
|
||||
'token': token,
|
||||
'id': user.id,
|
||||
'openid': openid,
|
||||
'openid': user.openid,
|
||||
'is_new': created,
|
||||
'nickname': user.nickname
|
||||
'nickname': user.nickname,
|
||||
'phone_number': user.phone_number
|
||||
})
|
||||
|
||||
@extend_schema(
|
||||
|
||||
Reference in New Issue
Block a user