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

@ -33,3 +33,21 @@ class SecretLoginSerializer(serializers.Serializer):
class FaceLoginSerializer(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, from apps.auth1.views import (CodeLogin, LoginView, LogoutView, PwResetView,
SecretLogin, SendCode, TokenBlackView, WxLogin, WxmpLogin, TokenLoginView, FaceLoginView) 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/' API_BASE_URL = 'api/auth/'
urlpatterns = [ urlpatterns = [
@ -18,5 +20,7 @@ urlpatterns = [
path(API_BASE_URL + 'sms_code/', SendCode.as_view(), name='sms_code_send'), 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 + 'logout/', LogoutView.as_view(), name='session_logout'),
path(API_BASE_URL + 'reset_password/', PwResetView.as_view(), name='reset_password'), 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, 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 apps.system.models import User
from rest_framework_simplejwt.views import TokenObtainPairView from rest_framework_simplejwt.views import TokenObtainPairView
from apps.auth1.authentication import get_user_by_username_or from apps.auth1.authentication import get_user_by_username_or
@ -304,3 +304,64 @@ class FaceLoginView(CreateAPIView):
else: else:
raise ParseError('账户不存在或不可用') raise ParseError('账户不存在或不可用')
raise ParseError(msg) 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=["seg3"]),
models.Index(fields=["seg4"]), 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