feat: 添加邮箱验证码登录功能
This commit is contained in:
parent
32dcbf97a5
commit
dbdc0086d2
|
|
@ -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': '验证码已发送'})
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -32,4 +32,22 @@ 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)
|
||||||
|
|
@ -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')
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -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('账户不存在或已禁用')
|
||||||
|
|
|
||||||
|
|
@ -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': '企业信息',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue