调整验证码图片大小
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
FROM python:3.10-slim
|
FROM python:3.10-slim
|
||||||
|
|
||||||
# 系统依赖(python-magic 需要 libmagic)
|
# 系统依赖(python-magic 需要 libmagic,验证码需要清晰字体)
|
||||||
RUN apt-get update && \
|
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/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
@@ -38,6 +38,16 @@ NAME_REGEX = re.compile(r'^[\u4e00-\u9fa5a-zA-Z0-9_]{2,20}$')
|
|||||||
# 文件名中不安全的字符
|
# 文件名中不安全的字符
|
||||||
UNSAFE_FILENAME_CHARS = re.compile(r'[<>:"/\\|?*\x00-\x1f]')
|
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:
|
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))
|
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:
|
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))
|
bg_color = (random.randint(235, 255), random.randint(235, 255), random.randint(235, 255))
|
||||||
image = Image.new('RGB', (width, height), bg_color)
|
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))
|
dot_color = (random.randint(140, 200), random.randint(140, 200), random.randint(140, 200))
|
||||||
draw.point((x, y), fill=dot_color)
|
draw.point((x, y), fill=dot_color)
|
||||||
|
|
||||||
# 绘制文字
|
font = load_captcha_font()
|
||||||
try:
|
char_slot_width = width // len(text)
|
||||||
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 65)
|
|
||||||
except OSError:
|
|
||||||
font = ImageFont.load_default()
|
|
||||||
|
|
||||||
for i, char in enumerate(text):
|
for i, char in enumerate(text):
|
||||||
x = 20 + i * 72
|
bbox = draw.textbbox((0, 0), char, font=font)
|
||||||
y = random.randint(6, 18)
|
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))
|
color = (random.randint(0, 80), random.randint(0, 80), random.randint(0, 80))
|
||||||
draw.text((x, y), char, font=font, fill=color)
|
draw.text((x, y), char, font=font, fill=color)
|
||||||
|
|
||||||
|
|||||||
@@ -610,8 +610,11 @@ input[type="text"].input-error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-captcha-img {
|
.modal-captcha-img {
|
||||||
height: 46px;
|
height: 86px;
|
||||||
flex: 1;
|
width: min(260px, 100%);
|
||||||
|
max-width: 260px;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1 1 0;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
border: 1.5px solid var(--border);
|
border: 1.5px solid var(--border);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -721,6 +724,11 @@ input[type="text"].input-error {
|
|||||||
height: 38px;
|
height: 38px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-captcha-img {
|
||||||
|
height: 72px;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.form-actions {
|
.form-actions {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -610,9 +610,11 @@ input[type="text"].input-error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-captcha-img {
|
.modal-captcha-img {
|
||||||
height: 80px;
|
height: 86px;
|
||||||
width: 240px;
|
width: min(260px, 100%);
|
||||||
flex: none;
|
max-width: 260px;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1 1 0;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
border: 1.5px solid var(--border);
|
border: 1.5px solid var(--border);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -722,6 +724,11 @@ input[type="text"].input-error {
|
|||||||
height: 38px;
|
height: 38px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-captcha-img {
|
||||||
|
height: 72px;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.form-actions {
|
.form-actions {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user