This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 6.0.1 on 2026-02-27 05:45
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('shop', '0037_wechatuser_has_web_badge'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='vccourse',
|
||||
name='is_video_course',
|
||||
field=models.BooleanField(default=False, verbose_name='是否视频课程'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='vccourse',
|
||||
name='video_url',
|
||||
field=models.URLField(blank=True, help_text='仅当用户付费或报名后可见', null=True, verbose_name='视频课程URL'),
|
||||
),
|
||||
]
|
||||
@@ -362,6 +362,10 @@ class VCCourse(models.Model):
|
||||
|
||||
tag = models.CharField(max_length=20, blank=True, verbose_name="标签", help_text="例如: 热门, 推荐, 进阶")
|
||||
|
||||
# 视频课程相关
|
||||
is_video_course = models.BooleanField(default=False, verbose_name="是否视频课程")
|
||||
video_url = models.URLField(blank=True, null=True, verbose_name="视频课程URL", help_text="仅当用户付费或报名后可见")
|
||||
|
||||
# 课程时间安排
|
||||
is_fixed_schedule = models.BooleanField(default=False, verbose_name="是否固定时间课程", help_text="勾选后,前端将显示具体的开课时间")
|
||||
start_time = models.DateTimeField(blank=True, null=True, verbose_name="开始时间")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from rest_framework import serializers
|
||||
from .models import ESP32Config, Order, Salesperson, Service, VCCourse, ProductFeature, ServiceOrder, WeChatUser, Distributor, Withdrawal, CommissionLog, CourseEnrollment
|
||||
from .utils import get_current_wechat_user
|
||||
|
||||
class CommissionLogSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
@@ -174,6 +175,7 @@ class VCCourseSerializer(serializers.ModelSerializer):
|
||||
display_cover_image = serializers.SerializerMethodField()
|
||||
display_detail_image = serializers.SerializerMethodField()
|
||||
course_type_display = serializers.CharField(source='get_course_type_display', read_only=True)
|
||||
video_url = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = VCCourse
|
||||
@@ -193,6 +195,38 @@ class VCCourseSerializer(serializers.ModelSerializer):
|
||||
return obj.detail_image.url
|
||||
return None
|
||||
|
||||
def get_video_url(self, obj):
|
||||
"""
|
||||
仅当用户已付费/报名时返回视频URL
|
||||
"""
|
||||
if not obj.is_video_course:
|
||||
return None
|
||||
|
||||
request = self.context.get('request')
|
||||
if not request:
|
||||
return None
|
||||
|
||||
# 尝试获取当前用户
|
||||
user = get_current_wechat_user(request)
|
||||
if not user:
|
||||
return None
|
||||
|
||||
# 如果是管理员,直接返回
|
||||
if user.user and user.user.is_staff:
|
||||
return obj.video_url
|
||||
|
||||
# 检查是否已购买/报名 (通过已支付的订单)
|
||||
has_paid = Order.objects.filter(
|
||||
wechat_user=user,
|
||||
course=obj,
|
||||
status__in=['paid', 'shipped', 'completed'] # 包含所有已完成状态
|
||||
).exists()
|
||||
|
||||
if has_paid:
|
||||
return obj.video_url
|
||||
|
||||
return None
|
||||
|
||||
class ESP32ConfigSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
ESP32配置序列化器
|
||||
|
||||
@@ -1,11 +1,50 @@
|
||||
import requests
|
||||
from django.core.cache import cache
|
||||
from .models import WeChatPayConfig
|
||||
from django.core.signing import TimestampSigner, BadSignature, SignatureExpired
|
||||
from .models import WeChatPayConfig, WeChatUser
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def get_current_wechat_user(request):
|
||||
"""
|
||||
根据 Authorization 头获取当前微信用户
|
||||
增强逻辑:如果 Token 解析出的 OpenID 对应的用户不存在(可能已被合并删除),
|
||||
但该 OpenID 是 Web 虚拟 ID (web_phone),尝试通过手机号查找现有的主账号。
|
||||
"""
|
||||
auth_header = request.headers.get('Authorization')
|
||||
if not auth_header or not auth_header.startswith('Bearer '):
|
||||
return None
|
||||
token = auth_header.split(' ')[1]
|
||||
signer = TimestampSigner()
|
||||
try:
|
||||
# 签名包含 openid
|
||||
openid = signer.unsign(token, max_age=86400 * 30) # 30天有效
|
||||
user = WeChatUser.objects.filter(openid=openid).first()
|
||||
|
||||
if user:
|
||||
return user
|
||||
|
||||
# 如果没找到用户,检查是否是 Web 虚拟 OpenID
|
||||
# 场景:Web 用户已被合并到小程序账号,旧 Web Token 依然有效,指向合并后的账号
|
||||
if openid.startswith('web_'):
|
||||
try:
|
||||
# 格式: web_13800138000
|
||||
parts = openid.split('_', 1)
|
||||
if len(parts) == 2:
|
||||
phone = parts[1]
|
||||
# 尝试通过手机号查找(查找合并后的主账号)
|
||||
user = WeChatUser.objects.filter(phone_number=phone).first()
|
||||
if user:
|
||||
return user
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None
|
||||
except (BadSignature, SignatureExpired):
|
||||
return None
|
||||
|
||||
def get_access_token(config=None):
|
||||
"""
|
||||
获取微信接口调用凭证 (client_credential)
|
||||
|
||||
@@ -10,7 +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 .utils import get_access_token, get_current_wechat_user
|
||||
from .services import handle_post_payment
|
||||
from django.db import transaction, models
|
||||
from django.core.signing import TimestampSigner, BadSignature, SignatureExpired
|
||||
@@ -934,43 +934,7 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
|
||||
return Response({'status': 'success', 'message': '支付成功'})
|
||||
|
||||
def get_current_wechat_user(request):
|
||||
"""
|
||||
根据 Authorization 头获取当前微信用户
|
||||
增强逻辑:如果 Token 解析出的 OpenID 对应的用户不存在(可能已被合并删除),
|
||||
但该 OpenID 是 Web 虚拟 ID (web_phone),尝试通过手机号查找现有的主账号。
|
||||
"""
|
||||
auth_header = request.headers.get('Authorization')
|
||||
if not auth_header or not auth_header.startswith('Bearer '):
|
||||
return None
|
||||
token = auth_header.split(' ')[1]
|
||||
signer = TimestampSigner()
|
||||
try:
|
||||
# 签名包含 openid
|
||||
openid = signer.unsign(token, max_age=86400 * 30) # 30天有效
|
||||
user = WeChatUser.objects.filter(openid=openid).first()
|
||||
|
||||
if user:
|
||||
return user
|
||||
|
||||
# 如果没找到用户,检查是否是 Web 虚拟 OpenID
|
||||
# 场景:Web 用户已被合并到小程序账号,旧 Web Token 依然有效,指向合并后的账号
|
||||
if openid.startswith('web_'):
|
||||
try:
|
||||
# 格式: web_13800138000
|
||||
parts = openid.split('_', 1)
|
||||
if len(parts) == 2:
|
||||
phone = parts[1]
|
||||
# 尝试通过手机号查找(查找合并后的主账号)
|
||||
user = WeChatUser.objects.filter(phone_number=phone).first()
|
||||
if user:
|
||||
return user
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None
|
||||
except (BadSignature, SignatureExpired):
|
||||
return None
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary="微信小程序登录",
|
||||
|
||||
Reference in New Issue
Block a user