• 从零开始的Django框架入门到实战教程(内含实战实例) - 08 用户界面(内含图形验证码的生成和校验详解)(学习笔记)



      Django是目前比较火爆的框架,之前有在知乎刷到,很多毕业生进入大厂实习后因为不会git和Django框架3天就被踢掉了,因为他们很难把自己的工作融入到整个组的工作中。因此,我尝试自学Django并整理出如下笔记。
      为了防止用户名和密码被暴力破解,很多网页都会采取图形验证码的方式缓解这个问题,我们页用Dango实现一下这个功能。内容包括:

    1. 使用pillow(PIL包)中的Image, ImageDraw, ImageFont, ImageFilter生成白画布、生成字母(字体、加粗)、加入干扰元素(点、线、圆)
    2. 在已有文件中加入验证码图片,并将用户输入的验证码与验证码结果对比校验

    0. 既有工作

       介绍一下已经完成的东西。首先是一个登录界面,但是不含有图形验证码

    在这里插入图片描述

      登录之后就是一个平平无奇的管理系统,这里不多介绍。

    在这里插入图片描述

      我们的工作主要是针对login登录界面的。

    1. 生成图片

      这里用的是pillow包。主要思路为:生成白画布 -> 生成字母(字体、加粗) -> 加入干扰元素(点、线、圆)。

    # -*- coding:utf-8 -*-
    import random
    
    from PIL import Image, ImageDraw, ImageFont, ImageFilter
    def get_image(width=128, height=38, char_length=4, font_file='application01/static/font/font.ttf', font_size=30):
        code = []
        # 创建画布
        img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255))
        draw = ImageDraw.Draw(img, mode='RGB')
    
        def rndChar():
            return chr(random.randint(65, 90))
    
        def rndColor():
            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)
            # 加粗显示
            for j in range(4):
                draw.text([i * width / char_length + j, 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, width)
            draw.arc((x, y, x+4, y+4), 0, 90, fill=rndColor())
    
        # 干扰线
        for i in range(10):
            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)
        # print(''.join(code))
        # img.show()
        return img, ''.join(code)
    
    if __name__ == '__main__':
        get_image()
    
    • 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

      将整个代码放入项目。

    在这里插入图片描述

    2. 校验

      考虑到多用户访问,我们采取内存读取,并给每个校验码设置超时时长。

    from io import BytesIO
    from application01.utils.create_image import get_image
    def image_code(request):
        img, code = get_image()
        # 考虑到多用户访问
        request.session['image_code'] = code
        # 设置60秒后超时
        request.session.set_expiry(60)
        # 直接存内存里
        stream = BytesIO()
        img.save(stream, 'png')
        return HttpResponse(stream.getvalue())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

      将这段代码加入至我们平时放用户认证的文件account.py中:

    在这里插入图片描述
      在html前端界面中加入验证码图片和校验,重点关注注释为验证码部分的代码:

    {% load static %}
    DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="http://at.alicdn.com/t/font_1786038_m62pqneyrzf.css">
        <title>Documenttitle>
        <style>
            * {
                margin: 0;
                padding: 0;
            }
    
            html {
                height: 100%;
            }
    
            body {
                height: 100%;
                font-family: JetBrains Mono Medium;
                display: flex;
                align-items: center;
                justify-content: center;
                /* background-color: #0e92b3; */
                background: url({% static 'img/Olsen02.jpg' %}) no-repeat;
                background-size: 100%;
            }
    
            .form-wrapper {
                width: 300px;
                background-color: rgba(41, 45, 62, .8);
                color: #fff;
                border-radius: 2px;
                padding: 50px;
            }
    
            .form-wrapper .header {
                text-align: center;
                font-size: 35px;
                text-transform: uppercase;
                line-height: 100px;
            }
    
            .form-wrapper .input-wrapper input {
                background-color: rgb(41, 45, 62);
                border: 0;
                width: 100%;
                text-align: center;
                font-size: 15px;
                color: #fff;
                outline: none;
            }
    
            .form-wrapper .input-wrapper input::placeholder {
                text-transform: uppercase;
            }
    
            .form-wrapper .input-wrapper .border-wrapper {
                background-image: linear-gradient(to right, #e8198b, #0eb4dd);
                width: 100%;
                height: 50px;
                margin-bottom: 20px;
                border-radius: 30px;
                display: flex;
                align-items: center;
                justify-content: center;
            }
    
            .form-wrapper .input-wrapper .border-wrapper .border-item {
                height: calc(100% - 4px);
                width: calc(100% - 4px);
                border-radius: 30px;
            }
    
            .form-wrapper .action {
                display: flex;
                justify-content: center;
            }
    
            .form-wrapper .action .btn {
                width: 60%;
                text-transform: uppercase;
                border: 2px solid #0e92b3;
                text-align: center;
                line-height: 50px;
                border-radius: 30px;
                cursor: pointer;
            }
    
            .form-wrapper .action .btn:hover {
                background-image: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
            }
    
            .form-wrapper .icon-wrapper {
                text-align: center;
                width: 60%;
                margin: 0 auto;
                margin-top: 20px;
                border-top: 1px dashed rgb(146, 146, 146);
                padding: 20px;
            }
    
            .form-wrapper .icon-wrapper i {
                font-size: 20px;
                color: rgb(187, 187, 187);
                cursor: pointer;
                border: 1px solid #fff;
                padding: 5px;
                border-radius: 20px;
            }
    
            .form-wrapper .icon-wrapper i:hover {
                background-color: #0e92b3;
            }
    
            .block {
                background-color: transparent;
                color: #fff;
                font-family: JetBrains Mono Medium;
                display: block;
                font-size: 16px;
                margin-top:15px;
            }
    
    
        style>
    head>
    
    <body>
    <div class="form-wrapper">
        <div class="header">
            login
        div>
        <form method="post">
            {% csrf_token %}
            <div class="input-wrapper">
                <div class="border-wrapper">
                    
                    {{ form.username }}
                div>
                <div style="text-align: center; width: calc(100% - 4px);">{{ form.username.errors.0 }}div>
                <div class="border-wrapper">
                    
                    {{ form.password }}
                div>
                <div style="text-align: center; width: calc(100% - 4px);">{{ form.password.errors.0 }}div>
            div>
            
    		
    		
            <div class="form-group">
                <div class="row">
                    <div class="col-xs-7">
                        <div class="input-wrapper">
                            <div class="border-wrapper">
                                {{ form.code }}
                                <span style="color: red;">span>
                            div>
                        div>
                    div>
                    <div class="col-xs-5">
                        <a href="/login"><img id="image_code" src="/image/code">a>
                        <span style="color: #898989;">点击图片换一张span>
                        <div style="text-align: center; width: 50%; color: red">{{ form.code.errors.0 }}div>
                    div>
                div>
            div>
    
            <div class="action">
                
                <input type="submit" class="btn block" value="LOGIN">input>
            div>
        form>
        <div class="icon-wrapper">
            <i class="iconfont icon-weixin">i>
            <i class="iconfont icon-qq">i>
            <i class="iconfont icon-git">i>
        div>
    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
    • 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
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184

      最后,在已有的登录界面中加入这段验证码图片生成代码,并在用户输入后校验输入的结果与验证码本身。这段代码大部分属于已有工作,详见07 用户管理

    # 登录界面
    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():
            # 校验验证码
            input_code = form.cleaned_data.pop('code')
            true_code = request.session.get('image_code', '')
            if input_code.upper() != true_code.upper():
                form.add_error('code', '验证码错误')
                return render(request, 'login.html', {'form': form})
    
            # 这里是没有models.save的,但是form.cleaned_data
            # 直接用字典形式,不用一个一个写
            admin_exist = models.Admin.objects.filter(**form.cleaned_data).first()
            # 密码错误
            if not admin_exist:
                form.add_error('password', '用户名或密码错误')
                return render(request, 'login.html', {'form': form})
            # 生成随机cookie原来还是比较麻烦的事情,但是Django帮我们简化了
            # 这一行代码直接完成了cookie的随机字符串的生成,将随机字符串保存到session中并附上响应的值
            request.session['info'] = {'id': admin_exist.id, 'username': admin_exist.username}
            # 7天免登录
            request.session.set_expiry(60 * 60 * 24 * 7)
            return redirect("/admin/list")
        return render(request, 'login.html', {"form": form})
    
    • 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

    3. 结果展示

      因为文件大小限制,图片画质有点离谱。
    在这里插入图片描述

  • 相关阅读:
    Methoxy-PEG-PCL,Methoxy-PEG-Poly(ε-caprolactone)可以作为制备纳米颗粒的重要原料
    【svn使用教程】
    Orange Pi i96 入手填坑问题(2)-wifi网卡MAC地址随机变化和串口粘贴死机问题
    面试官:说说反射的底层实现原理?
    海康威视相机在QTcreate上的使用教程
    微信小程序检查版本更新
    Java版企业电子招标采购系统源码Spring Cloud + Spring Boot +二次开发+ MybatisPlus + Redis
    【error】root - Exception during pool initialization
    万字 HashMap 详解,基础(优雅)永不过时
    从零到一搭建基础架构(8)-业务服务接入基础架构
  • 原文地址:https://blog.csdn.net/Hjh1906008151/article/details/126660954