Files
chunyu_project/user/views/email.py
T
2026-08-05 23:59:15 +08:00

170 lines
6.4 KiB
Python

from rest_framework import status
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
import logging
from chunyu_project.common_schemas import success_response, error_response, unauthorized_response, not_found_response
from utils.response_codes import (
ResponseCode,
create_standardized_response,
create_standardized_error_response
)
from utils.email_utils import validate_email_mx
from ..serializers.user_serializers import (
SendEmailCodeSerializer,
ChangeEmailSerializer,
UserSerializer,
)
logger = logging.getLogger(__name__)
class SendChangeEmailCodeAPIView(APIView):
permission_classes = [IsAuthenticated]
@swagger_auto_schema(
tags=['用户'],
operation_summary='发送修改邮箱验证码',
operation_description='向新邮箱发送验证码用于修改邮箱',
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'email': openapi.Schema(type=openapi.TYPE_STRING, description='新邮箱地址'),
'captcha_key': openapi.Schema(type=openapi.TYPE_STRING, description='图形验证码 key'),
'captcha_code': openapi.Schema(type=openapi.TYPE_STRING, description='图形验证码'),
},
),
responses={200: success_response, 400: error_response, 401: unauthorized_response, 500: error_response},
)
def post(self, request):
from utils.captcha import check_captcha_required, verify_captcha, record_failure, reset_failures
identifier = str(request.user.id)
operation = 'change_email'
captcha_required = check_captcha_required(operation, identifier)
if captcha_required:
captcha_key = request.data.get('captcha_key', None)
captcha_code = request.data.get('captcha_code', None)
if not captcha_key or not captcha_code:
return create_standardized_error_response(
code=ResponseCode.CAPTCHA_REQUIRED,
status_code=status.HTTP_400_BAD_REQUEST
)
captcha_result = verify_captcha(captcha_key, captcha_code)
if captcha_result == 'expired':
return create_standardized_error_response(
code=ResponseCode.CAPTCHA_EXPIRED,
status_code=status.HTTP_400_BAD_REQUEST
)
elif captcha_result == 'wrong':
record_failure(operation, identifier)
return create_standardized_error_response(
code=ResponseCode.CAPTCHA_ERROR,
status_code=status.HTTP_400_BAD_REQUEST
)
serializer = SendEmailCodeSerializer(
data=request.data,
context={'request': request}
)
if not serializer.is_valid():
errors = serializer.errors
first_error = ''
for field, msgs in errors.items():
if isinstance(msgs, list) and msgs:
first_error = str(msgs[0])
break
record_failure(operation, identifier)
return create_standardized_error_response(
data=errors,
code=ResponseCode.PARAMETER_ERROR,
message=first_error or '参数异常',
status_code=status.HTTP_400_BAD_REQUEST
)
email = serializer.validated_data.get('email')
if email and not validate_email_mx(email):
logger.warning(f'[ChangeEmail] Domain MX check failed: email={email}')
record_failure(operation, identifier)
return create_standardized_error_response(
code=ResponseCode.EMAIL_DOMAIN_INVALID,
status_code=status.HTTP_400_BAD_REQUEST
)
try:
email = serializer.save()
reset_failures(operation, identifier)
return create_standardized_response(
data={'email_sent': True},
code=ResponseCode.EMAIL_CHANGE_CODE_SENT,
status_code=status.HTTP_200_OK
)
except Exception as e:
record_failure(operation, identifier)
return create_standardized_error_response(
message=f'邮件发送失败: {str(e)}',
code=ResponseCode.EMAIL_SEND_FAILED,
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
)
class ChangeEmailAPIView(APIView):
permission_classes = [IsAuthenticated]
@swagger_auto_schema(
tags=['用户'],
operation_summary='修改邮箱',
operation_description='使用邮箱验证码修改用户邮箱',
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'email': openapi.Schema(type=openapi.TYPE_STRING, description='新邮箱地址'),
'email_code': openapi.Schema(type=openapi.TYPE_STRING, description='邮箱验证码'),
},
),
responses={200: success_response, 400: error_response, 401: unauthorized_response, 500: error_response},
)
def post(self, request):
serializer = ChangeEmailSerializer(
data=request.data,
context={'request': request}
)
if not serializer.is_valid():
errors = serializer.errors
first_error = ''
for field, msgs in errors.items():
if isinstance(msgs, list) and msgs:
first_error = str(msgs[0])
break
return create_standardized_error_response(
data=errors,
code=ResponseCode.PARAMETER_ERROR,
message=first_error or '参数异常',
status_code=status.HTTP_400_BAD_REQUEST
)
try:
updated_user = serializer.save()
user_serializer = UserSerializer(updated_user)
return create_standardized_response(
data={'user': user_serializer.data},
code=ResponseCode.EMAIL_CHANGED,
status_code=status.HTTP_200_OK
)
except Exception as e:
return create_standardized_error_response(
message=f'邮箱修改失败: {str(e)}',
code=ResponseCode.SERVER_INTERNAL_ERROR,
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
)