最近因为业务需求,需要重写图形验证码部分。
使用的是Python3.6,代码很简单,看一眼基本就知道其中原理,这里仅作记录。
from PIL import (
Image, ImageDraw, ImageFont, ImageFilter
)
from django.core.cache import cache
from django.conf import settings
from io import BytesIO
import os, random, string, time
class Captcha:
"""
TODO: 自定义生成图形验证码
using: captcha 获取图形验证码
using: verify_captcha 验证图形验证码
"""
def __init__(self):
self._code = string.ascii_uppercase + string.digits
self._width = 100 # 图片宽
self._height = 40 # 图片高
self._bits = 4
self._draw_line = True # 干扰线
self._line_num = (1, 5) # 干扰线数量
self._bgcolor = random.randint(0, 255), random.randint(0, 255), random.randint(0, 255) # 背景颜色
self._font_path = os.path.join(settings.BASE_DIR, 'captcha/fonts/Vera.ttf')
self.captcha_code = self._generate_shuffle_str()
# 生成随机字符串
def _generate_shuffle_str(self):
shuffle_list = ','.join(self._code).split(',')
random.shuffle(shuffle_list)
return ''.join(shuffle_list[:self._bits])
# 生成图像
def _generate_image(self):
image = Image.new('RGBA', (self._width, self._height), self._bgcolor) # 画布
font = ImageFont.truetype(self._font_path, 24) # 用到的字体
draw = ImageDraw.Draw(image) # 画笔
text = self._generate_shuffle_str()
# 在画布上画字着色
for i in range(len(text)):
font_width, font_height = font.getsize(text[i])
draw.text((self._width / self._bits * (i + 1) - font_width,
(self._height - font_height) / random.randint(2, self._bits)),
text[i],
font=font, fill=(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))
# 画上干扰线
if self._draw_line:
self._append_line(draw)
# 画上躁点
self._append_points(draw)
# 应用图形变换
image = image.transform((self._width, self._height),
Image.AFFINE,
(1, 0, 0, 0, 1, 0),
Image.BILINEAR) # 创建扭曲
image = image.filter(ImageFilter.EDGE_ENHANCE_MORE) # 汉斯滤镜, 边界加强
return image
# 追加躁点
def _append_points(self, draw):
chance = min(100, max(0, 5))
for w in range(self._width):
for h in range(self._height):
tmp = random.randint(0, 100)
if tmp > 100 - chance:
draw.point((w, h), fill=(0, 0, 0))
# 追加干扰线
def _append_line(self, draw):
for _ in range(random.randint(*self._line_num)):
begin = random.randint(0, self._width), random.randint(0, self._height)
end = random.randint(0, self._width), random.randint(0, self._height)
draw.line([begin, end], fill=(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))
def captcha(self):
buf = BytesIO()
im = self._generate_image()
tk = self._cache_captcha()
im.save(buf, 'JPEG')
im.close()
buf.seek(0)
return tk, buf
# 临时记录图形验证码
def _cache_captcha(self):
timestamp_key = int(time.time() * 100000000) + random.randint(10, 99)
cache.set(timestamp_key, self.captcha_code, 300)
return timestamp_key
# 验证图形验证码
def verify_captcha(self, timestamp_key=None, captcha=None):
if timestamp_key and captcha and captcha == cache.get(timestamp_key):
# del the cache data
cache.delete(timestamp_key)
return True
return False
因为我的设计验证是key:value形式,所以要把key也传给移动端,这里我使用了下面的方法:
from django.http import JsonResponse, HttpResponse
from django.views.decorators.csrf import csrf_exempt
from .captcha import Captcha
from base64 import b64encode
@csrf_exempt
def get_captcha(request):
captcha = Captcha()
tk, im_buf = captcha.captcha()
# return HttpResponse(im_buf, content_type='image/jpeg')
return JsonResponse({'recode': 1,
'remsg': '获取成功!',
'data': {'timestamp': tk, 'captcha': b64encode(im_buf.read()).decode('utf-8')}})
注释的部分用于在浏览器里面查看图片验证码。
这里在BytesIO那里踩了点坑,主要还是怪自己学艺不精。
-- 路漫漫其修远兮,吾将上下而求索。
网友评论