Django 从 0 到 1 打造完整电商平台:登录与登出功能实现


作者: IT策士

10余年一线大厂经验,专注 IT 思维、架构、职场进阶。持续发布最新文章,助你少走弯路。


前言

上一篇我们完成了用户注册功能,数据库里已经积累了一批注册用户。但一个完整的身份认证体系,光有注册是不够的------用户还需要能够登录登出

今天这篇文章,我们将基于 Django 内置的认证系统(django.contrib.auth),通过自定义认证后端实现多方式登录(手机号/邮箱/用户名),同时集成"记住我"、登录后跳转、导航栏状态切换等实用功能。

跟着我的步骤,半小时内让你的项目真正"认人"。


一、需求分析

我们要实现的登录功能清单:

功能点 说明
多方式登录 支持手机号、邮箱、用户名任一方式登录
错误提示 密码错误或用户不存在时给出明确提示
记住我 支持延长 session 有效期
导航栏切换 登录后显示"欢迎,XXX"及下拉菜单
智能跳转 登录后返回之前想访问的页面,否则回首页
安全登出 清除 session,回到首页

二、自定义认证后端

2.1 为什么要自定义?

Django 默认的认证后端只能通过 usernamepassword 登录。但我们的注册逻辑允许用户用手机号或邮箱注册,username 字段存储的可能是手机号或邮箱前缀,这会导致用户记不住自己的用户名。

解决方案: 自定义认证后端,让系统同时匹配手机号、邮箱和用户名。

2.2 实现代码

apps/users/ 目录下创建 backends.py

bash 复制代码
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
from .models import User


class MultiFieldAuthBackend(ModelBackend):
    """
    支持手机号 / 邮箱 / 用户名 登录的自定义认证后端
    """
    def authenticate(self, request, username=None, password=None, **kwargs):
        try:
            # 使用 Q 对象进行 OR 查询,同时匹配三个字段
            user = User.objects.get(
                Q(username=username) |
                Q(email=username) |
                Q(phone=username)
            )
        except User.DoesNotExist:
            return None

        # 校验密码(Django 自动处理密码哈希比对)
        if user.check_password(password):
            return user
        return None

2.3 工作原理

Django 调用 authenticate() 时会遍历所有配置的认证后端。我们的自定义后端优先尝试匹配手机号、邮箱或用户名,只要用户输入的是三者之一,都能找到对应账户。

2.4 注册后端

django_ecommerce/settings.py 中声明:

bash 复制代码
AUTHENTICATION_BACKENDS = [
    'users.backends.MultiFieldAuthBackend',      # 自定义后端(优先)
    'django.contrib.auth.backends.ModelBackend', # 保留默认后端
]

三、登录表单

apps/users/forms.py 中追加登录表单:

bash 复制代码
from django import forms


class LoginForm(forms.Form):
    username = forms.CharField(
        label='账号',
        widget=forms.TextInput(attrs={
            'class': 'form-control',
            'placeholder': '手机号 / 邮箱 / 用户名'
        })
    )
    password = forms.CharField(
        label='密码',
        widget=forms.PasswordInput(attrs={
            'class': 'form-control',
            'placeholder': '请输入密码'
        })
    )
    remember = forms.BooleanField(
        label='记住我',
        required=False,
        widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
    )

设计思路: 表单只负责收集和基础校验,真正的认证逻辑交给视图处理,保持关注点分离。


四、登录视图

编辑 apps/users/views.py,添加登录逻辑:

bash 复制代码
from django.contrib.auth import authenticate, login, logout
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import LoginForm


def user_login(request):
    # 已登录用户直接重定向到首页
    if request.user.is_authenticated:
        return redirect('home')

    if request.method == 'POST':
        form = LoginForm(request.POST)
        if form.is_valid():
            username = form.cleaned_data['username']
            password = form.cleaned_data['password']
            remember = form.cleaned_data.get('remember')

            # 调用自定义后端进行认证
            user = authenticate(request, username=username, password=password)

            if user is not None:
                if user.is_active:
                    # 执行登录,将用户写入 session
                    login(request, user)

                    # 处理"记住我"逻辑
                    if remember:
                        # Session 有效期 30 天
                        request.session.set_expiry(60 * 60 * 24 * 30)
                    else:
                        # 浏览器关闭即失效
                        request.session.set_expiry(0)

                    # 跳转到 next 参数指定的页面,否则回首页
                    next_url = request.GET.get('next', 'home')
                    messages.success(request, f'欢迎回来,{user.username}!')
                    return redirect(next_url)
                else:
                    messages.error(request, '该账号已被禁用,请联系管理员。')
            else:
                messages.error(request, '账号或密码错误,请重试。')
    else:
        form = LoginForm()

    return render(request, 'users/login.html', {'form': form})

核心要点解析

要点 说明
request.user.is_authenticated 检查用户是否已登录,避免重复登录
authenticate() 自动调用我们自定义的 MultiFieldAuthBackend
login(request, user) 将认证信息写入 session,完成登录
set_expiry() 控制 session 有效期,实现"记住我"
next 参数 Django 内置跳转机制,登录后自动返回原页面

五、登出视图

继续在 views.py 末尾添加,逻辑非常简单:

bash 复制代码
def user_logout(request):
    logout(request)  # 清除当前 session 中的所有认证数据
    messages.success(request, '您已成功退出登录。')
    return redirect('home')

六、配置 URL 路由

编辑 apps/users/urls.py,追加登录和登出路由:

bash 复制代码
from django.urls import path
from . import views

urlpatterns = [
    path('register/', views.register, name='register'),
    path('send_sms/', views.send_sms_code, name='send_sms'),
    path('activate/<int:user_id>/<str:token>/', views.activate_email, name='activate_email'),
    path('login/', views.user_login, name='login'),
    path('logout/', views.user_logout, name='logout'),
]

七、登录页面模板

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

bash 复制代码
{% extends 'base.html' %}
{% block title %}用户登录{% endblock %}

{% block content %}
<div class="row justify-content-center">
    <div class="col-md-6 col-lg-5">
        <div class="card shadow-sm">
            <div class="card-body p-4">
                <h3 class="text-center mb-4">🔐 用户登录</h3>

                <form method="post" novalidate>
                    {% csrf_token %}

                    <!-- 全局错误提示 -->
                    {% if form.non_field_errors %}
                        <div class="alert alert-danger">
                            {{ form.non_field_errors.0 }}
                        </div>
                    {% endif %}

                    <!-- 账号输入 -->
                    <div class="mb-3">
                        <label class="form-label">{{ form.username.label }}</label>
                        {{ form.username }}
                        {% if form.username.errors %}
                            <div class="text-danger small">{{ form.username.errors.0 }}</div>
                        {% endif %}
                    </div>

                    <!-- 密码输入 -->
                    <div class="mb-3">
                        <label class="form-label">{{ form.password.label }}</label>
                        {{ form.password }}
                        {% if form.password.errors %}
                            <div class="text-danger small">{{ form.password.errors.0 }}</div>
                        {% endif %}
                    </div>

                    <!-- 记住我 -->
                    <div class="mb-3 form-check">
                        {{ form.remember }}
                        <label class="form-check-label" for="{{ form.remember.id_for_label }}">
                            {{ form.remember.label }}
                        </label>
                    </div>

                    <button type="submit" class="btn btn-primary w-100">登录</button>
                </form>

                <p class="text-center mt-3">
                    还没有账号?<a href="{% url 'users:register' %}">立即注册</a>
                </p>
            </div>
        </div>
    </div>
</div>
{% endblock %}

八、更新导航栏(动态切换)

打开 templates/base.html,替换导航栏的用户相关部分:

bash 复制代码
<ul class="navbar-nav ms-auto">
    <li class="nav-item">
        <a class="nav-link" href="{% url 'home' %}">首页</a>
    </li>
    <li class="nav-item">
        <a class="nav-link" href="#">商品</a>
    </li>
    <li class="nav-item">
        <a class="nav-link" href="#">购物车</a>
    </li>
    
    {% if user.is_authenticated %}
        <!-- 已登录:显示用户下拉菜单 -->
        <li class="nav-item dropdown">
            <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
                👤 {{ user.username }}
            </a>
            <ul class="dropdown-menu dropdown-menu-end">
                <li><a class="dropdown-item" href="#">个人中心</a></li>
                <li><a class="dropdown-item" href="#">我的订单</a></li>
                <li><hr class="dropdown-divider"></li>
                <li><a class="dropdown-item" href="{% url 'admin:index' %}">后台管理</a></li>
                <li><a class="dropdown-item" href="{% url 'users:logout' %}">退出登录</a></li>
            </ul>
        </li>
    {% else %}
        <!-- 未登录:显示登录/注册入口 -->
        <li class="nav-item">
            <a class="nav-link" href="{% url 'users:login' %}">登录</a>
        </li>
        <li class="nav-item">
            <a class="nav-link" href="{% url 'users:register' %}">注册</a>
        </li>
    {% endif %}
</ul>

效果: 未登录时显示"登录"和"注册",登录后显示用户名下拉菜单,包含个人中心、订单、后台管理、退出登录等入口。


九、完整流程测试

启动开发服务器:

bash 复制代码
python manage.py runserver

9.1 手机号登录测试

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

  2. 输入手机号 13800138000(上一篇注册的手机号)和密码

  3. 勾选"记住我",点击登录

预期结果:

  • 终端输出 302 重定向状态码

  • 浏览器跳转到首页

  • 导航栏显示 👤 13800138000 下拉菜单

  • 页面顶部显示绿色消息:"欢迎回来,13800138000!"

9.2 邮箱登录测试

  1. 点击导航栏"退出登录",确认看到成功提示

  2. 重新进入登录页,输入邮箱 test@example.com 和密码

  3. 点击登录

预期结果: 同样登录成功,验证自定义后端对邮箱的匹配支持。

9.3 错误情况测试

输入错误的密码,页面应显示红色提示:

账号或密码错误,请重试。

终端返回 200 状态码,表示停留在登录页并展示错误信息。

9.4 登出流程测试

点击"退出登录":

  • 终端输出 302 重定向

  • 回到首页,导航栏恢复为"登录"和"注册"

  • 页面显示绿色消息:"您已成功退出登录。"


十、未登录访问保护(预告)

某些页面(如个人中心、购物车)必须登录才能访问。Django 提供了 login_required 装饰器,后续我们会这样使用:

bash 复制代码
from django.contrib.auth.decorators import login_required

@login_required(login_url='users:login')
def personal_center(request):
    ...

当未登录用户访问被保护页面时,系统会自动跳转到登录页,并带上 ?next=/原路径 参数。这正是我们在登录视图中已经支持的 next 跳转机制,登录后自动返回原页面。


十一、总结与下集预告

本篇完成清单

  • 自定义认证后端:支持手机号/邮箱/用户名三种方式登录

  • 登录视图 :集成"记住我"功能和 next 智能跳转

  • 登出功能:一行代码清除 session

  • 导航栏动态切换:根据登录状态显示不同菜单

  • 完整测试:覆盖登录、登出、错误提示等全部流程

身份认证的基础已经打牢。从下一篇开始,我们将进入用户体系的深化部分。

下集预告

第 8 篇:个人中心页面

  • 用户信息展示

  • 修改密码

  • 更换手机号/邮箱

让用户真正拥有自己的"小窝"。


📌 想了解更多? 搜索其它平台「IT策士」,一起升级 IT 思维!


相关推荐
程序边界2 小时前
标量子查询消除与向量化:一个被低估的协同效应
数据库
zero.cyx2 小时前
软件设计师(4)数据库
数据库
.小小陈.2 小时前
MySQL 高频考点:表连接与索引全解析
数据库
HwJack202 小时前
Flask+SQLite简易后端数据存储实现原理
jvm·sqlite·flask
阳光九叶草LXGZXJ2 小时前
达梦数据库-学习-57-读写数据页超时告警排查(page[x,x,xxxxxx] disk write uses)-DSC集群版
linux·运维·服务器·数据库·sql·学习
Omics Pro2 小时前
前沿学科:量子生物学!
大数据·数据库·人工智能·windows·redis·量子计算
霸道流氓气质2 小时前
Spring 事务提交后执行异步操作:原理、陷阱与最佳实践
数据库·spring
无小道2 小时前
Redis——list相关指令
数据库·redis·缓存
阳光九叶草LXGZXJ2 小时前
达梦数据库-堆栈看问题-01-asmapi_asm_extent_load
linux·运维·数据库·sql·学习