python验证码,python将PIL生成的图片进行base64编码并返回给前端浏览器

我用python的PIL生成了验证码图片,需要将图片返回给前端浏览器,有两种返回方式。一种是返回response,另一种是将图片进行base64编码后返回。

注意:PIL生成验证码图片时,需要传入font参数,是字体文件,从系统中复制出来的字体文件不能改名,而且它的路径最好写绝对路径,否则在服务器运行或者说命令行运行时,会找不到这个字体文件,导致无法生成验证码图片,去服务器日志查看有报错OSError: cannot open resource。若是在本地用pycharm运行,不写绝对路径也没有这个问题,详细原因看另一篇文章

先放两种方法的服务端公共代码

def random_string(length=32): # 生成32位随机字符串,原本是为了生成随机文件名,用于保存上传的头像,所以默认32位
    import random
    string='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
    return ''.join(random.choice(string) for i in range(length))


def random_color():
    r=random.randint(0,255)
    g=random.randint(0,255)
    b=random.randint(0,255)
    if r*0.299+g*0.578+b*0.114>=100: # 它们的和越小,颜色越深。from https://blog.csdn.net/qq_36843675/article/details/79448051和https://blog.csdn.net/dKnightL/article/details/107222075
        return (r,g,b)
    else:
        return random_color() # 递归,直到满足条件才停止,避免产生深色画布


def generate_verification_code_image(length=4):
    size=130,50
    image=Image.new('RGB',size=size,color=random_color()) # 创建画布,返回Image对象。https://pillow.readthedocs.io/en/stable/
    font=ImageFont.truetype(os.path.abspath(os.path.dirname(__file__))+'/times.ttf',size=40) # 创建字体,是系统中的Times New Roman 常规,从系统中复制出来就是这个文件名,不能修改文件名,否则前端加载不出验证码图片(服务端控制台报错OSError: cannot open resource),即访问者的系统中需有这个字体。不建议只写'times.ttf',虽然和代码文件在同一个目录,但服务器命令行找不到字体文件('./times.ttf'也不行),pycharm可以,所以为了跨平台,用绝对路径
    draw=ImageDraw.Draw(image) # 创建ImageDraw对象,相当于创建画笔,参数是画布
    str=random_string(length)
    for i,value in enumerate(str): # 绘制验证码
        draw.text((5+random.randint(4,7)+25*i,1+random.randint(2,8)+2*i),text=value,fill=random_color(),font=font) # 第一个参数是每个字符的左上角的坐标,横坐标里25是固定字符间距,前面是随机字符间距,实际字符间距等于它们的和;text是要绘制的字符,fill是字符颜色
    for j in range(random.randint(5,8)): # 绘制干扰直线
        # 将画布平均分成上下两部分,使每条线的第一个端点在上部分,第二个端点在下部分,干扰效果更好
        x1=random.randint(0,130)
        y1=random.randint(0,50/2)
        x2=random.randint(0,130)
        y2=random.randint(50/2,50)
        draw.line(((x1,y1),(x2,y2)),fill=random_color()) # 第一个参数是每条线的端点坐标,fill是线条颜色
    for k in range(random.randint(5,8)): # 绘制干扰弧线from https://blog.csdn.net/qq_34356800/article/details/86744042,将他的size[0]改成了130,size[1]改成了50
        start=(-50,-50) # 起始位置在外边看起来才会像弧线
        end=(130+10,random.randint(0,50+10))
        draw.arc(start+end,0,360,fill=random_color())
    for m in range(130): # 绘制干扰点
        for n in range(50):
            number=random.randint(1,100)
            if number>90: # 只有10%的概率画点,概率再大的话影响人识别
                draw.point((m,n),fill=random_color())
    '''
    for i in range(40): # 绘制干扰点和干扰线 from https://www.cnblogs.com/Zzbj/p/13797672.html
        draw.point([random.randint(0,130),random.randint(0,50)],fill=random_color())
        x=random.randint(0,130)
        y=random.randint(0,50)
        draw.arc((x,y,x+4,y+4),0,90,fill=random_color())
    '''
    image=image.filter(ImageFilter.SMOOTH) # 返回添加了过滤器的Image对象,SMOOTH让图片模糊
    # image.show() # 展示图片
    return str,image

一.返回response

服务端主要代码

@user.route('/verification_code_image')
def verification_code_image():
    verification_code,image=generate_verification_code_image(4)
    session['verification_code']=verification_code # 多人访问时,验证码会存在各自的session里,不会发生后访问的人的验证码覆盖前面的人的验证码。若放在redis缓存里,写cache.set('verification_code',verification_code,timeout=120)会发生前面说的覆盖的情况,因每次存入redis的键值对的键相同
    buffer=BytesIO()
    image.save(buffer,"PNG") # 将Image对象转为二进制存入buffer。因BytesIO()是在内存中操作,所以实际是存入内存
    buf_bytes=buffer.getvalue() # 从内存中取出bytes类型的图片
    response=make_response(buf_bytes)
    response.headers['Content-Type']='image/png' # 设置请求头,文件格式与前面save时一致
    return response

上面的代码是服务端验证验证码是否正确,也可以在前端验证,只需将验证码字符串的存放位置由session(或redis)改为cookie,前端验证时从cookie中取值(cookie是存在本地的),这样可以节省服务器和数据库资源开销。

response参考链接1,参考链接2,参考链接3,参考链接4

前端主要代码

<script>
        $(function (){ //使用jQuery时,JavaScript代码直接写在$()内
            $('#recaptcha').after('<img src=\"{{ url_for("user.verification_code_image") }}\" id=\"img\" alt=\"验证码图片加载失败\" />') //三层引号嵌套from https://blog.csdn.net/feiyangbaxia/article/details/49681131和https://blog.csdn.net/zhongyangjian/article/details/46010157
            $('#img').click(function (){
                $(this).attr('src',"{{ url_for('user.verification_code_image') }}?ran="+Math.random()) //带一个随机数,否则点击时请求的路由与点击前加载的一致,服务端会返回相同的结果,即点击前加载的结果
            })
        })
</script>

二.将图片进行base64编码后返回

由于验证码图片很小,所以base64编码后不太影响网页性能

服务端主要代码

@user.route('/verification_code_image')
def verification_code_image():
    verification_code,image=generate_verification_code_image(4)
    session['verification_code']=verification_code # 多人访问时,验证码会存在各自的session里,不会发生后访问的人的验证码覆盖前面的人的验证码。若放在redis缓存里,写cache.set('verification_code',verification_code,timeout=120)会发生前面说的覆盖的情况,因每次存入redis的键值对的键相同
    buffer=BytesIO()
    image.save(buffer,"PNG") # 将Image对象转为二进制存入buffer。因BytesIO()是在内存中操作,所以实际是存入内存
    buf_bytes=buffer.getvalue() # 从内存中取出bytes类型的图片
    base64_data=base64.b64encode(buf_bytes) # 将bytes类型按base64进行编码,返回值还是bytes类型
    with open('1.txt','wb') as file:
        file.write(b'data:image/png;base64,'+base64_data) # 将1.txt中的内容复制到浏览器地址栏,直接访问就显示图片
    base64_data2='data:image/png;base64,'+str(base64.b64encode(buf_bytes),'utf-8') # 1.txt中的内容和base64_data2一样,也可以用base64_data3的写法
    # base64_data3=(b'data:image/png;base64,'+base64_data).decode('utf-8')
    return render_template('index.html',result=base64_data2)

base64参考链接5,参考链接6,参考链接7

前端主要代码

<img src="{{result}}">

对于方式二,如果服务端像return response那样直接return base64_data2,前端也用方式一的代码,则浏览器无法加载验证码图片,因为此时浏览器的Elements里验证码标签的src是src="/user/verification_code_image",问题出在url_for()。而方式二的浏览器的Elements里验证码标签的src是src="很长的字符串",这个很长的字符串可以手动复制到浏览器地址栏访问,也能显示验证码图片,类似通过网址或本地路径加载图片。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值