下面给出两个最常用、最安全的实现示例(Django ≥ 3.2,SMTP 用 QQ/163/Gmail 均可)。
目录结构约定:
myproject/
├── myproject/
│ ├── settings.py
│ └── urls.py
├── users/ # 新建 app
│ ├── models.py
│ ├── serializers.py
│ ├── views.py
│ ├── urls.py
│ └── utils.py
└── manage.py一、settings.py 里先把邮箱后端配好
python
# myproject/settings.py
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.qq.com' # 或 smtp.163.com / smtp.gmail.com
EMAIL_PORT = 465 # SSL 端口
EMAIL_USE_SSL = True # 465 用 SSL;587 用 TLS
EMAIL_HOST_USER = 'your_qq@qq.com'
EMAIL_HOST_PASSWORD = '授权码' # 不是登录密码!
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
# 验证码有效期(秒)
CODE_EXPIRE = 300二、users/models.py —— 只存验证码(不存密码)
python
from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model()
class EmailVerifyCode(models.Model):
email = models.EmailField()
code = models.CharField(max_length=6)
created = models.DateTimeField(auto_now_add=True)
class Meta:
indexes = [models.Index(fields=['email', '-created'])]三、users/utils.py —— 生成 6 位随机码 + 发送函数
python
import random
from django.core.mail import send_mail
from django.conf import settings
from django.utils import timezone
from datetime import timedelta
from .models import EmailVerifyCode
def generate_code():
return ''.join(random.choices('0123456789', k=6))
def send_mail_code(email):
code = generate_code()
EmailVerifyCode.objects.create(email=email, code=code)
subject = '登录验证码'
message = f'您的验证码是:{code},{settings.CODE_EXPIRE // 60} 分钟内有效。'
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [email])四、users/serializers.py
python
from rest_framework import serializers
from django.contrib.auth import get_user_model
from .models import EmailVerifyCode
from .utils import send_mail_code
from django.utils import timezone
from datetime import timedelta
User = get_user_model()
class SendCodeSerializer(serializers.Serializer):
email = serializers.EmailField()
def validate_email(self, value):
# 可在此处限制注册或未注册邮箱
return value
def save(self):
send_mail_code(self.validated_data['email'])
class LoginByCodeSerializer(serializers.Serializer):
email = serializers.EmailField()
code = serializers.CharField(min_length=6, max_length=6)
def validate(self, attrs):
email = attrs['email']
code = attrs['code']
qs = EmailVerifyCode.objects.filter(
email=email, code=code,
created__gte=timezone.now() - timedelta(seconds=settings.CODE_EXPIRE)
)
if not qs.exists():
raise serializers.ValidationError('验证码错误或已过期')
return attrs
def save(self):
email = self.validated_data['email']
user, _ = User.objects.get_or_create(username=email, defaults={'email': email})
# 清理用过的验证码
EmailVerifyCode.objects.filter(email=email).delete()
from rest_framework_simplejwt.tokens import RefreshToken
refresh = RefreshToken.for_user(user)
return {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
class CustomMailSerializer(serializers.Serializer):
to = serializers.EmailField()
subject = serializers.CharField(max_length=100)
body = serializers.CharField()五、users/views.py —— 两个 CBV
python
from rest_framework import generics, status
from rest_framework.response import Response
from .serializers import (
SendCodeSerializer,
LoginByCodeSerializer,
CustomMailSerializer,
)
from django.core.mail import send_mail
from django.conf import settings
class SendCodeView(generics.GenericAPIView):
serializer_class = SendCodeSerializer
def post(self, request, *args, **kwargs):
ser = self.get_serializer(data=request.data)
ser.is_valid(raise_exception=True)
ser.save()
return Response({'detail': '验证码已发送,请查收'})
class LoginByCodeView(generics.GenericAPIView):
serializer_class = LoginByCodeSerializer
def post(self, request, *args, **kwargs):
ser = self.get_serializer(data=request.data)
ser.is_valid(raise_exception=True)
tokens = ser.save()
return Response(tokens)
class SendCustomMailView(generics.GenericAPIView):
serializer_class = CustomMailSerializer
def post(self, request, *args, **kwargs):
ser = self.get_serializer(data=request.data)
ser.is_valid(raise_exception=True)
data = ser.validated_data
send_mail(
subject=data['subject'],
message=data['body'],
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[data['to']],
fail_silently=False,
)
return Response({'detail': '邮件已发送'})六、users/urls.py
python
from django.urls import path
from .views import SendCodeView, LoginByCodeView, SendCustomMailView
urlpatterns = [
path('send_code/', SendCodeView.as_view(), name='send_code'),
path('login_by_code/', LoginByCodeView.as_view(), name='login_by_code'),
path('send_mail/', SendCustomMailView.as_view(), name='send_custom_mail'),
]七、myproject/urls.py 里 include
python
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('modules.users.urls')),
]八、requirements.txt(最小集)
Django>=4.2
djangorestframework
djangorestframework-simplejwt九、迁移 & 运行
bash
python manage.py makemigrations
python manage.py migrate
python manage.py runserver十、接口测试(curl / Postman)
- 获取验证码
shell
curl -X POST http://127.0.0.1:8000/api/send_code/ \
-H "Content-Type: application/json" \
-d '{"email":"test@qq.com"}'- 验证码登录
shell
curl -X POST http://127.0.0.1:8000/api/login_by_code/ \
-H "Content-Type: application/json" \
-d '{"email":"test@qq.com","code":"123456"}'返回:{"refresh":"xxx","access":"yyy"}
- 任意自定义邮件
shell
curl -X POST http://127.0.0.1:8000/api/send_mail/ \
-H "Content-Type: application/json" \
-d '{
"to": "friend@example.com",
"subject": "生日快乐",
"body": "祝你生日快乐!愿每天都开心~"
}'至此,两个需求全部完成,可直接并入正式项目。祝编码愉快!