141 lines
4.9 KiB
Python
141 lines
4.9 KiB
Python
|
# _*_ 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": "验证码错误"
|
|||
|
}
|