# _*_ 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": "验证码错误" }