本篇主要介紹圖片驗證碼功能的實現,其可能用到第三方庫PIL,以及前端發送請求、后端生成驗證碼圖片並且返回給前端,前端渲染圖片驗證碼的整個邏輯。
一、前端邏輯實現
首先是前端的HTML代碼:
<div class="form_group"> <input type="text" name="code_pwd" id="imagecode" class="code_pwd"> <div class="input_tip">圖形驗證碼</div> <img src="../../static/news/images/pic_code.png" class="get_pic_code" onclick="generateImageCode()"> <div id="register-image-code-err" class="error_tip">圖形碼不能為空</div> </div>
由於我們需要做局部刷新、異步的效果,故這里我們使用ajax請求圖片驗證碼:
// 用於生成UUID function generateUUID() { // 作為后端要使用可以百度,直接獲取 var d = new Date().getTime(); if(window.performance && typeof window.performance.now === "function"){ d += performance.now(); //use high-precision timer if available } var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = (d + Math.random()*16)%16 | 0; d = Math.floor(d/16); return (c=='x' ? r : (r&0x3|0x8)).toString(16); }); return uuid; } var imageCodeId = ""; // 生成一個圖片驗證碼的編號,並設置頁面中圖片驗證碼img標簽的src屬性 function generateImageCode() { // 調用函數生成UUID imageCodeId = generateUUID(); //拼接請求路徑 url = "/passport/image_code?code_id=" + imageCodeId; // 設置對應的src屬性為該url $(".get_pic_code").attr("src", url) }
這里我們在生成圖片驗證碼時,前端需要生成一個UUID作為code_id參數傳給后端,用來區分每一個圖片驗證(因為在實際生產環境中,可能同時會有多個用戶去請求圖片驗證碼,UUID能保證該圖片驗證碼的唯一性)。
二、后端邏輯的實現
這里我們可能用到調用第三方庫PIL去生成圖片驗證碼,生成圖片驗證碼的邏輯如下:

#!/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() def generate_captcha(self): self.initialize() return self.captcha("") captcha = Captcha.instance() if __name__ == '__main__': print(captcha.generate_captcha())
我們需要定義視圖函數去接收前端發送過來的請求,並通過 獲取參數、校驗參數、業務邏輯(生成圖片驗證碼、保存到redis數據庫中)、返回響應來完成圖片驗證碼的邏輯。
@passport_blu.route("/image_code") def generate_image_code(): '''圖片驗證碼''' # 獲取圖片驗證碼的UUID即code_id image_code_id = request.args.get("code_id") if not image_code_id: abort(403) # 生成圖片驗證碼,其返回為名字、文本、以及圖片 name, text, image = captcha.generate_captcha() # name,text, image = generate_code() print("圖片驗證碼是:{}".format(text)) # 將圖形驗證碼保存到redis數據庫中 try: # 保存當前生成的圖片驗證碼內容,並且設置過期時間 redis_store.setex('ImageCode_' + image_code_id, constants.IMAGE_CODE_REDIS_EXPIRES, text) except Exception as e: current_app.logger.error(e) response = make_response(image) # 設置請求頭屬性-Content-Type響應的格式 response.headers["Content-Type"] = "image/png" return response
總結:
1、通過分析頁面需求,前端發送請求(根據需求選擇不同的請求方式,並攜帶請求參數)
2、后端接收請求,實現業務邏輯,返回響應
3、前端得到響應,實現相關效果。