import re from django.core.cache import cache from django.conf import settings from rest_framework.decorators import api_view, permission_classes, throttle_classes from rest_framework.permissions import AllowAny from rest_framework.response import Response from rest_framework import serializers as drf_serializers from drf_spectacular.utils import extend_schema, inline_serializer from config.exceptions import AppError from apps.user.models import User from apps.user.throttling import SmsPhoneMinuteThrottle, SmsPhoneDayThrottle, SmsIpThrottle from apps.user.utils.sms import generate_sms_code, get_sms_service, SmsError @extend_schema( summary='U1 发送短信验证码', request=inline_serializer('SendCodeRequest', fields={ 'phone': drf_serializers.CharField(help_text='手机号'), 'scene': drf_serializers.ChoiceField(choices=['register', 'login', 'reset'], help_text='场景:register/login/reset'), }), responses={200: inline_serializer('SendCodeResponse', fields={ 'message': drf_serializers.CharField(), })}, tags=['认证'], ) @api_view(['POST']) @permission_classes([AllowAny]) @throttle_classes([SmsPhoneMinuteThrottle, SmsPhoneDayThrottle, SmsIpThrottle]) def send_code(request): """U1 发送短信验证码""" data = request.data phone = data.get('phone', '') scene = data.get('scene', '') if not re.match(r'^1[3-9]\d{9}$', str(phone)): raise AppError('SMS_INVALID_PHONE', '手机号格式不合法') if scene not in ('register', 'login', 'reset'): raise AppError('SMS_INVALID_SCENE', 'scene 参数无效,仅允许 register / login / reset') user_exists = User.objects.filter(phone=phone).exists() if scene == 'register' and user_exists: raise AppError('AUTH_PHONE_REGISTERED', '该手机号已注册') if scene in ('login', 'reset') and not user_exists: raise AppError('AUTH_PHONE_NOT_FOUND', '手机号未注册') code = generate_sms_code() cache_key = f'sms:{scene}:{phone}' cache.set(cache_key, code, timeout=settings.SMS_CODE_EXPIRE) try: get_sms_service().send_code(phone, scene, code) except SmsError as e: cache.delete(cache_key) err_code = str(e) if str(e) in ('SMS_BIZ_ERROR',) else 'SMS_PROVIDER_ERROR' raise AppError(err_code, '短信发送失败,请稍后重试', status_code=500) return Response({'message': '验证码已发送'})