From dbdc0086d23c0e9aae147c2a80bdd0378feaf45b Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 9 Mar 2026 10:57:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=82=AE=E7=AE=B1?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E7=A0=81=E7=99=BB=E5=BD=95=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/auth1/email_code_view.py | 41 ++++++++++ apps/auth1/register_view.py | 97 +++++++++++++++++++++++ apps/auth1/serializers.py | 20 ++++- apps/auth1/urls.py | 6 +- apps/auth1/views.py | 63 ++++++++++++++- apps/carbon/migrations/0003_enterprise.py | 46 +++++++++++ apps/carbon/models.py | 20 +++++ 7 files changed, 290 insertions(+), 3 deletions(-) create mode 100644 apps/auth1/email_code_view.py create mode 100644 apps/auth1/register_view.py create mode 100644 apps/carbon/migrations/0003_enterprise.py diff --git a/apps/auth1/email_code_view.py b/apps/auth1/email_code_view.py new file mode 100644 index 0000000..54cf9bf --- /dev/null +++ b/apps/auth1/email_code_view.py @@ -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': '验证码已发送'}) diff --git a/apps/auth1/register_view.py b/apps/auth1/register_view.py new file mode 100644 index 0000000..8c74ef1 --- /dev/null +++ b/apps/auth1/register_view.py @@ -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) diff --git a/apps/auth1/serializers.py b/apps/auth1/serializers.py index 2732bf2..5c1e44a 100755 --- a/apps/auth1/serializers.py +++ b/apps/auth1/serializers.py @@ -32,4 +32,22 @@ class SecretLoginSerializer(serializers.Serializer): class FaceLoginSerializer(serializers.Serializer): - base64 = serializers.CharField() \ No newline at end of file + 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) \ No newline at end of file diff --git a/apps/auth1/urls.py b/apps/auth1/urls.py index e2244f9..8402f29 100755 --- a/apps/auth1/urls.py +++ b/apps/auth1/urls.py @@ -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') ] diff --git a/apps/auth1/views.py b/apps/auth1/views.py index b5c858e..344709e 100755 --- a/apps/auth1/views.py +++ b/apps/auth1/views.py @@ -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('账户不存在或已禁用') diff --git a/apps/carbon/migrations/0003_enterprise.py b/apps/carbon/migrations/0003_enterprise.py new file mode 100644 index 0000000..4a2e23b --- /dev/null +++ b/apps/carbon/migrations/0003_enterprise.py @@ -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': '企业信息', + }, + ), + ] diff --git a/apps/carbon/models.py b/apps/carbon/models.py index 4c413b4..3976343 100644 --- a/apps/carbon/models.py +++ b/apps/carbon/models.py @@ -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