• Django系列14-员工管理系统实战--用户登陆


    一. cookie和session

    1.1 http无状态短连接

    截止目前为止,我们的项目都是直接访问url的,没有使用用户登陆,此时浏览器与服务器之间都是通过短连接的形式来进行交互的,但是我没操作一次,都会重新建立一次短连接,如果是高并发的环境,会带来性能的瓶颈。
    image.png

    1.2 cookie和session

    如下图所示:
    当浏览器与服务器之间建立了一个连接后,就会将创建连接时候的信息保存下来,在浏览器端就是cookie,在server端就是session,然后只要连接不主动断开或不过期,就不用重复认证。
    image.png

    二. 中间件

    2.1 登陆页面设计

    登录成功后:

    1. cookie,随机字符串
    2. session,用户信息

    在其他需要登录才能访问的页面中,都需要加入:

    def index(request):
        info = request.session.get("info")
        if not info:
            return redirect('/login/')
    
    • 1
    • 2
    • 3
    • 4

    目标:在18个视图函数前面统一加入判断。

    info = request.session.get("info")
    if not info:
        return redirect('/login/')
    
    • 1
    • 2
    • 3

    2.2 中间件的体验

    中间件有点类似拦截器,在浏览器和服务器中间的一层,所有的操作都需要经过这一层。
    有了中间件,我们就不需要在18个视图(后面可能增加更多)的视图函数加入判断了。

    image.png

    定义中间件

     from django.utils.deprecation import MiddlewareMixin
      from django.shortcuts import HttpResponse
      
      class M1(MiddlewareMixin):
          """ 中间件1 """
      
          def process_request(self, request):
      
              # 如果方法中没有返回值(返回None),继续向后走
              # 如果有返回值 HttpResponse、render 、redirect
              print("M1.process_request")
              return HttpResponse("无权访问")
      
          def process_response(self, request, response):
              print("M1.process_response")
              return response
      
      
      class M2(MiddlewareMixin):
          """ 中间件2 """
      
          def process_request(self, request):
              print("M2.process_request")
      
          def process_response(self, request, response):
              print("M2.process_response")
              return response
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    应用中间件 setings.py

     MIDDLEWARE = [
          'django.middleware.security.SecurityMiddleware',
          'django.contrib.sessions.middleware.SessionMiddleware',
          'django.middleware.common.CommonMiddleware',
          'django.middleware.csrf.CsrfViewMiddleware',
          'django.contrib.auth.middleware.AuthenticationMiddleware',
          'django.contrib.messages.middleware.MessageMiddleware',
          'django.middleware.clickjacking.XFrameOptionsMiddleware',
          'app01.middleware.auth.M1',
          'app01.middleware.auth.M2',
      ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在中间件的process_request方法

    # 如果方法中没有返回值(返回None),继续向后走
    # 如果有返回值 HttpResponse、render 、redirect,则不再继续向后执行。
    
    • 1
    • 2

    三. 用户登陆功能实现

    3.1 URL调整

        # 登录
        path('login/', account.login),
        path('logout/', account.logout),
        path('image/code/', account.image_code),
    
    • 1
    • 2
    • 3
    • 4

    调整为如下,直接访问默认页面就进入了登陆页面:
    image.png

    3.2 后端功能实现

    3.2.1 中间件实现登陆校验

    auth.py

    from django.utils.deprecation import MiddlewareMixin
    from django.shortcuts import HttpResponse, redirect
    
    
    class AuthMiddleware(MiddlewareMixin):
    
        def process_request(self, request):
            # 0.排除那些不需要登录就能访问的页面
            #   request.path_info 获取当前用户请求的URL /login/
            if request.path_info in ["/login/", "/image/code/"]:
                return
    
            # 1.读取当前访问的用户的session信息,如果能读到,说明已登陆过,就可以继续向后走。
            info_dict = request.session.get("info")
            # print(info_dict)
            if info_dict:
                return
    
            # 2.没有登录过,重新回到登录页面
            return redirect('/login/')
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    settings.py

    MIDDLEWARE = [
          'django.middleware.security.SecurityMiddleware',
          'django.contrib.sessions.middleware.SessionMiddleware',
          'django.middleware.common.CommonMiddleware',
          'django.middleware.csrf.CsrfViewMiddleware',
          'django.contrib.auth.middleware.AuthenticationMiddleware',
          'django.contrib.messages.middleware.MessageMiddleware',
          'django.middleware.clickjacking.XFrameOptionsMiddleware',
          'app01.middleware.auth.AuthMiddleware',
      ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.2.2 验证码功能实现

    code.py

    import random
    from PIL import Image, ImageDraw, ImageFont, ImageFilter
    
    
    def check_code(width=120, height=30, char_length=5, font_file='Monaco.ttf', font_size=28):
        code = []
        img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255))
        draw = ImageDraw.Draw(img, mode='RGB')
    
        def rndChar():
            """
            生成随机字母
            :return:
            """
            # return str(random.randint(0, 9))
            return chr(random.randint(65, 90))
    
    
        def rndColor():
            """
            生成随机颜色
            :return:
            """
            return (random.randint(0, 255), random.randint(10, 255), random.randint(64, 255))
    
        # 写文字
        font = ImageFont.truetype(font_file, font_size)
        for i in range(char_length):
            char = rndChar()
            code.append(char)
            h = random.randint(0, 4)
            draw.text([i * width / char_length, h], char, font=font, fill=rndColor())
    
        # 写干扰点
        for i in range(40):
            draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
    
        # 写干扰圆圈
        for i in range(40):
            draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
            x = random.randint(0, width)
            y = random.randint(0, height)
            draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor())
    
        # 画干扰线
        for i in range(5):
            x1 = random.randint(0, width)
            y1 = random.randint(0, height)
            x2 = random.randint(0, width)
            y2 = random.randint(0, height)
    
            draw.line((x1, y1, x2, y2), fill=rndColor())
    
        img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)
        return img, ''.join(code)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    3.2.3 视图函数

    account.py

    from django.shortcuts import render, HttpResponse, redirect
    from django import forms
    from io import BytesIO
    
    from app01.utils.code import check_code
    from app01 import models
    from app01.utils.bootstrap import BootStrapForm
    from app01.utils.encrypt import md5
    
    
    class LoginForm(BootStrapForm):
        username = forms.CharField(
            label="用户名",
            widget=forms.TextInput,
            required=True
        )
        password = forms.CharField(
            label="密码",
            widget=forms.PasswordInput(render_value=True),
            required=True
        )
    
        code = forms.CharField(
            label="验证码",
            widget=forms.TextInput,
            required=True
        )
    
        def clean_password(self):
            pwd = self.cleaned_data.get("password")
            return md5(pwd)
    
    
    def login(request):
        """ 登录 """
        if request.method == "GET":
            form = LoginForm()
            return render(request, 'login.html', {'form': form})
    
        form = LoginForm(data=request.POST)
        if form.is_valid():
            # 验证成功,获取到的用户名和密码
            # {'username': 'wupeiqi', 'password': '123',"code":123}
            # {'username': 'wupeiqi', 'password': '5e5c3bad7eb35cba3638e145c830c35f',"code":xxx}
    
            # 验证码的校验
            user_input_code = form.cleaned_data.pop('code')
            code = request.session.get('image_code', "")
            if code.upper() != user_input_code.upper():
                form.add_error("code", "验证码错误")
                return render(request, 'login.html', {'form': form})
    
            # 去数据库校验用户名和密码是否正确,获取用户对象、None
            # admin_object = models.Admin.objects.filter(username=xxx, password=xxx).first()
            admin_object = models.Admin.objects.filter(**form.cleaned_data).first()
            if not admin_object:
                form.add_error("password", "用户名或密码错误")
                # form.add_error("username", "用户名或密码错误")
                return render(request, 'login.html', {'form': form})
    
            # 用户名和密码正确
            # 网站生成随机字符串; 写到用户浏览器的cookie中;在写入到session中;
            request.session["info"] = {'id': admin_object.id, 'name': admin_object.username}
            # session可以保存7天
            request.session.set_expiry(60 * 60 * 24 * 7)
    
            return redirect("/admin/list/")
    
        return render(request, 'login.html', {'form': form})
    
    
    def image_code(request):
        """ 生成图片验证码 """
    
        # 调用pillow函数,生成图片
        img, code_string = check_code()
    
        # 写入到自己的session中(以便于后续获取验证码再进行校验)
        request.session['image_code'] = code_string
        # 给Session设置60s超时
        request.session.set_expiry(60)
    
        stream = BytesIO()
        img.save(stream, 'png')
        return HttpResponse(stream.getvalue())
    
    
    def logout(request):
        """ 注销 """
    
        request.session.clear()
    
        return redirect('/login/')
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94

    3.3 前端页面

    3.3.1 login.html

    {% load static %}
    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Titletitle>
        <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.min.css' %}">
        <style>
            .account {
                width: 400px;
                border: 1px solid #dddddd;
                border-radius: 5px;
                box-shadow: 5px 5px 20px #aaa;
    
                margin-left: auto;
                margin-right: auto;
                margin-top: 100px;
                padding: 20px 40px;
            }
    
            .account h2 {
                margin-top: 10px;
                text-align: center;
            }
        style>
    head>
    <body>
    <div class="account">
        <h2>用户登录h2>
        <form method="post" novalidate>
            {% csrf_token %}
            <div class="form-group">
                <label>用户名label>
                {{ form.username }}
                <span style="color: red;">{{ form.username.errors.0 }}span>
            div>
            <div class="form-group">
                <label>密码label>
                {{ form.password }}
                <span style="color: red;">{{ form.password.errors.0 }}span>
            div>
            <div class="form-group">
                <label for="id_code">图片验证码label>
                <div class="row">
                    <div class="col-xs-7">
                        {{ form.code }}
                        <span style="color: red;">{{ form.code.errors.0 }}span>
                    div>
                    <div class="col-xs-5">
                        <img id="image_code" src="/image/code/" style="width: 125px;">
                    div>
                div>
            div>
            <input type="submit" value="登 录" class="btn btn-primary">
        form>
    div>
    
    body>
    html>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    3.3.2 error.html

    {% extends 'layout.html' %}
    
    {% block content %}
        <div class="container">
            <div class="alert alert-danger" role="alert">{{ msg }}div>
        div>
    {% endblock %}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.3.3 layout.html

    之前我们把页面上登陆的用户写死了,现在需要获取到当前登陆的用户,然后从显示。

    {% load static %}
    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Titletitle>
        <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.min.css' %}">
        <style>
            .navbar {
                border-radius: 0;
            }
        style>
        {% block css %}{% endblock %}
    head>
    <body>
    <nav class="navbar navbar-default">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                        data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                    <span class="sr-only">Toggle navigationspan>
                    <span class="icon-bar">span>
                    <span class="icon-bar">span>
                    <span class="icon-bar">span>
                button>
                <a class="navbar-brand" href="#"> 员工管理系统 a>
            div>
            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                <ul class="nav navbar-nav">
                    <li><a href="/admin/list/">管理员账户a>li>
                    <li><a href="/depart/list/">部门管理a>li>
                    <li><a href="/user/list/">用户管理a>li>
                    <li><a href="/pretty/list/">靓号管理a>li>
    
    
                ul>
                <ul class="nav navbar-nav navbar-right">
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                           aria-expanded="false">{{ request.session.info.name }} <span class="caret">span>a>
                        <ul class="dropdown-menu">
                            <li><a href="#">个人资料a>li>
                            <li><a href="#">我的信息a>li>
                            <li role="separator" class="divider">li>
                            <li><a href="/logout/">注销a>li>
                        ul>
                    li>
                ul>
            div>
        div>
    nav>
    
    <div>
        {% block content %}{% endblock %}
    div>
    
    
    <script src="{% static 'js/jquery-3.6.0.min.js' %}">script>
    <script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.min.js' %}">script>
    {% block js %}{% endblock %}
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    四. 页面测试

    直接访问: http://127.0.0.1:8000/

    image.png

    验证码必须填写:
    image.png

    先验证验证码,后验证用户名和密码:
    image.png

    image.png

    登陆成功页面:
    image.png

    参考:

    1. https://www.bilibili.com/video/BV1NL41157ph
  • 相关阅读:
    超美!ChatGPT DALL-E 3已可用,另外GPT-4可上传图片进行问答
    CentOS7安装Nginx+ModSecurity
    AJAX【基于XML的数据交换 、AJAX乱码问题 、AJAX的异步与同步 、AJAX代码封装】
    大数据--hadoop生态12--高频知识点总结
    Spark 离线开发框架设计与实现
    IO类型游戏研发定制开发
    LeetCode_数学分析_中等_754.到达终点数字
    一篇图文搞定Java内存模型
    依赖:类之间的依赖关系【python】
    谁会拒绝一款开源的 3D 博客呢?
  • 原文地址:https://blog.csdn.net/u010520724/article/details/126883595