diff --git a/file-upload-site/Dockerfile b/file-upload-site/Dockerfile index 47ac341..2c37f8c 100644 --- a/file-upload-site/Dockerfile +++ b/file-upload-site/Dockerfile @@ -1,8 +1,8 @@ FROM python:3.10-slim -# 系统依赖(python-magic 需要 libmagic) +# 系统依赖(python-magic 需要 libmagic,验证码需要清晰字体) RUN apt-get update && \ - apt-get install -y --no-install-recommends libmagic1 && \ + apt-get install -y --no-install-recommends libmagic1 fonts-dejavu-core && \ rm -rf /var/lib/apt/lists/* WORKDIR /app diff --git a/file-upload-site/app.py b/file-upload-site/app.py index 1350a28..040f9af 100644 --- a/file-upload-site/app.py +++ b/file-upload-site/app.py @@ -38,6 +38,16 @@ NAME_REGEX = re.compile(r'^[\u4e00-\u9fa5a-zA-Z0-9_]{2,20}$') # 文件名中不安全的字符 UNSAFE_FILENAME_CHARS = re.compile(r'[<>:"/\\|?*\x00-\x1f]') +# 验证码图片参数 +CAPTCHA_IMAGE_SIZE = (320, 100) +CAPTCHA_FONT_SIZE = 68 +CAPTCHA_FONT_PATHS = ( + "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", + "/usr/local/share/fonts/DejaVuSans-Bold.ttf", + "/System/Library/Fonts/Supplemental/Arial Bold.ttf", + "/System/Library/Fonts/Supplemental/Verdana Bold.ttf", +) + # ── 工具函数 ──────────────────────────────────────────────────────── def sanitize_name(name: str) -> str | None: @@ -88,9 +98,25 @@ def generate_captcha_text(length: int = 4) -> str: return ''.join(random.choices(chars, k=length)) +def load_captcha_font(size: int = CAPTCHA_FONT_SIZE) -> ImageFont.ImageFont: + """加载适合验证码的大号字体,兼容 Linux/Docker 和 macOS 本地环境""" + for font_path in CAPTCHA_FONT_PATHS: + if not os.path.exists(font_path): + continue + try: + return ImageFont.truetype(font_path, size) + except OSError: + continue + + try: + return ImageFont.truetype("DejaVuSans-Bold.ttf", size) + except OSError: + return ImageFont.load_default(size=size) + + def generate_captcha_image(text: str) -> Image.Image: """生成验证码图片(含干扰线和噪点)""" - width, height = 320, 100 + width, height = CAPTCHA_IMAGE_SIZE # 浅色随机背景 bg_color = (random.randint(235, 255), random.randint(235, 255), random.randint(235, 255)) image = Image.new('RGB', (width, height), bg_color) @@ -109,15 +135,15 @@ def generate_captcha_image(text: str) -> Image.Image: dot_color = (random.randint(140, 200), random.randint(140, 200), random.randint(140, 200)) draw.point((x, y), fill=dot_color) - # 绘制文字 - try: - font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 65) - except OSError: - font = ImageFont.load_default() + font = load_captcha_font() + char_slot_width = width // len(text) for i, char in enumerate(text): - x = 20 + i * 72 - y = random.randint(6, 18) + bbox = draw.textbbox((0, 0), char, font=font) + char_width = bbox[2] - bbox[0] + char_height = bbox[3] - bbox[1] + x = i * char_slot_width + (char_slot_width - char_width) // 2 - bbox[0] + random.randint(-4, 4) + y = (height - char_height) // 2 - bbox[1] + random.randint(-5, 5) color = (random.randint(0, 80), random.randint(0, 80), random.randint(0, 80)) draw.text((x, y), char, font=font, fill=color) diff --git a/file-upload-site/static/css/style.css b/file-upload-site/static/css/style.css index ce57675..07be958 100644 --- a/file-upload-site/static/css/style.css +++ b/file-upload-site/static/css/style.css @@ -610,8 +610,11 @@ input[type="text"].input-error { } .modal-captcha-img { - height: 46px; - flex: 1; + height: 86px; + width: min(260px, 100%); + max-width: 260px; + min-width: 0; + flex: 1 1 0; border-radius: var(--radius-sm); border: 1.5px solid var(--border); cursor: pointer; @@ -721,6 +724,11 @@ input[type="text"].input-error { height: 38px; } + .modal-captcha-img { + height: 72px; + max-width: 100%; + } + .form-actions { flex-direction: column; } diff --git a/file-upload-site/style.css b/file-upload-site/style.css index 5b87e4e..07be958 100644 --- a/file-upload-site/style.css +++ b/file-upload-site/style.css @@ -610,9 +610,11 @@ input[type="text"].input-error { } .modal-captcha-img { - height: 80px; - width: 240px; - flex: none; + height: 86px; + width: min(260px, 100%); + max-width: 260px; + min-width: 0; + flex: 1 1 0; border-radius: var(--radius-sm); border: 1.5px solid var(--border); cursor: pointer; @@ -722,6 +724,11 @@ input[type="text"].input-error { height: 38px; } + .modal-captcha-img { + height: 72px; + max-width: 100%; + } + .form-actions { flex-direction: column; }