用户登录登出功能

I. 功能需求分析

1>功能分析

1.1>流程图

python request跳转到其他服务的登陆页面并返回到前端_django

1.2>功能接口
  1. 登录页面
  2. 登录功能
  3. 退出功能

II. 登陆页面

1>接口设计

1.1>接口说明

类目

说明

请求方法

GET

url定义

/users/login/

参数格式

无参数

1.2>返回结果

登陆页面

2.后端代码
  1. user/views.py代码:
from django.shortcuts import render
from django.views import View
# ....

class LoginView(View):
    """
    登录视图
    """
    def get(self, request):
        return render(request, 'users/login.html')
  1. user/urls.py代码:
from django.urls import path, include
from . import views
app_name = 'users'

urlpatterns = [
    path('register/', views.RegisterView.as_view(), name='register'),
    path('login/', views.LoginView.as_view(), name='login'),
]
3.前端页面代码

1.users/login.html

因为表单需要post请求,所以记得在页面使用{% csrf_token %}标签。

注册功能已经开发好了,所以页面的上的立即注册处的a标签href可以写好

{% extends 'base/base.html' %}
{% load static %}

{% block title %}登录{% endblock title %}
{% block link %}
      <link rel="stylesheet" href="{% static 'css/users/auth.css' %}">
{% endblock link %}

{% block main_start %}
    <!-- container start -->
<main id="container">
  <div class="login-contain">
    <div class="top-contain">
      <h4 class="please-login">请登录</h4>
      <a href="{% url 'users:register' %}" class="register">立即注册 ">></a>
    </div>
    <form action="" method="post" class="form-contain">
      <div class="form-item">
        <input type="tel" placeholder="请输入用户名或手机号" name="account" class="form-control" autocomplete="off">
      </div>
      <div class="form-item">
        <input type="password" placeholder="请输入密码" name="password" class="form-control">
      </div>
      <div class="form-item clearfix">
        <label>
          <input type="checkbox" name="remember">
          <span>记住我</span>
        </label>
        <a href="javascript:void(0);" class="forget-password">忘记密码?</a>
      </div>
      <div class="form-login">
        <input type="submit" value="登录" class="login-btn">
      </div>
        {% csrf_token %}
    </form>
  </div>
</main>
<!-- container end -->
{% endblock main_start %}

III. 登录功能

1>业务流程分析

  1. 参数校验
  1. 账户校验(用户名/手机号)
  2. 密码校验
  3. 账户密码联合校验
  1. 登陆逻辑
  1. 登陆就是在session中保存状态
  2. 根据记住我选项, 设置sessionID有效期

2>接口设计

2.1>接口说明:

类目

说明

请求方法

POST

url定义

/users/login/

参数格式

表单参数

2.2>参数说明:

参数名

类型

是否必须

描述

account

字符串


用户输入的手机号或用户名

password

字符串


用户输入的密码

remember

字符串


用户是否选择免登录

2.3>返回结果:
{
    "errno": "0", 
 	"errmsg": "OK", 
}

虽然登陆功能简单, 但安全需求极高, 需要考虑到的底层的东西很多, 所有这里我们可以使用django内置的auth模块来实现, 目的追求的是快速开发, 不深入底层

3>后端代码

3.1>users/views.py视图

在users目录下的views.py文件中定义如下视图:

class LoginView(View):
    """
    登录视图
    """
    def get(self, request):
        return render(request, 'user/login.html')

    def post(self, request):
        # 1.校验参数
        form = LoginForm(request.POST, request=request)
        if form.is_valid():
            # 2.登录功能
            return json_response(errmsg='恭喜登录成功!')
        else:
            # 引发的错误可能会有多条错误信息,如果出错了,就将表单的报错信息进行拼接,
            err_msg_list = []
            for item in form.errors.values():
                # 遍历的item也是一个列表, 其中的第一个元素是报错信息
                err_msg_list.append(item[0])
            err_msg_str = '/'.join(err_msg_list)
            # 由于是参数错误, 所以使用Code.PARAMERR
            return json_response(errno=Code.PARAMERR, errmsg=err_msg_str)
3.2>users/forms.py表单

在user目下的forms.py文件中定义如下表单:

import re

from django import forms
from django.db.models import Q
from django.contrib.auth import login

from users.models import User
from .constants import USER_SESSION_EXPIRY

class LoginForm(forms.Form):
    # 由于不确定是手机号码还是用户名,而这两者的格式是不一样的,所以要单独校验
    account = forms.CharField(error_messages={'required': '账户不能为空'})
    password = forms.CharField(max_length=20, min_length=6,
                               error_messages={
                                   'max_length': '密码长度要小于20',
                                   'min_length': '密码长度要大于6',
                                   'require': '密码不能为空'
                               })
    remember = forms.BooleanField(required=False)

    def __init__(self, *args, **kwargs):    # 在不清楚有什么具体参数的情况下,可使用这种方式代替
        self.request = kwargs.pop('request', None)    # 将接收到的request从参数中剔除出来,然后赋值
        # 执行完成上面的操作后再去执行父类的init,从而不会破环父类的初始化
        super().__init__(*args, **kwargs)


    def clean_account(self):
        """
        校验用户账户
        :return:
        """
        account = self.cleaned_data.get('account')
        if not re.match(r'^1[3-9]\d{9}$', account) and (len(account)<5 or len(account)>20):
            raise forms.ValidationError('用户账户格式不正确,请重新输入')
        # 一定要return
        return account

    def clean(self):
        """
        校验用户名密码,并实现登录逻辑
        :return:
        """
        clean_data = super().clean()

        # 拿到参数
        account = clean_data.get('account')
        password = clean_data.get('password')
        remember = clean_data.get('remember')

        # 实现登陆逻辑
        # 判断用户名与密码是否匹配
        # 1. 先找到这个用户, 使用以下代码来试下这一句sql语句
        # select * from tb_users where mobile=account or username=account;
        user_queryset = User.objects.filter(Q(mobile=account) | Q(username=account))
        if user_queryset:
            # 2. 校验是否匹配'
            user = user_queryset.first()
            if user.check_password(password):
                # 3. 是否免密登陆
                if remember:
                    # 免密登陆14天
                    self.request.session.set_expiry(USER_SESSION_EXPIRY)
                else:
                    # 关闭浏览器就重置登陆状态
                    self.request.session.set_expiry(0)
                # 4. 登录
                login(self.request, user)
            else:
                raise forms.ValidationError('用户名密码错误!')

        else:
            raise forms.ValidationError('该账户不存在,请重新输入!')

        return clean_data

在user文件加下创建constants.py文件定义如下常量

# 用户session信息过期时间 单位秒 默认14天
USER_SESSION_EXPIRY = 14*24*60*60

4>前端代码

修改user/login.html中用户账户输入框input的name为account

在static/js/user/下创建login.js文件

js代码如下:

$(function () {
    // 首先拿掉登录按钮原有的提交功能
    let $loginBtn = $('.login-btn');    // 获取登录按钮元素
    $loginBtn.click(function (e) {
        e.preventDefault(); // 阻止默认提交
    // 其次校验表单数据
        // 1.校验账户
        let sAccount = $('input[name="account"]').val();
        if (sAccount === ''){
            message.showError('用户账户不能为空');
            return
        }
        if(!(/^\w{5,20}$/).test(sAccount) && !(/^1[3-9]\d{9}$/).test(sAccount)){
            message.showError('用户账户格式不正确,请求重新输入');
            return
        }
        // 2.校验用户输入密码
        let sPassword = $('input[name="password"]').val();
        if(sPassword === ''){
            message.showError('用户密码不能为空');
            return
        }
        // 3.获取用户是否勾选'记住我',勾选为true,否则为false
        let bRemember = $('input[name="remember"]').is(':checked');
        
    // 然后发送ajax请求
        $.ajax({
            url: '/users/login/',
            data: {
                account: sAccount,
                password: sPassword,
                remember: bRemember
            },
            type: 'POST',
            dataType: 'json',
            success: function (res) {
                if(res.errno === '0'){
                    message.showSuccess('恭喜, 登录成功!');
                    setTimeout(function () {
                        //注册成功之后重定向到打开登录页面之前的页面,document.referrer表示前一个的页面
                        if(!document.referrer || document.referrer.includes('/users/login/') || document.referrer.includes('/users/register/')){
                            // 如果没有前一个页面(即直接通过登录页面的url访问的),
                            // 或是前一个页面就是登录页面(原地刷新),
                            // 或是前一个页面是注册页面(先注册后登陆),
                            // 那么将直接跳转到主页面
                            window.location.href = '/'
                        }else {
                            // 如果是通过其他界面来到登录的, 那就跳回前一个的页面
                            window.location.href = document.referrer
                        }
                    }, 3000)
                }else{
                    message.showError(res.errmsg)
                }
            },
            error: function (xhr, msg) {
                message.showError('服务器超时,请重试')

            }
        });
    });
});

5>页面效果

python request跳转到其他服务的登陆页面并返回到前端_django_02

IIII. 登出功能

1>接口设计

接口说明:

类目

说明

请求方法

GET

url定义

/user/logout/

参数格式

无参数

2>后端代码
# 在user目录下的views.py文件中定义如下视图:
class LogoutView(View):
    """
    登出视图
    """
    def get(self, request):
        logout(request)
        
        return redirect(reverse('users:login'))
# 在urser目录下的urls.py文件定义如下路由:
from django.urls import path
from . import views
app_name = 'users'

urlpatterns = [
    path('register/', views.RegisterView.as_view(), name='register'),
    path('login/', views.LoginView.as_view(), name='login'),
    path('logout/', views.LogoutView.as_view(), name='logout'),
]
3>前端代码

修改templates/base/base.html中的header部分的代码如下

通过is_authenticated确定当前登录状态, 然后if判断,

如果已登录则展示??user.username, 如未登录则展示登录/注册

<header id="header">
  <div class="mw1200 header-contain clearfix">
    <!-- logo start -->
    <h1 class="logo">
      <a href="javascript:void(0);" class="logo-title">Python</a>
    </h1>
    <!-- logo end -->
    <!-- nav start -->
    <nav class="nav">...</nav>
    <!-- nav end -->
    <!-- login start -->
      <div class="login-box">
          {% if user.is_authenticated %}
                <div class="author">
                    <i class="PyWhich py-user"></i>
                    <span>{{ user.username }}</span>
                    <ul class="author-menu">
                        <li><a href="javascript:void(0);">后台管理</a></li>
                        <li><a href="{% url 'users:logout' %}">退出登录</a></li>
                    </ul>
                </div>
            {% else %}
                <div>
                    <i class="PyWhich py-user"></i>
                    <span>
                  <a href="{% url 'users:login' %}" class="login">登录</a> /
                  <a href="{% url 'users:register' %}" class="reg">注册</a>
              </span>
                </div>
            {% endif %}
      </div>
    <!-- login end -->
  </div>
</header>
4>页面效果:

python request跳转到其他服务的登陆页面并返回到前端_注销_03


项目源码:https://gitee.com/hao4875/newssite