YOLO813

网站嵌入验证码和服务器500错误通知邮件

    在Django如何快速搭建邮件功能文章介绍了如何快速搭建邮件功能,但是也存在一个问题,就是如何防止机器人来发送邮件,毕竟现在的自动化太泛滥了,很多网站都是使用的谷歌插件,应该是很好用的,但我想尝试一下python的方法,所以自己在网上找了一个大神在2016年写的生成验证码的代码,先来看下前端的效果。

    先说下可以实现的功能,任意数字(0-9)+大小写字母(a-zA-Z)随机生成,可以自定义任意位数(我定义的是4位),自定义背景图颜色,文字颜色、字体大小、字体种类,自定义干扰线条数及其颜色(颜色都是通过RGB来控制),是否加入干扰点。


    具体实现过程,根目录下定义了一个utils python包,下面创建了一个captcha包,里面存放了字体ttf文件(字体文件表示方式)和定义验证码的python文件。

    详细的生成代码如下

#mycaptcha.py
import random
# pip install Pillow
# Image:是一个画板(context),ImageDraw:是一个画笔, ImageFont:画笔的字体
from PIL import Image,ImageDraw,ImageFont
import time
from pathlib import Path
import string
from django.urls.conf import path
# Captcha验证码
class Captcha(object):
    # 把一些常量抽取成类属性
    #字体的位置
    font_path = str(Path(__file__).resolve(strict=True).parent) + "/verdana.ttf"
    # font_path = 'utils/captcha/verdana.ttf'
    #生成几位数的验证码
    number = 4
    #生成验证码图片的宽度和高度
    size = (120,40)
    #背景颜色,默认为白色 RGB(Re,Green,Blue)
    bgcolor = (0,0,0)
    #随机字体颜色
    random.seed(int(time.time()))
    fontcolor = (random.randint(200,255),random.randint(100,255),random.randint(100,255))
    # 验证码字体大小
    fontsize = 20
    #随机干扰线颜色。
    linecolor = (random.randint(0,250),random.randint(0,255),random.randint(0,250))
    # 是否要加入干扰线
    draw_line = True
    # 是否绘制干扰点
    draw_point = True
    # 加入干扰线的条数
    line_number = 4
    # 生成a-zA-Z列表
    SOURCE = list(string.ascii_letters)
    # 将数字加入进去
    for index in range(0, 10):
        SOURCE.append(str(index))
    #用来随机生成一个字符串(包括英文和数字)
    # 定义成类方法,然后是私有的,对象在外面不能直接调用
    @classmethod
    def gene_text(cls):
        #number是生成验证码的位数
        return ''.join(random.sample(cls.SOURCE,cls.number))
    #用来绘制干扰线
    @classmethod
    def __gene_line(cls,draw,width,height):
        begin = (random.randint(0, width), random.randint(0, height))
        end = (random.randint(0, width), random.randint(0, height))
        draw.line([begin, end], fill = cls.linecolor)
    # 用来绘制干扰点
    @classmethod
    def __gene_points(cls,draw,point_chance,width,height):
        chance = min(100, max(0, int(point_chance))) # 大小限制在[0, 100]
        for w in range(width):
            for h in range(height):
                tmp = random.randint(0, 100)
                if tmp > 100 - chance:
                    draw.point((w, h), fill=(0, 0, 0))
    #生成验证码
    @classmethod
    def gene_code(cls):
        width,height = cls.size #宽和高
        image = Image.new('RGBA',(width,height),cls.bgcolor) #创建画板
        font = ImageFont.truetype(cls.font_path,cls.fontsize) #验证码的字体
        draw = ImageDraw.Draw(image)  #创建画笔
        text = cls.gene_text() #生成字符串
        font_width, font_height = font.getsize(text)
        draw.text(((width - font_width) / 2, (height - font_height) / 2),text,font= font,fill=cls.fontcolor) #填充字符串
        # 如果需要绘制干扰线
        if cls.draw_line:
            # 遍历line_number次,就是画line_number根线条
            for x in range(0,cls.line_number):
                cls.__gene_line(draw,width,height)
        # 如果需要绘制噪点
        if cls.draw_point:
            cls.__gene_points(draw,10,width,height)
        return (text,image)

以上是利用pillow生成验证码的步骤,我们还需要视图函数中生成验证码图片,

#views.py
from utils.captcha.mycaptcha import Captcha
from django.http import HttpResponse
from io import BytesIO
def img_captcha(request):
    text,image = Captcha.gene_code()
    # BytesIO:相当于一个管道,用来存储图片的流数据
    out = BytesIO()
    # 调用image的save方法,将这个image对象保存到BytesIO中
    image.save(out,'png')
    # 将BytesIO的文件指针移动到最开始的位置
    out.seek(0)
    response = HttpResponse(content_type='image/png')
    # 从BytesIO的管道中,读取出图片数据,保存到response对象上
    response.write(out.read())
    response['Content-length'] = out.tell()
    return response

    这时候,我们再在url中配置一个路径指向这个视图函数就可以生成验证码图片了。这其实还不完整,因为需要验证用户的输入和验证码是否相等,我现在使用的是全局变量来解决,但在谷歌浏览器上经常会有报错发生。理论上最好是在return response之前将text存储到内存中,方便后面的视图函数从内存中取出对比验证码,这可以使用memcached(分布式的高速缓存系统)来解决,由于时间关系,后续再来解决这个问题。

 

    在使用django建站时,我经常会碰到上线网站后,DEBUG为False,这时候服务器报了错,咱也不知道是啥错误,如果是线上线下都有问题还好说,毕竟可以在线下排查出来,最怕的是本地OK,服务器上500错误,这时候就一个光秃秃的500提醒,简直不要太难受,问题都不知道从哪里排查起。

    django也是可以解决的,在之前邮件已经配置好的基础上,再增加一些参数,即可实现自动报告网站错误信息。

# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' 默认
EMAIL_HOST = "smtp.qq.com"
EMAIL_PORT = 465
EMAIL_USE_TLS = False   # 是否使用TLS安全传输协议
#必须添加SERVER_EMAIL才可以发送错误邮件
EMAIL_HOST_USER =SERVER_EMAIL= '123@qq.com'
EMAIL_USE_SSL = True    # 是否使用SSL加密,qq企业邮箱要求使用
EMAIL_HOST_PASSWORD = "secret"
# 收件人
ADMINS = [('zxf','zxf@qq.com'),]

    这个上面添加了SERVER_EMAIL和ADMINS,ADMINS用于定义收件人,这个时候其实已经可以收到网站的错误报告了,但是是密密麻麻的文字,我们可以配置LOGGING来发送相关的网站错误截图(纯粹个人喜好)

LOGGING = {
        'version': 1,
        'disable_existing_loggers': True,
        'formatters': {
        'verbose': {
                'format': '%(levelname)s [%(asctime)s] %(module)s %(message)s'
                },
        },
        'handlers': {
                'mail_admins': {
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler',
            'include_html': True,
                }
        },
        'loggers': {
                'django': {
            'handlers': ['mail_admins'],
            'propagate': False,
            'level': 'DEBUG',
                },
        }
}

效果如下:


参考:

# errors on mail
https://docs.djangoproject.com/zh-hans/3.2/howto/error-reporting/