from rest_framework import viewsets, status, mixins, parsers from rest_framework.decorators import action from rest_framework.response import Response from rest_framework import serializers from django.core.signing import TimestampSigner, BadSignature, SignatureExpired from drf_spectacular.utils import extend_schema from shop.models import WeChatUser from .models import Activity, ActivitySignup, Topic, Reply, TopicMedia from .serializers import ActivitySerializer, ActivitySignupSerializer, TopicSerializer, ReplySerializer, TopicMediaSerializer def get_current_wechat_user(request): """ 根据 Authorization 头获取当前微信用户 (复用 shop app 的逻辑) """ 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天有效 return WeChatUser.objects.filter(openid=openid).first() except (BadSignature, SignatureExpired): return None class ActivityViewSet(viewsets.ReadOnlyModelViewSet): """ 社区活动接口 """ queryset = Activity.objects.filter(is_active=True).order_by('-created_at') serializer_class = ActivitySerializer @extend_schema(summary="报名活动") @action(detail=True, methods=['post']) def signup(self, request, pk=None): user = get_current_wechat_user(request) if not user: return Response({'error': '请先登录'}, status=401) activity = self.get_object() # Check if already signed up if ActivitySignup.objects.filter(activity=activity, user=user).exists(): return Response({'error': '您已报名该活动'}, status=400) if activity.signups.count() >= activity.max_participants: return Response({'error': '活动名额已满'}, status=400) signup = ActivitySignup.objects.create(activity=activity, user=user) serializer = ActivitySignupSerializer(signup) return Response(serializer.data, status=201) @extend_schema(summary="我的报名记录") @action(detail=False, methods=['get']) def my_signups(self, request): user = get_current_wechat_user(request) if not user: return Response({'error': '请先登录'}, status=401) signups = ActivitySignup.objects.filter(user=user).order_by('-signup_time') serializer = ActivitySignupSerializer(signups, many=True) return Response(serializer.data) class TopicViewSet(viewsets.ModelViewSet): """ 技术论坛帖子接口 """ queryset = Topic.objects.all() serializer_class = TopicSerializer def perform_create(self, serializer): user = get_current_wechat_user(self.request) # Auth check is done in create or permission, but here we need user for save if user: serializer.save(author=user) def create(self, request, *args, **kwargs): user = get_current_wechat_user(request) if not user: return Response({'error': '请先登录'}, status=401) return super().create(request, *args, **kwargs) class ReplyViewSet(viewsets.ModelViewSet): """ 帖子回复接口 """ queryset = Reply.objects.all() serializer_class = ReplySerializer def perform_create(self, serializer): user = get_current_wechat_user(self.request) if user: serializer.save(author=user) def create(self, request, *args, **kwargs): user = get_current_wechat_user(request) if not user: return Response({'error': '请先登录'}, status=401) return super().create(request, *args, **kwargs) class TopicMediaViewSet(viewsets.GenericViewSet, mixins.CreateModelMixin): """ 论坛多媒体资源上传接口 """ queryset = TopicMedia.objects.all() serializer_class = TopicMediaSerializer parser_classes = [parsers.MultiPartParser, parsers.FormParser] @extend_schema(summary="上传媒体文件 (返回URL用于Markdown)") def create(self, request, *args, **kwargs): user = get_current_wechat_user(request) if not user: return Response({'error': '请先登录'}, status=401) # 允许上传时不关联 Topic (发帖前上传),或后续关联 # 主要是返回 url return super().create(request, *args, **kwargs)