141 lines
4.9 KiB
Python
Raw Normal View History

2025-02-13 02:27:44 +08:00
# _*_ coding : UTF-8 _*_
# @Time : 2025/01/26 20:59
# @UpdateTime : 2025/01/26 20:59
# @Author : sonder
# @File : captcha.py
# @Software : PyCharm
# @Comment : 本程序
import base64
import io
import os
import random
import string
from PIL import Image, ImageDraw, ImageFont
from fastapi import Request
from config.constant import RedisKeyConfig
class Captcha:
"""
验证码类
"""
@classmethod
async def create_captcha(cls, captcha_type: str = "0"):
"""
生成验证码
:param captcha_type: 验证码类型0为数字1为数字和字母
:return: 验证码图片和验证码base64字符串
"""
# 创建空白图像
image = Image.new('RGB', (120, 40), color='#EAEAEA')
draw = ImageDraw.Draw(image)
# 设置字体
font_path = os.path.join(os.path.abspath(os.getcwd()), 'assets', 'font', 'MiSans-Medium.ttf')
font = ImageFont.truetype(font_path, size=25)
if captcha_type == '0':
# 生成两个0-9之间的随机整数
num1 = random.randint(0, 9)
num2 = random.randint(0, 9)
# 从运算符列表中随机选择一个
operational_character_list = ['+', '-', '*']
operational_character = random.choice(operational_character_list)
# 根据选择的运算符进行计算
if operational_character == '+':
result = num1 + num2
elif operational_character == '-':
result = num1 - num2
else:
result = num1 * num2
# 生成算术题文本
text = f'{num1} {operational_character} {num2} = ?'
# 计算文本宽度以居中显示
text_width = draw.textlength(text, font=font)
x = (120 - text_width) / 2
draw.text((x, 5), text, fill='blue', font=font)
else:
# 生成随机字母和数字组合
result = ''.join(random.choices(string.ascii_letters + string.digits, k=4))
# 绘制每个字符,并添加随机旋转和倾斜
x = 10
for char in result:
# 创建单个字符的图像
char_image = Image.new('RGBA', (25, 40), color=(234, 234, 234, 0))
char_draw = ImageDraw.Draw(char_image)
char_draw.text((0, 0), char, font=font, fill=(0, 0, 255))
# 随机旋转字符
char_image = char_image.rotate(random.randint(-40, 40), expand=1)
# 随机倾斜字符
char_image = char_image.transform(char_image.size, Image.AFFINE,
(1, random.uniform(-0.3, 0.3), 0, 0, 1, 0))
# 将字符粘贴到主图像上
image.paste(char_image, (x, 0), char_image)
x += 25
# 添加干扰元素
cls._add_noise(image)
cls._add_lines(image)
# 将图像数据保存到内存中
buffer = io.BytesIO()
image.save(buffer, format='PNG')
# 将图像数据转换为base64字符串
base64_string = base64.b64encode(buffer.getvalue()).decode()
return [base64_string, result]
@staticmethod
def _add_noise(image):
"""
添加噪点干扰
"""
draw = ImageDraw.Draw(image)
for _ in range(150): # 添加100个噪点
x = random.randint(0, 120)
y = random.randint(0, 40)
draw.point((x, y), fill=(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))
@staticmethod
def _add_lines(image):
"""
添加干扰线
"""
draw = ImageDraw.Draw(image)
for _ in range(10): # 添加5条干扰线
x1 = random.randint(0, 120)
y1 = random.randint(0, 40)
x2 = random.randint(0, 120)
y2 = random.randint(0, 40)
draw.line((x1, y1, x2, y2),
fill=(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)),
width=1)
@classmethod
async def verify_code(cls, request: Request, code: str, session_id: str) -> dict:
"""
验证验证码
:param request
:param code: 验证码
:param session_id: 会话ID
"""
redis_code = await request.app.state.redis.get(f"{RedisKeyConfig.CAPTCHA_CODES.key}:{session_id}")
if redis_code is None:
return {
"status": False,
"msg": "验证码已过期"
}
if str(redis_code).lower() == code.lower():
await request.app.state.redis.delete(f"{RedisKeyConfig.CAPTCHA_CODES.key}:{session_id}")
return {
"status": True,
"msg": "验证码正确"
}
return {
"status": False,
"msg": "验证码错误"
}