import json from django.contrib.auth.password_validation import validate_password from django.db import transaction from rest_framework import serializers from .migration_service import migration_summary from .models import InviteCode, InviteCodeUsage, User, VisitorMigration class UserSerializer(serializers.ModelSerializer): class Meta: model = User fields = ( "id", "username", "nickname", "avatar_url", "bio", "track", "rating", "is_staff", ) read_only_fields = ("id", "rating", "is_staff") class RegisterSerializer(serializers.Serializer): invite_code = serializers.CharField(max_length=32) username = serializers.RegexField(r"^[A-Za-z0-9_]{3,30}$") password = serializers.CharField(write_only=True, min_length=8) nickname = serializers.CharField(max_length=40) track = serializers.ChoiceField(choices=User.Track.choices, default=User.Track.STANDARD) visitor_data = serializers.JSONField(required=False, default=dict, write_only=True) def validate_username(self, value): if User.objects.filter(username__iexact=value).exists(): raise serializers.ValidationError("用户名已存在") return value def validate_password(self, value): validate_password(value) return value def validate_visitor_data(self, value): if not isinstance(value, dict): raise serializers.ValidationError("游客数据必须是对象") for field in ("story_saves", "favorites", "cards"): if field in value and not isinstance(value[field], list): raise serializers.ValidationError(f"{field} 必须是数组") if "mathbti" in value and not isinstance(value["mathbti"], dict): raise serializers.ValidationError("mathbti 必须是对象") if len(json.dumps(value, ensure_ascii=False).encode("utf-8")) > 256 * 1024: raise serializers.ValidationError("游客数据不能超过 256KB") return value @transaction.atomic def create(self, validated_data): code_text = validated_data.pop("invite_code") visitor_data = validated_data.pop("visitor_data", {}) try: invite = InviteCode.objects.select_for_update().get(code__iexact=code_text) except InviteCode.DoesNotExist as exc: raise serializers.ValidationError({"invite_code": "邀请码无效"}) from exc if not invite.is_available: raise serializers.ValidationError({"invite_code": "邀请码已过期或已用完"}) user = User.objects.create_user(**validated_data) InviteCodeUsage.objects.create( invite_code=invite, user=user, ip_address=self.context.get("ip_address"), ) invite.used_count += 1 invite.save(update_fields=["used_count"]) if visitor_data: VisitorMigration.objects.create( user=user, payload=visitor_data, summary=migration_summary(visitor_data), ) return user