Files
chunyu_project/utils/captcha.py
T
2026-08-05 23:59:15 +08:00

108 lines
3.1 KiB
Python

import io
import secrets
import uuid
import base64
from PIL import Image, ImageDraw, ImageFont
from django.core.cache import caches
default_cache = caches['default']
CAPTCHA_CHARS = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'
def _random_color(low, high):
return tuple(secrets.randbelow(high - low + 1) + low for _ in range(3))
def generate_captcha():
captcha_key = str(uuid.uuid4())
code = ''.join(secrets.choice(CAPTCHA_CHARS) for _ in range(4))
width, height = 120, 40
image = Image.new('RGB', (width, height), _random_color(200, 255))
draw = ImageDraw.Draw(image)
for _ in range(30):
x = secrets.randbelow(width)
y = secrets.randbelow(height)
draw.point((x, y), fill=_random_color(0, 255))
for _ in range(3):
x1 = secrets.randbelow(width)
y1 = secrets.randbelow(height)
x2 = secrets.randbelow(width)
y2 = secrets.randbelow(height)
draw.line([(x1, y1), (x2, y2)], fill=_random_color(0, 255), width=1)
font = ImageFont.load_default()
for i, char in enumerate(code):
font_size = secrets.randbelow(5) + 12
try:
char_font = ImageFont.load_default(size=font_size)
except TypeError:
char_font = ImageFont.load_default()
char_image = Image.new('RGBA', (30, 30), (0, 0, 0, 0))
char_draw = ImageDraw.Draw(char_image)
char_color = _random_color(0, 100)
char_draw.text((5, 5), char, font=char_font, fill=char_color)
angle = secrets.randbelow(61) - 30
rotated = char_image.rotate(angle, expand=True, resample=Image.BICUBIC)
x_offset = i * 25 + secrets.randbelow(6)
y_offset = secrets.randbelow(10)
image.paste(rotated, (x_offset, y_offset), rotated)
buffer = io.BytesIO()
image.save(buffer, format='PNG')
image_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
captcha_image = f'data:image/png;base64,{image_base64}'
default_cache.set(f'captcha_{captcha_key}', code.upper(), timeout=300)
return captcha_key, captcha_image
def verify_captcha(captcha_key, captcha_code):
cache_key = f'captcha_{captcha_key}'
stored_code = default_cache.get(cache_key)
if stored_code is None:
return 'expired'
if stored_code == captcha_code.upper():
default_cache.delete(cache_key)
return 'ok'
return 'wrong'
CAPTCHA_THRESHOLD = 3
CAPTCHA_TRIGGER_TTL = 600
def check_captcha_required(operation, identifier):
cache_key = f'captcha_trigger_{operation}_{identifier}'
count = default_cache.get(cache_key)
if count is not None and count >= CAPTCHA_THRESHOLD:
return True
return False
def record_failure(operation, identifier):
cache_key = f'captcha_trigger_{operation}_{identifier}'
count = default_cache.get(cache_key)
if count is None:
default_cache.set(cache_key, 1, timeout=CAPTCHA_TRIGGER_TTL)
else:
default_cache.set(cache_key, count + 1, timeout=CAPTCHA_TRIGGER_TTL)
def reset_failures(operation, identifier):
cache_key = f'captcha_trigger_{operation}_{identifier}'
default_cache.delete(cache_key)