当前位置: 首页 > news >正文

Django 从 0 到 1 打造完整电商平台:用户注册与手机号/邮箱验证

作者:IT策士
系列:《Django 从 0 到 1 打造完整电商平台》第 6 篇
标签:Django, 用户认证, 短信验证码, 邮箱激活, 电商开发


前言

上一篇我们把静态文件、模板骨架全部搞定,项目已经可以呈现出漂亮的页面。从今天开始,我们正式踏入业务逻辑开发的第一站——用户注册

用户注册看似简单,但在电商项目里,它涉及表单验证、手机号唯一性校验、验证码发送、邮箱激活、密码加密存储等一大堆细节。今天我会带着大家一步一步写出完整的注册流程,并且让手机验证码和邮箱激活都跑通。


一、需求分析

我们的注册功能需要支持两种方式:

注册方式流程
手机号注册输入手机号 → 获取短信验证码 → 填写验证码 + 密码 → 完成注册
邮箱注册输入邮箱 + 密码 → 注册成功 → 发送激活邮件 → 点击链接激活账号

开发环境说明:由于没有真实短信通道,我们采用控制台模拟发送短信验证码(生产环境换成阿里云/腾讯云 SDK 即可)。邮件方面,Django 提供了console.EmailBackend,激活邮件会直接打印在终端里,非常方便调试。


二、配置邮件后端(开发环境)

django_ecommerce/settings.py中添加邮件配置:

# 开发环境:将邮件打印到控制台EMAIL_BACKEND='django.core.mail.backends.console.EmailBackend'# 生产环境才需要下面的真实 SMTP 配置,现在注释掉# EMAIL_HOST = 'smtp.example.com'# EMAIL_PORT = 587# EMAIL_HOST_USER = 'your_email@example.com'# EMAIL_HOST_PASSWORD = 'your_password'# EMAIL_USE_TLS = True# DEFAULT_FROM_EMAIL = '电商平台 <noreply@example.com>'

这样所有发出的邮件都会显示在runserver的终端输出中,注册后去终端复制激活链接即可。


三、编写注册表单

Django 的 Form 组件能帮我们处理前端数据校验。我们在apps/users/forms.py中创建自定义注册表单,支持手机号或邮箱两种方式:

from djangoimportforms from django.core.validatorsimportRegexValidator from .modelsimportUser class RegisterForm(forms.Form):# 手机号(可选,如果用邮箱注册则留空)phone=forms.CharField(max_length=11,min_length=11,required=False,validators=[RegexValidator(r'^1[3-9]\d{9}$',message='请输入有效的手机号')],widget=forms.TextInput(attrs={'class':'form-control','placeholder':'手机号(选填)'}))# 邮箱email=forms.EmailField(required=False,widget=forms.EmailInput(attrs={'class':'form-control','placeholder':'邮箱(选填)'}))# 密码password=forms.CharField(min_length=6,max_length=20,widget=forms.PasswordInput(attrs={'class':'form-control','placeholder':'密码(至少6位)'}))password2=forms.CharField(label='确认密码',widget=forms.PasswordInput(attrs={'class':'form-control','placeholder':'再次输入密码'}))# 手机验证码(如果用手机注册时必填)sms_code=forms.CharField(max_length=6,required=False,widget=forms.TextInput(attrs={'class':'form-control','placeholder':'短信验证码'}))def clean(self): cleaned_data=super().clean()phone=cleaned_data.get('phone')email=cleaned_data.get('email')# 必须提供手机号或邮箱之一ifnot phone and not email: raise forms.ValidationError('请至少填写手机号或邮箱')returncleaned_data def clean_phone(self): phone=self.cleaned_data.get('phone')ifphone and User.objects.filter(phone=phone).exists(): raise forms.ValidationError('该手机号已被注册')returnphone def clean_email(self): email=self.cleaned_data.get('email')ifemail and User.objects.filter(email=email).exists(): raise forms.ValidationError('该邮箱已被注册')returnemail def clean_password2(self):pwd=self.cleaned_data.get('password')pwd2=self.cleaned_data.get('password2')ifpwdand pwd2 andpwd!=pwd2: raise forms.ValidationError('两次密码不一致')returnpwd2

设计要点

  • 手机号和邮箱均设为required=False,但通过clean()方法确保至少填一个

  • 自定义手机号正则校验^1[3-9]\d{9}$匹配中国大陆手机号格式

  • 唯一性校验:在clean_phoneclean_email中检查是否已被注册

  • 密码一致性校验:在clean_password2中比对两次输入


四、编写注册视图

apps/users/views.py中实现注册逻辑:

importrandom from django.shortcutsimportrender, redirect from django.contribimportmessages from django.core.mailimportsend_mail from django.confimportsettings from django.httpimportJsonResponse from django.views.decorators.httpimportrequire_POST from .formsimportRegisterForm from .modelsimportUser def register(request):ifrequest.method=='POST':form=RegisterForm(request.POST)ifform.is_valid(): phone=form.cleaned_data.get('phone')email=form.cleaned_data.get('email')password=form.cleaned_data.get('password')# 验证手机验证码(如果使用手机注册)ifphone: sms_code_input=form.cleaned_data.get('sms_code')sms_code_session=request.session.get('sms_code')ifnot sms_code_session or sms_code_input!=sms_code_session: form.add_error('sms_code','验证码错误或已过期')returnrender(request,'users/register.html',{'form':form})# 清空 session 中的验证码request.session.pop('sms_code', None)# 创建用户user=User.objects.create_user(username=phone or email.split('@')[0],# 用手机号或邮箱前缀当用户名password=password,phone=phoneifphoneelseNone,email=emailifemailelseNone,)# 如果用邮箱注册,发送激活邮件ifemail:# 生成简单的 token(生产环境建议用 itsdangerous)token=str(random.randint(100000,999999))request.session[f'email_token_{user.id}']=token activate_url=request.build_absolute_uri(f'/users/activate/{user.id}/{token}/')send_mail(subject='激活你的电商账号',message=f'点击链接激活账号:{activate_url}',from_email='noreply@example.com',recipient_list=[email],)messages.success(request,'注册成功!激活邮件已发送,请前往邮箱查收(查看终端输出)。')else: messages.success(request,'注册成功!您现在可以登录了。')returnredirect('home')# 后续改为登录页else: form=RegisterForm()returnrender(request,'users/register.html',{'form':form})@require_POST def send_sms_code(request):"""发送短信验证码(模拟)""" phone=request.POST.get('phone')ifnot phone:returnJsonResponse({'ok':False,'msg':'手机号不能为空'},status=400)# 生成 6 位随机验证码code=str(random.randint(100000,999999))# 存入 sessionrequest.session['sms_code']=code request.session.set_expiry(300)# 5分钟有效# 控制台模拟发送print(f"\n{'='*40}")print(f"【模拟短信】验证码:{code},发送至手机号:{phone}")print(f"{'='*40}\n")returnJsonResponse({'ok':True,'msg':'验证码已发送'})

关键点说明

功能实现方式
验证码校验手机注册时,比对用户输入与request.session['sms_code'],通过后立即清空
用户创建使用User.objects.create_user(),自动处理密码加密;用户名取手机号或邮箱前缀
邮箱激活生成 6 位随机 token 存入 session,构建激活链接并通过send_mail()发送
短信模拟send_sms_code是独立 AJAX 视图,验证码存 session 并设置 5 分钟过期,同时在控制台打印

五、URL 路由配置

5.1 应用级路由:apps/users/urls.py

from django.urlsimportpath from.importviews app_name='users'urlpatterns=[path('register/', views.register,name='register'), path('send_sms/', views.send_sms_code,name='send_sms'),]

5.2 项目级路由:django_ecommerce/urls.py

from django.contribimportadmin from django.urlsimportpath, include from django.confimportsettings from django.conf.urls.staticimportstatic from django.views.genericimportTemplateView urlpatterns=[path('admin/', admin.site.urls), path('', TemplateView.as_view(template_name='home.html'),name='home'), path('users/', include('apps.users.urls')),# 注意路径前缀]ifsettings.DEBUG: urlpatterns+=static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)

六、注册页面模板

创建apps/users/templates/users/register.html

{% extends'base.html'%}{% block title %}用户注册{% endblock %}{% block content %}<divclass="row justify-content-center"><divclass="col-md-6 col-lg-5"><divclass="card shadow-sm"><divclass="card-body p-4"><h3class="text-center mb-4">📝 创建账号</h3><formmethod="post"novalidate>{% csrf_token %}<!-- 手机号 --><divclass="mb-3"><labelclass="form-label">手机号</label>{{form.phone}}{%ifform.phone.errors %}<divclass="text-danger small">{{form.phone.errors.0}}</div>{% endif %}</div><!-- 验证码(仅手机注册时显示) --><divclass="mb-3"id="sms-code-group"style="display:none;"><labelclass="form-label">验证码</label><divclass="input-group">{{form.sms_code}}<buttonclass="btn btn-outline-secondary"type="button"id="get-sms-btn">获取验证码</button></div>{%ifform.sms_code.errors %}<divclass="text-danger small">{{form.sms_code.errors.0}}</div>{% endif %}</div><!-- 邮箱 --><divclass="mb-3"><labelclass="form-label">邮箱</label>{{form.email}}{%ifform.email.errors %}<divclass="text-danger small">{{form.email.errors.0}}</div>{% endif %}</div><!-- 密码 --><divclass="mb-3"><labelclass="form-label">密码</label>{{form.password}}{%ifform.password.errors %}<divclass="text-danger small">{{form.password.errors.0}}</div>{% endif %}</div><divclass="mb-3"><labelclass="form-label">确认密码</label>{{form.password2}}{%ifform.password2.errors %}<divclass="text-danger small">{{form.password2.errors.0}}</div>{% endif %}</div><!-- 全局错误(如未填手机号或邮箱) -->{%ifform.non_field_errors %}<divclass="alert alert-danger">{{form.non_field_errors.0}}</div>{% endif %}<buttontype="submit"class="btn btn-primary w-100">注册</button></form><pclass="text-center mt-3">已有账号?<<ahref="#">立即登录</a></p></div></div></div></div>{% endblock %}{% block extra_js %}<script>const phoneInput=document.querySelector('#id_phone');const emailInput=document.querySelector('#id_email');const smsGroup=document.querySelector('#sms-code-group');const getSmsBtn=document.querySelector('#get-sms-btn');// 根据手机号输入框是否有内容来显示/隐藏验证码区域functiontoggleSmsGroup(){if(phoneInput.value.trim().length>0){smsGroup.style.display='block';}else{smsGroup.style.display='none';}}phoneInput.addEventListener('input', toggleSmsGroup);// 页面初始化 toggleSmsGroup();// 获取验证码 getSmsBtn.addEventListener('click',function(){const phone=phoneInput.value.trim();if(!phone){alert('请先输入手机号');return;}// 简单的前端倒计时letcountdown=60;getSmsBtn.disabled=true;getSmsBtn.textContent=countdown +'秒后重试';const timer=setInterval(()=>{ countdown--;getSmsBtn.textContent=countdown+'秒后重试';if(countdown<=0){ clearInterval(timer);getSmsBtn.disabled=false;getSmsBtn.textContent='获取验证码';} },1000);//发送请求 fetch('{%url "users:send_sms"%}',{ method:'POST',headers:{ 'X-CSRFToken':document.querySelector('[name=csrfmiddlewaretoken]').value,'Content-Type':'application/x-www-form-urlencoded',},body:'phone='+encodeURIComponent(phone)}).then(response=>response.json()).then(data=>{if(!data.ok){alert(data.msg);}});});</script>{% endblock %}

前端交互亮点

  • 动态显示验证码区域:监听手机号输入框,有内容时自动显示验证码输入框

  • 60 秒倒计时防重复点击:点击"获取验证码"后按钮禁用并倒计时

  • AJAX 发送验证码:使用fetch()发送 POST 请求,自动携带 CSRF Token


七、邮箱激活功能

为了完成完整的邮箱注册流程,我们再添加一个激活视图。

7.1 激活视图:apps/users/views.py

from django.httpimportHttp404 from django.shortcutsimportget_object_or_404 def activate_email(request, user_id, token): user=get_object_or_404(User,id=user_id)session_token=request.session.get(f'email_token_{user.id}')ifnot session_token or session_token!=token: raise Http404('激活链接无效或已过期')user.email_active=True user.save(update_fields=['email_active'])# 激活成功后清理 tokenrequest.session.pop(f'email_token_{user.id}', None)messages.success(request,'邮箱激活成功!现在可以登录了。')returnredirect('home')# 后续改为登录页

7.2 添加激活路由:apps/users/urls.py

path('activate/<int:user_id>/<str:token>/', views.activate_email,name='activate_email'),

八、测试完整流程

启动开发服务器:

python manage.py runserver

8.1 手机号注册测试

  1. 访问http://127.0.0.1:8000/users/register/

  2. 输入手机号13800138000,此时验证码输入框会出现

  3. 点击"获取验证码",查看终端输出:

[20/May/202614:35:22]"POST /users/send_sms/ HTTP/1.1"20027========================================【模拟短信】验证码:384729,发送至手机号:13800138000========================================
  1. 输入收到的验证码(如384729),设置密码,提交注册

  2. 注册成功,跳转到首页并显示提示

验证数据库:

SELECT id, username, phone, is_active FROM tb_users;

结果示例:

1|admin|13800138001|1← 超级管理员(之前创建)2|13800138000|13800138000|1← 刚注册的用户

8.2 邮箱注册测试

  1. 填写邮箱test@example.com,密码,确认密码,提交

  2. 终端会输出激活邮件内容:

Content-Type: text/plain;charset="utf-8"MIME-Version:1.0Content-Transfer-Encoding: 7bit Subject: 激活你的电商账号 From: noreply@example.com To: test@example.com Date: Wed,20May202614:40:00-0000Message-ID:<...>X-Mailer: Django Mailer 点击链接激活账号:http://127.0.0.1:8000/users/activate/3/123456/
  1. 复制终端中的链接(注意你的 token 和用户 ID 可能不同),用浏览器打开

  2. 提示"邮箱激活成功!",用户email_active变为True


九、当前注册的不足与改进方向

问题现状改进方案
手机验证码防刷没有对同一手机号频繁发送做限制后续使用 Redis 缓存记录发送频率
激活 Token 安全性使用 6 位随机数,易被暴力破解生产环境使用itsdangerous或 Django 自带的PasswordResetTokenGenerator生成有时效的签名 token
用户名冲突邮箱前缀可能重复后续在clean中增加唯一性校验,或改用 UUID 作为 username

这些优化会在后续篇(第 24~26 篇)中逐步完善。


十、总结与下集预告

今天我们实现了用户注册的核心功能,涵盖:

自定义注册表单,支持手机号或邮箱双通道注册
模拟短信验证码的发送与验证,使用 session 存储验证码
邮箱注册的激活流程,通过控制台邮件后端完成激活
前端动态显示验证码输入区域,AJAX 请求发送验证码

注册搞定了,登录自然是下一步。第 7 篇,我将带大家实现登录与登出功能,包括 Django 内置认证系统的使用、登录装饰器、记住我功能,以及登录后导航栏的状态变化。


📢想了解更多?去公众号、今日头条搜索「IT策士」,一起升级 IT 思维!
本文为《Django 从 0 到 1 打造完整电商平台》系列第 6 篇,作者:IT策士,未经授权禁止转载。


http://www.jsqmd.com/news/854328/

相关文章:

  • 哪个工具可以降知网ai率?2026年降AI率测评:比话降知网ai率效果最佳? - 我要发一区
  • 【2026】ISCC 数字古墓
  • 小孩玩的烟花排行榜
  • 通达信缠论可视化插件终极指南:5步实现专业级技术分析
  • 东台市自动化设备外壳厂家实力排行:口碑与硬实力对标 - 奔跑123
  • PICO-RAP4微控制器开发板:从硬件设计到物联网项目实战全解析
  • 东台市储能电池箱厂家实力排行 硬核资质与实绩对比 - 奔跑123
  • 极简TextCNN,五分钟看懂文本分类基线算法
  • RK3506 SPI从设备开发全攻略:从硬件设计到Linux驱动实战
  • 2026年AI论文软件盘点:12款神器助你高效完成学术写作、润色和降重
  • CS5466芯片设计实战:实现Type-C转HDMI 2.1的8K/144Hz高规格视频扩展
  • 手把手教你接入滴图地图 API:10 分钟跑通第一个 Demo
  • 认知智能模型:AI从“说话”到“思考”的跃迁 ——意图共鸣的品牌理念
  • 频率精度标准全解析:从定义、测量到系统设计实践
  • 2026乐清洗脚放松去哪里?乐清“铁招牌“十多年口碑养成记
  • 终极指南:使用wxappUnpacker深度解析微信小程序架构
  • AWorksOS:下一代嵌入式开发平台如何实现软硬件解耦与高效复用
  • RK3506三核A7架构解析:从实时控制到边缘计算的嵌入式设计新范式
  • ComfyUI-Impact-Pack V8:专业级图像增强与语义分割的终极指南
  • 2026 年 RSA 大会:多家初创公司填补 AI 打破安全边界后的空白
  • LPC55S6x MCU实战:异构架构、DSP加速与低功耗设计解析
  • 全志V3506-S12开发板评测:88元RISC-V AIoT开发板实战指南
  • 锁相环(PLL)核心原理、模块拆解与实战选型指南
  • 慢病精准管控,筑牢老人健康防线
  • Windows 11 LTSC系统完整添加微软商店的实用指南
  • 为金融 Agent 设计 Harness 异常交易模式实时阻断
  • 避开PostgreSQL逻辑复制的那些坑:从复制标识(Replica Identity)配置到性能调优指南
  • MemGPT 论文深度解读:突破 LLM 上下文窗口限制的层级记忆管理
  • 人文交互,让技术回归人本的温度:意图共鸣科技
  • LabVIEW图形化编程入门:从核心概念到数据采集实战