• 【Python百日进阶-WEB开发】Day177 - Django案例:09图形验证码(一)


    八、图形验证码

    8.1 图形验证码逻辑分析

    UUID 是 通用唯一识别码(Universally Unique Identifier)的缩写,是一种软件建构的标准,亦为开放软件基金会组织在分布式计算环境领域的一部分。其目的,是让分布式系统中的所有元素,都能有唯一的辨识信息,而不需要通过中央控制端来做辨识信息的指定。如此一来,每个人都可以创建不与其它人冲突的UUID。在这样的情况下,就不需考虑数据库创建时的名称重复问题。目前最广泛应用的UUID,是微软公司的全局唯一标识符(GUID),而其他重要的应用,则有Linux ext2/ext3文件系统、LUKS加密分区、GNOME、KDE、Mac OS X等等。另外我们也可以在e2fsprogs包中的UUID库找到实现。
    UUID(Universally Unique Identifier)全局唯一标识符,定义为一个字符串主键,采用32位数字组成,编码采用16进制,定义了在时间和空间都完全惟一的系统信息。
    在这里插入图片描述

    8.2 图形验证码接口设计和定义

    在这里插入图片描述

    8.3 图形验证码后端逻辑

    8.3.1 新建子应用

    8.3.1 创建子应用
    (meiduo_mall) PS E:\meiduo_project\meiduo_mall> cd meiduo_mall/apps
    (meiduo_mall) PS E:\meiduo_project\meiduo_mall\meiduo_mall\apps> python ../../manage.py startapp verifications
    (meiduo_mall) PS E:\meiduo_project\meiduo_mall\meiduo_mall\apps> cd ..
    (meiduo_mall) PS E:\meiduo_project\meiduo_mall\meiduo_mall> cd..
    (meiduo_mall) PS E:\meiduo_project\meiduo_mall> 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    8.3.2 meiduo_mall.settings.dev.py中注册验证子应用,也可以不注册

    注册的子应用一般需要迁移或模板,但验证模块既不需要迁移也没有模板
    在这里插入图片描述

    8.3.3 主路由中添加首页子路由转向和命名空间
    from django.contrib import admin
    from django.urls import path, include
    from meiduo_mall.apps.users import urls as users_urls
    from meiduo_mall.apps.contents import urls as contents_urls
    from meiduo_mall.apps.verifications import urls as verifications_urls
    
    urlpatterns = [
        path('admin/', admin.site.urls),
    
        # users
        path('', include(users_urls, namespace='users')),
    
        # contents
        path('', include(contents_urls, namespace='contents')),
        
        # verifications
        path('', include(verifications_urls, namespace='verifications')),
    ]
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    8.3.4 新建子路由contents.urls.py文件
    from django.urls import path, re_path
    from . import views
    
    app_name = 'verifications'
    
    urlpatterns = [
        # 图形验证码,re_path路由正则校验,响应json数据,不需要重定向,也就不需要命名空间
        re_path(r'^image_codes/(?P[\w-]+)/', views.ImageCodeView.as_view()),
    
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    8.3.5 首页子应用view.py文件
    from django.views import View
    from meiduo_mall.apps.users.models import User
    from meiduo_mall.utils.response_code import RETCODE
    
    
    class ImageCodeView(View):
        """  图形验证码 """
        def get(self, request, uuid):
            """
            参数:uuid通用唯一识别码,用于唯一识别图形验证码属于哪个用户的
            返回值:image/jpg
            """
            pass
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    8.3.2 准备captcha扩展包

    1. 先安装前置包Pillow:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple Pillow
    2. verifictions子应用中新建libs包,包含 init.py文件,用于存放扩展包文件;内部新建captcha包,captcha包内新建fonts包和captcha.py文件,复制字体文件到fonts包中。结构如下
      captcha包
      captcha.py文件
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    # refer to `https://bitbucket.org/akorn/wheezy.captcha`
    
    import random
    import string
    import os.path
    from io import BytesIO
    
    from PIL import Image
    from PIL import ImageFilter
    from PIL.ImageDraw import Draw
    from PIL.ImageFont import truetype
    
    
    class Bezier:       # 贝塞尔曲线
        def __init__(self):
            self.tsequence = tuple([t / 20.0 for t in range(21)])
            self.beziers = {}
    
        def pascal_row(self, n):
            """ Returns n-th row of Pascal's triangle
            """
            result = [1]
            x, numerator = 1, n
            for denominator in range(1, n // 2 + 1):
                x *= numerator
                x /= denominator
                result.append(x)
                numerator -= 1
            if n & 1 == 0:
                result.extend(reversed(result[:-1]))
            else:
                result.extend(reversed(result))
            return result
    
        def make_bezier(self, n):
            """ Bezier curves:
                http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Generalization
            """
            try:
                return self.beziers[n]
            except KeyError:
                combinations = self.pascal_row(n - 1)
                result = []
                for t in self.tsequence:
                    tpowers = (t ** i for i in range(n))
                    upowers = ((1 - t) ** i for i in range(n - 1, -1, -1))
                    coefs = [c * a * b for c, a, b in zip(combinations,
                                                          tpowers, upowers)]
                    result.append(coefs)
                self.beziers[n] = result
                return result
    
    
    class Captcha(object):
        def __init__(self):
            self._bezier = Bezier()
            self._dir = os.path.dirname(__file__)
            # self._captcha_path = os.path.join(self._dir, '..', 'static', 'captcha')
    
        @staticmethod
        def instance():
            if not hasattr(Captcha, "_instance"):
                Captcha._instance = Captcha()
            return Captcha._instance
    
        def initialize(self, width=200, height=75, color=None, text=None, fonts=None):
            # self.image = Image.new('RGB', (width, height), (255, 255, 255))
            self._text = text if text else random.sample(string.ascii_uppercase + string.ascii_uppercase + '3456789', 4)
            self.fonts = fonts if fonts else \
                [os.path.join(self._dir, 'fonts', font) for font in ['Arial.ttf', 'Georgia.ttf', 'actionj.ttf']]
            self.width = width
            self.height = height
            self._color = color if color else self.random_color(0, 200, random.randint(220, 255))
    
        @staticmethod
        def random_color(start, end, opacity=None):
            red = random.randint(start, end)
            green = random.randint(start, end)
            blue = random.randint(start, end)
            if opacity is None:
                return red, green, blue
            return red, green, blue, opacity
    
        # draw image
    
        def background(self, image):
            Draw(image).rectangle([(0, 0), image.size], fill=self.random_color(238, 255))
            return image
    
        @staticmethod
        def smooth(image):
            return image.filter(ImageFilter.SMOOTH)
    
        def curve(self, image, width=4, number=6, color=None):
            dx, height = image.size
            dx /= number
            path = [(dx * i, random.randint(0, height))
                    for i in range(1, number)]
            bcoefs = self._bezier.make_bezier(number - 1)
            points = []
            for coefs in bcoefs:
                points.append(tuple(sum([coef * p for coef, p in zip(coefs, ps)])
                                    for ps in zip(*path)))
            Draw(image).line(points, fill=color if color else self._color, width=width)
            return image
    
        def noise(self, image, number=50, level=2, color=None):
            width, height = image.size
            dx = width / 10
            width -= dx
            dy = height / 10
            height -= dy
            draw = Draw(image)
            for i in range(number):
                x = int(random.uniform(dx, width))
                y = int(random.uniform(dy, height))
                draw.line(((x, y), (x + level, y)), fill=color if color else self._color, width=level)
            return image
    
        def text(self, image, fonts, font_sizes=None, drawings=None, squeeze_factor=0.75, color=None):
            color = color if color else self._color
            fonts = tuple([truetype(name, size)
                           for name in fonts
                           for size in font_sizes or (65, 70, 75)])
            draw = Draw(image)
            char_images = []
            for c in self._text:
                font = random.choice(fonts)
                c_width, c_height = draw.textsize(c, font=font)
                char_image = Image.new('RGB', (c_width, c_height), (0, 0, 0))
                char_draw = Draw(char_image)
                char_draw.text((0, 0), c, font=font, fill=color)
                char_image = char_image.crop(char_image.getbbox())
                for drawing in drawings:
                    d = getattr(self, drawing)
                    char_image = d(char_image)
                char_images.append(char_image)
            width, height = image.size
            offset = int((width - sum(int(i.size[0] * squeeze_factor)
                                      for i in char_images[:-1]) -
                          char_images[-1].size[0]) / 2)
            for char_image in char_images:
                c_width, c_height = char_image.size
                mask = char_image.convert('L').point(lambda i: i * 1.97)
                image.paste(char_image,
                            (offset, int((height - c_height) / 2)),
                            mask)
                offset += int(c_width * squeeze_factor)
            return image
    
        # draw text
        @staticmethod
        def warp(image, dx_factor=0.27, dy_factor=0.21):
            width, height = image.size
            dx = width * dx_factor
            dy = height * dy_factor
            x1 = int(random.uniform(-dx, dx))
            y1 = int(random.uniform(-dy, dy))
            x2 = int(random.uniform(-dx, dx))
            y2 = int(random.uniform(-dy, dy))
            image2 = Image.new('RGB',
                               (width + abs(x1) + abs(x2),
                                height + abs(y1) + abs(y2)))
            image2.paste(image, (abs(x1), abs(y1)))
            width2, height2 = image2.size
            return image2.transform(
                (width, height), Image.QUAD,
                (x1, y1,
                 -x1, height2 - y2,
                 width2 + x2, height2 + y2,
                 width2 - x2, -y1))
    
        @staticmethod
        def offset(image, dx_factor=0.1, dy_factor=0.2):
            width, height = image.size
            dx = int(random.random() * width * dx_factor)
            dy = int(random.random() * height * dy_factor)
            image2 = Image.new('RGB', (width + dx, height + dy))
            image2.paste(image, (dx, dy))
            return image2
    
        @staticmethod
        def rotate(image, angle=25):
            return image.rotate(
                random.uniform(-angle, angle), Image.BILINEAR, expand=1)
    
        def captcha(self, path=None, fmt='JPEG'):
            """Create a captcha.
    
            Args:
                path: save path, default None.
                fmt: image format, PNG / JPEG.
            Returns:
                A tuple, (name, text, StringIO.value).
                For example:
                    ('fXZJN4AFxHGoU5mIlcsdOypa', 'JGW9', '\x89PNG\r\n\x1a\n\x00\x00\x00\r...')
    
            """
            image = Image.new('RGB', (self.width, self.height), (255, 255, 255))
            image = self.background(image)
            image = self.text(image, self.fonts, drawings=['warp', 'rotate', 'offset'])
            image = self.curve(image)
            image = self.noise(image)
            image = self.smooth(image)
            # name = "".join(random.sample(string.ascii_lowercase + string.ascii_uppercase + '3456789', 24))
            text = "".join(self._text)
            out = BytesIO()
            image.save(out, format=fmt)
            # if path:
            #     image.save(os.path.join(path, name), fmt)
            # return name, text, out.getvalue()
            return text, out.getvalue()
    
        def generate_captcha(self):
            self.initialize()
            return self.captcha("")
    
    captcha = Captcha.instance()
    
    if __name__ == '__main__':
        print(captcha.generate_captcha())
    
    • 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
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
  • 相关阅读:
    vue学习之v-if/v-else/v-else-if
    【python】云打印实现
    React路由与导航
    代码随想录Day61 | 503. 下一个更大元素 II | 42. 接雨水
    maven本地仓库存在jar导包时依然试图远程仓库下载问题解决
    257. 关押罪犯
    SpringCloud Alibaba - Seata 四种分布式事务解决方案(XA、AT)+ 实践部署(上)
    #include<>和#include“”的区别
    基于Springboot社区疫情防控系统设计与实现 毕业设计开题报告
    IDEA实现远程Debug调试
  • 原文地址:https://blog.csdn.net/yuetaope/article/details/123005265