captcha_util.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. # -*- coding: utf-8 -*-
  2. import base64
  3. import random
  4. import string
  5. from io import BytesIO
  6. from typing import Tuple
  7. from PIL import Image, ImageDraw, ImageFont
  8. from app.config.setting import settings
  9. class CaptchaUtil:
  10. """
  11. 验证码工具类
  12. """
  13. @classmethod
  14. def generate_captcha(cls) -> Tuple[str, str]:
  15. """
  16. 生成带有噪声和干扰的验证码图片(4位随机字符)。
  17. 返回:
  18. - Tuple[str, str]: [base64图片字符串, 验证码值]。
  19. """
  20. # 生成4位随机验证码
  21. chars = string.digits + string.ascii_letters
  22. captcha_value = ''.join(random.sample(chars, 4))
  23. # 创建一张随机颜色背景的图片
  24. width, height = 160, 60
  25. background_color = tuple(random.randint(230, 255) for _ in range(3))
  26. image = Image.new('RGB', (width, height), color=background_color)
  27. draw = ImageDraw.Draw(image)
  28. # 使用指定字体
  29. font = ImageFont.truetype(font=settings.CAPTCHA_FONT_PATH, size=settings.CAPTCHA_FONT_SIZE)
  30. # 计算文本总宽度和高度
  31. total_width = sum(draw.textbbox((0, 0), char, font=font)[2] for char in captcha_value)
  32. text_height = draw.textbbox((0, 0), captcha_value[0], font=font)[3]
  33. # 计算起始位置,使文字居中
  34. x_start = (width - total_width) / 2
  35. y_start = (height - text_height) / 2 - draw.textbbox((0, 0), captcha_value[0], font=font)[1]
  36. # 绘制字符
  37. x = x_start
  38. for char in captcha_value:
  39. # 使用深色文字,增加对比度
  40. text_color = tuple(random.randint(0, 80) for _ in range(3))
  41. # 随机偏移,增加干扰
  42. x_offset = x + random.uniform(-2, 2)
  43. y_offset = y_start + random.uniform(-2, 2)
  44. # 绘制字符
  45. draw.text((x_offset, y_offset), char, font=font, fill=text_color)
  46. # 更新x坐标,增加字符间距的随机性
  47. x += draw.textbbox((0, 0), char, font=font)[2] + random.uniform(1, 5)
  48. # 添加干扰线
  49. for _ in range(4):
  50. line_color = tuple(random.randint(150, 200) for _ in range(3))
  51. points = [(i, int(random.uniform(0, height))) for i in range(0, width, 20)]
  52. draw.line(points, fill=line_color, width=1)
  53. # 添加随机噪点
  54. for _ in range(width * height // 60):
  55. point_color = tuple(random.randint(0, 255) for _ in range(3))
  56. draw.point(
  57. (random.randint(0, width), random.randint(0, height)),
  58. fill=point_color
  59. )
  60. # 将图像数据保存到内存中并转换为base64
  61. buffer = BytesIO()
  62. image.save(buffer, format='PNG', optimize=True)
  63. base64_string = base64.b64encode(buffer.getvalue()).decode()
  64. return base64_string, captcha_value
  65. @classmethod
  66. def captcha_arithmetic(cls) -> Tuple[str, int]:
  67. """
  68. 创建验证码图片(加减乘运算)。
  69. 返回:
  70. - Tuple[str, int]: [base64图片字符串, 计算结果]。
  71. """
  72. # 创建空白图像,使用随机浅色背景
  73. background_color = tuple(random.randint(230, 255) for _ in range(3))
  74. image = Image.new('RGB', (160, 60), color=background_color)
  75. draw = ImageDraw.Draw(image)
  76. # 设置字体
  77. font = ImageFont.truetype(font=settings.CAPTCHA_FONT_PATH, size=settings.CAPTCHA_FONT_SIZE)
  78. # 生成运算数字和运算符
  79. operators = ['+', '-', '*']
  80. operator = random.choice(operators)
  81. # 对于减法,确保num1大于num2
  82. if operator == '-':
  83. num1 = random.randint(6, 10)
  84. num2 = random.randint(1, 5)
  85. else:
  86. num1 = random.randint(1, 9)
  87. num2 = random.randint(1, 9)
  88. # 计算结果
  89. result_map = {
  90. '+': lambda x, y: x + y,
  91. '-': lambda x, y: x - y,
  92. '*': lambda x, y: x * y
  93. }
  94. captcha_value = result_map[operator](num1, num2)
  95. # 绘制文本,使用深色增加对比度
  96. text = f'{num1} {operator} {num2} = ?'
  97. text_bbox = draw.textbbox((0, 0), text, font=font)
  98. text_width = text_bbox[2] - text_bbox[0]
  99. x = (160 - text_width) // 2
  100. draw.text((x, 15), text, fill=(0, 0, 139), font=font)
  101. # 添加干扰线
  102. for _ in range(3):
  103. line_color = tuple(random.randint(150, 200) for _ in range(3))
  104. draw.line([
  105. (random.randint(0, 160), random.randint(0, 60)),
  106. (random.randint(0, 160), random.randint(0, 60))
  107. ], fill=line_color, width=1)
  108. # 将图像数据保存到内存中并转换为base64
  109. buffer = BytesIO()
  110. image.save(buffer, format='PNG', optimize=True)
  111. base64_string = base64.b64encode(buffer.getvalue()).decode()
  112. return base64_string, captcha_value