基于sesson,实现字符图片验证码,点击验证码时刷新,效果如下:

一、验证码生成原理:

1、在html中添加验证码图片,通过src连接至CheckCodeHandler,

2、CheckCodeHandler中生成验证码、带验证码的图片,

3、将验证码写入sesson,

二、验证码使用步骤:

1、form表单提交浏览器输入验证码,

2、取sesson中保存的验证码进行比对,

三、验证码刷新

1、浏览器点击验证码文件,激发js函数,

2、js函数重新发送src请求,即重新刷新验证码图片,

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import tornado.ioloop
import tornado.web

container = {}

class Sesson:
    def __init__(self,handler):
        self.handler = handler
        self.random_str = None

    def __generate_random_str(self):
        # 生成一组随机码(这里使用的是当前时间)
        import hashlib
        import time
        obj = hashlib.md5()
        obj.update(bytes(str(time.time()),encoding='utf-8'))
        random_str = obj.hexdigest()
        return random_str

    def __setitem__(self, key, value):
        # 生成随机码对应的容器,保存客户信息,将随机码写入客户端
        if not self.random_str:                     # 一次请求,多次操作时,避免重复刷新随机码
            random_str = str(self.handler.get_secure_cookie('kkk'),encoding='utf-8')                    # 获取客户端随机码
            if not random_str:                                                                 # 如果客户端没有随机码,生成
                random_str = self.__generate_random_str()
                container[random_str] = {}
            else:
                if random_str in container.keys():                             # 如果客户端有随机码,且与服务器端一致,保留
                    pass
                else:                                                  # 如果客户端有随机码,但是与服务器端不一致,重新生成
                    random_str = self.__generate_random_str()
                    container[random_str] = {}
            self.random_str = random_str
        container[self.random_str][key] = value
        self.handler.set_secure_cookie('kkk',self.random_str)

    def __getitem__(self, key):
        # 获取客户端的随机码,提取数据
        random_str = str(self.handler.get_secure_cookie('kkk'),encoding='utf-8')                    # 获取客户端随机码
        if not random_str:                                                     # 如果客户端没有随机码,取不到值,返回空
            return None
        user_info_dict = container.get(random_str,None)            # 如果客户端有随机码,但是与服务器端不匹配,也返回空
        if not user_info_dict:
            return None
        val = user_info_dict.get(key,None)                         # 如果客户端有随机码,且与服务器端一致,返回相应的值
        return val

class BaseHandler(tornado.web.RequestHandler):
    def initialize(self):
        self.sesson = Sesson(self)

class IndexHandler(BaseHandler):
    def get(self):
        if self.get_argument('u',None) in ['lucy','luna','dongxue']:
            self.sesson['is_login'] = True
            self.sesson['name'] = self.get_argument('u',None)
            print(container)
        else:
            self.write("请注册!")

class ManagerHandler(BaseHandler):
    def get(self):
        val = self.sesson['is_login']
        if val:
            self.write(self.sesson['name'])
        else:
            self.write("失败!")

class LoginHandler(BaseHandler):
    def get(self):
        self.render('login.html',status = "")
    def post(self, *args, **kwargs):
        user = self.get_argument('user')
        pwd = self.get_argument('pwd')
        code = self.get_argument('code')
        check_code = self.sesson["CheckCode"]
        if code.upper() == check_code.upper():
            self.write("验证码正确")
        else:
            self.render('login.html',status = "验证码错误")

class CheckCodeHandler(BaseHandler):
    def get(self):
        import io
        import check_code
        mstream = io.BytesIO()                              # 相当于创建一个内存IO文件,可读写内容
        img, code = check_code.create_validate_code()       # 创建图片对象img,并写上验证码code,
        img.save(mstream, "GIF")                            # 将图片对象写入mstream
        self.sesson["CheckCode"] = code
        self.write(mstream.getvalue())


# 路径解析
settings = {
    "template_path":"views",
    "static_path":"statics",
    "cookie_secret":"nifjewoifnewkfcn",
}

# 二级路由,先匹配域名,
application = tornado.web.Application([
    (r"/index",IndexHandler),
    (r"/manager",ManagerHandler),
    (r"/login",LoginHandler),
    (r"/check_code",CheckCodeHandler),
],**settings)


# 开启服务器,监听
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
View Code

核心代码check_code.py,其中使用字体文件Monaco.ttf,:

#!/usr/bin/env python
#coding:utf-8

import random
from PIL import Image, ImageDraw, ImageFont, ImageFilter

_letter_cases = "abcdefghjkmnpqrstuvwxy"  # 小写字母,去除可能干扰的i,l,o,z
_upper_cases = _letter_cases.upper()  # 大写字母
_numbers = ''.join(map(str, range(3, 10)))  # 数字
init_chars = ''.join((_letter_cases, _upper_cases, _numbers))

def create_validate_code(size=(120, 30),
                         chars=init_chars,
                         img_type="GIF",
                         mode="RGB",
                         bg_color=(255, 255, 255),
                         fg_color=(0, 0, 255),
                         font_size=18,
                         font_type="Monaco.ttf",
                         length=4,
                         draw_lines=True,
                         n_line=(1, 2),
                         draw_points=True,
                         point_chance = 2):
    '''
    @todo: 生成验证码图片
    @param size: 图片的大小,格式(宽,高),默认为(120, 30)
    @param chars: 允许的字符集合,格式字符串
    @param img_type: 图片保存的格式,默认为GIF,可选的为GIF,JPEG,TIFF,PNG
    @param mode: 图片模式,默认为RGB
    @param bg_color: 背景颜色,默认为白色
    @param fg_color: 前景色,验证码字符颜色,默认为蓝色#0000FF
    @param font_size: 验证码字体大小
    @param font_type: 验证码字体,默认为 ae_AlArabiya.ttf
    @param length: 验证码字符个数
    @param draw_lines: 是否划干扰线
    @param n_lines: 干扰线的条数范围,格式元组,默认为(1, 2),只有draw_lines为True时有效
    @param draw_points: 是否画干扰点
    @param point_chance: 干扰点出现的概率,大小范围[0, 100]
    @return: [0]: PIL Image实例
    @return: [1]: 验证码图片中的字符串
    '''

    width, height = size # 宽, 高
    img = Image.new(mode, size, bg_color) # 创建图形
    draw = ImageDraw.Draw(img) # 创建画笔

    def get_chars():
        '''生成给定长度的字符串,返回列表格式'''
        return random.sample(chars, length)

    def create_lines():
        '''绘制干扰线'''
        line_num = random.randint(*n_line) # 干扰线条数

        for i in range(line_num):
            # 起始点
            begin = (random.randint(0, size[0]), random.randint(0, size[1]))
            #结束点
            end = (random.randint(0, size[0]), random.randint(0, size[1]))
            draw.line([begin, end], fill=(0, 0, 0))

    def create_points():
        '''绘制干扰点'''
        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))

    def create_strs():
        '''绘制验证码字符'''
        c_chars = get_chars()
        strs = ' %s ' % ' '.join(c_chars) # 每个字符前后以空格隔开

        font = ImageFont.truetype(font_type, font_size)
        font_width, font_height = font.getsize(strs)

        draw.text(((width - font_width) / 3, (height - font_height) / 3),
                    strs, font=font, fill=fg_color)

        return ''.join(c_chars)

    if draw_lines:
        create_lines()
    if draw_points:
        create_points()
    strs = create_strs()

    # 图形扭曲参数
    params = [1 - float(random.randint(1, 2)) / 100,
              0,
              0,
              0,
              1 - float(random.randint(1, 10)) / 100,
              float(random.randint(1, 2)) / 500,
              0.001,
              float(random.randint(1, 2)) / 500
              ]
    img = img.transform(size, Image.PERSPECTIVE, params) # 创建扭曲

    img = img.filter(ImageFilter.EDGE_ENHANCE_MORE) # 滤镜,边界加强(阈值更大)

    return img, strs
View Code

文件login.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>
    <form action="/login" method="post">
        <p><input name="user" type="text" placeholder="用户" /></p>
        <p><input name="pwd" type="text" placeholder="密码" /></p>
        <p>
            <input name="code" type="text" placeholder="验证码" />
            <img src="/check_code" id="check_code" onclick="ChangeCode()">
        </p>
        <p><input type="submit" value="Submit" /><span style="color:red">{{status}}</span></p>
    </form>
    <script type="text/javascript">
        function ChangeCode(){
            var node = document.getElementById("check_code")
            node.src += '?'         <!--刷新src-->
        }
    </script>
</body>
</html>
View Code