feat: 添加邮箱验证码登录功能

This commit is contained in:
caoqianming 2026-03-09 10:57:59 +08:00
parent 32dcbf97a5
commit dbdc0086d2
7 changed files with 290 additions and 3 deletions

View File

@ -0,0 +1,41 @@
from rest_framework.generics import CreateAPIView
from rest_framework.response import Response
from rest_framework.exceptions import ParseError
from apps.auth1.serializers import EmailCodeSerializer
from django.core.cache import cache
from apps.utils.tools import rannum
class EmailCodeView(CreateAPIView):
"""发送邮箱验证码
发送邮箱验证码用于注册
"""
authentication_classes = []
permission_classes = []
serializer_class = EmailCodeSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data
email = vdata.get('email')
cache_key = f'email_code_{email}'
# 生成6位验证码
code = rannum(6)
print(code)
# 缓存验证码有效期5分钟
cache.set(cache_key, code, 60 * 5)
# 使用 send_mail_task 发送验证码邮件
from apps.utils.tasks import send_mail_task
send_mail_task.delay(
subject='登录验证码',
message=f'您的验证码是:{code}5分钟内有效。如非本人操作请忽略此邮件。',
recipient_list=[email]
)
return Response({'message': '验证码已发送'})

View File

@ -0,0 +1,97 @@
from rest_framework.generics import CreateAPIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.exceptions import ParseError
from apps.auth1.serializers import RegisterSerializer
from apps.system.models import User
from django.db import transaction
class RegisterView(CreateAPIView):
"""用户注册
用户注册创建用户和企业信息
"""
authentication_classes = []
permission_classes = []
serializer_class = RegisterSerializer
@transaction.atomic
def create(self, request, *args, **kwargs):
from apps.carbon.models import Enterprise
import re
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data
email = vdata.get('email')
code = vdata.get('code')
nickname = vdata.get('nickname')
password = vdata.get('password')
enterprise_name = vdata.get('enterprise_name')
credit_code = vdata.get('credit_code')
# 验证邮箱验证码
from django.core.cache import cache
cache_key = f'email_code_{email}'
cached_code = cache.get(cache_key)
if not cached_code or cached_code != code:
raise ParseError('验证码错误或已过期')
# 验证统一社会信用代码格式
credit_code_pattern = r'^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$'
if not re.match(credit_code_pattern, credit_code):
raise ParseError('统一社会信用代码格式不正确')
# 检查邮箱是否已存在
if User.objects.filter(username=email).exists():
raise ParseError('该邮箱已注册')
# 检查统一社会信用代码是否已存在
if Enterprise.objects.filter(credit_code=credit_code).exists():
raise ParseError('该统一社会信用代码已注册')
# 创建企业部门
from apps.system.models import Dept
# 获取根组织部门
parent_dept = Dept.objects.get(name='根组织')
dept = Dept.objects.create(
name=enterprise_name,
type='enterprise',
parent=parent_dept
)
# 创建企业信息
enterprise = Enterprise.objects.create(
name=enterprise_name,
credit_code=credit_code,
belong_dept=dept
)
# 创建用户
user = User.objects.create_user(
username=email,
password=password,
type='enterprise',
name=nickname
)
# 授予权限
from apps.system.models import UserPost, Post
post = Post.objects.get(name='核算员')
UserPost.objects.create(
user = user,
post = post,
dept = dept,
sort = 1
)
user.belong_dept = dept
user.post = post
user.save(update_fields=['belong_dept', 'post'])
return Response({
'message': '注册成功',
'user_id': user.id,
'enterprise_id': enterprise.id
}, status=status.HTTP_201_CREATED)

View File

@ -32,4 +32,22 @@ class SecretLoginSerializer(serializers.Serializer):
class FaceLoginSerializer(serializers.Serializer):
base64 = serializers.CharField()
base64 = serializers.CharField()
class EmailCodeSerializer(serializers.Serializer):
email = serializers.EmailField(label="邮箱")
class EmailCodeLoginSerializer(serializers.Serializer):
email = serializers.EmailField(label="邮箱")
code = serializers.CharField(label="验证码")
class RegisterSerializer(serializers.Serializer):
email = serializers.EmailField(label="邮箱")
code = serializers.CharField(label="验证码", max_length=6)
nickname = serializers.CharField(label="昵称", max_length=20)
password = serializers.CharField(label="密码", min_length=6, max_length=50)
enterprise_name = serializers.CharField(label="企业名称", max_length=200)
credit_code = serializers.CharField(label="统一社会信用代码", max_length=18)

View File

@ -4,6 +4,8 @@ from rest_framework_simplejwt.views import TokenRefreshView
from apps.auth1.views import (CodeLogin, LoginView, LogoutView, PwResetView,
SecretLogin, SendCode, TokenBlackView, WxLogin, WxmpLogin, TokenLoginView, FaceLoginView)
from apps.auth1.register_view import RegisterView
from apps.auth1.email_code_view import EmailCodeView
API_BASE_URL = 'api/auth/'
urlpatterns = [
@ -18,5 +20,7 @@ urlpatterns = [
path(API_BASE_URL + 'sms_code/', SendCode.as_view(), name='sms_code_send'),
path(API_BASE_URL + 'logout/', LogoutView.as_view(), name='session_logout'),
path(API_BASE_URL + 'reset_password/', PwResetView.as_view(), name='reset_password'),
path(API_BASE_URL + 'login_face/', FaceLoginView.as_view(), name='face_login')
path(API_BASE_URL + 'login_face/', FaceLoginView.as_view(), name='face_login'),
path(API_BASE_URL + 'email_code/', EmailCodeView.as_view(), name='email_code'),
path(API_BASE_URL + 'register/', RegisterView.as_view(), name='register')
]

View File

@ -23,7 +23,7 @@ from apps.auth1.serializers import FaceLoginSerializer
from apps.auth1.serializers import (CodeLoginSerializer, LoginSerializer,
PwResetSerializer, SecretLoginSerializer, SendCodeSerializer, WxCodeSerializer)
PwResetSerializer, SecretLoginSerializer, SendCodeSerializer, WxCodeSerializer, RegisterSerializer, EmailCodeSerializer, EmailCodeLoginSerializer)
from apps.system.models import User
from rest_framework_simplejwt.views import TokenObtainPairView
from apps.auth1.authentication import get_user_by_username_or
@ -304,3 +304,64 @@ class FaceLoginView(CreateAPIView):
else:
raise ParseError('账户不存在或不可用')
raise ParseError(msg)
class EmailCodeView(CreateAPIView):
"""发送邮箱验证码
发送邮箱验证码
"""
authentication_classes = []
permission_classes = []
serializer_class = EmailCodeSerializer
def post(self, request):
"""邮箱验证码发送
邮箱验证码发送
"""
from apps.utils.tasks import send_mail_task
email = request.data['email']
code = rannum(6)
# 缓存验证码有效期5分钟
cache_key = f'email_code_{email}'
cache.set(cache_key, code, 60 * 5)
# 使用 send_mail_task 发送验证码邮件
send_mail_task.delay(
subject='登录验证码',
message=f'您的验证码是:{code}5分钟内有效。如非本人操作请忽略此邮件。',
recipient_list=[email]
)
return Response({'message': '验证码已发送'})
class EmailCodeLogin(CreateAPIView):
"""邮箱验证码登录
邮箱验证码登录
"""
authentication_classes = []
permission_classes = []
serializer_class = EmailCodeLoginSerializer
def post(self, request):
email = request.data['email']
code = request.data['code']
# 验证邮箱验证码
cache_key = f'email_code_{email}'
cached_code = cache.get(cache_key)
if not cached_code or cached_code != code:
raise ParseError('验证码错误或已过期')
# 查找用户
user = User.objects.filter(username=email).first()
if user:
ret = get_tokens_for_user(user)
# 删除已使用的验证码
cache.delete(cache_key)
return Response(ret)
raise ParseError('账户不存在或已禁用')

View File

@ -0,0 +1,46 @@
# Generated by Django 4.2.27 on 2026-03-09 01:29
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('system', '0007_alter_dept_create_by_alter_dept_update_by_and_more'),
('carbon', '0002_fingerprint'),
]
operations = [
migrations.CreateModel(
name='Enterprise',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=200, verbose_name='企业名称')),
('credit_code', models.CharField(max_length=18, unique=True, verbose_name='统一社会信用代码')),
('legal_representative', models.CharField(blank=True, max_length=50, null=True, verbose_name='法定代表人')),
('registered_capital', models.DecimalField(blank=True, decimal_places=2, max_digits=20, null=True, verbose_name='注册资本')),
('establishment_date', models.DateField(blank=True, null=True, verbose_name='成立日期')),
('business_scope', models.TextField(blank=True, null=True, verbose_name='经营范围')),
('address', models.CharField(blank=True, max_length=500, null=True, verbose_name='注册地址')),
('contact_phone', models.CharField(blank=True, max_length=20, null=True, verbose_name='联系电话')),
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='电子邮箱')),
('industry', models.CharField(blank=True, max_length=100, null=True, verbose_name='所属行业')),
('business_status', models.CharField(blank=True, max_length=50, null=True, verbose_name='经营状态')),
('registration_authority', models.CharField(blank=True, max_length=200, null=True, verbose_name='登记机关')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_belong_dept', to='system.dept', verbose_name='所属部门')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'verbose_name': '企业信息',
'verbose_name_plural': '企业信息',
},
),
]

View File

@ -52,3 +52,23 @@ class Fingerprint(BaseModel):
models.Index(fields=["seg3"]),
models.Index(fields=["seg4"]),
]
class Enterprise(CommonBDModel):
"""企业信息表"""
name = models.CharField("企业名称", max_length=200)
credit_code = models.CharField("统一社会信用代码", max_length=18, unique=True)
legal_representative = models.CharField("法定代表人", max_length=50, blank=True, null=True)
registered_capital = models.DecimalField("注册资本", max_digits=20, decimal_places=2, blank=True, null=True)
establishment_date = models.DateField("成立日期", blank=True, null=True)
business_scope = models.TextField("经营范围", blank=True, null=True)
address = models.CharField("注册地址", max_length=500, blank=True, null=True)
contact_phone = models.CharField("联系电话", max_length=20, blank=True, null=True)
email = models.EmailField("电子邮箱", blank=True, null=True)
industry = models.CharField("所属行业", max_length=100, blank=True, null=True)
business_status = models.CharField("经营状态", max_length=50, blank=True, null=True)
registration_authority = models.CharField("登记机关", max_length=200, blank=True, null=True)
class Meta:
verbose_name = "企业信息"
verbose_name_plural = verbose_name