refactor: 移除注册验证码,删掉 pillow

验证码只剩注册一处调用方,一并去掉。

- UserRegisterAPI 的校验、UserRegisterSerializer 的 captcha 字段、
  发图的 captcha 路由
- utils/captcha/ 整个包删除,含 Menlo.ttc 和 timesbi.ttf 两个字体
- utils/shortcuts.py 的 img2base64(),唯一调用方是验证码视图
- 依赖删除 pillow(本地实测 PIL/ 5.9M + pillow.libs/ 14M)

注意:注册接口现在只有 SysOptions.allow_register 一个开关,没有限流,
throttling 目前只用在 SubmissionAPI 上。内网自用可以接受,站点若暴露到
公网需要补 IP 维度限流。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 23:48:00 -06:00
parent 12c12b1840
commit fda7515263
11 changed files with 0 additions and 263 deletions

Binary file not shown.

View File

@@ -1,100 +0,0 @@
"""
Copyright 2013 TY<tianyu0915@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import os
import random
import time
from PIL import Image, ImageDraw, ImageFont
class Captcha(object):
def __init__(self, request):
"""
初始化,设置各种属性
"""
self.django_request = request
self.session_key = "_django_captcha_key"
self.captcha_expires_time = "_django_captcha_expires_time"
# 验证码图片尺寸
self.img_width = 90
self.img_height = 30
def _get_font_size(self, code):
"""
将图片高度的80%作为字体大小
"""
s1 = int(self.img_height * 0.8)
s2 = int(self.img_width / len(code))
return int(min((s1, s2)) + max((s1, s2)) * 0.05)
def _set_answer(self, answer):
"""
设置答案和过期时间
"""
self.django_request.session[self.session_key] = str(answer)
self.django_request.session[self.captcha_expires_time] = time.time() + 60
def _make_code(self):
"""
生成随机数或随机字符串
"""
string = random.sample("abcdefghkmnpqrstuvwxyzABCDEFGHGKMNOPQRSTUVWXYZ23456789", 4)
self._set_answer("".join(string))
return string
def get(self):
"""
生成验证码图片,返回值为图片的bytes
"""
background = (random.randrange(200, 255), random.randrange(200, 255), random.randrange(200, 255))
code_color = (random.randrange(0, 50), random.randrange(0, 50), random.randrange(0, 50), 255)
font_path = os.path.join(os.path.normpath(os.path.dirname(__file__)), "timesbi.ttf")
image = Image.new("RGB", (self.img_width, self.img_height), background)
code = self._make_code()
font_size = self._get_font_size(code)
draw = ImageDraw.Draw(image)
# x是第一个字母的x坐标
x = random.randrange(int(font_size * 0.3), int(font_size * 0.5))
for i in code:
# 字符y坐标
y = random.randrange(1, 7)
# 随机字符大小
font = ImageFont.truetype(font_path.replace("\\", "/"), font_size + random.randrange(-3, 7))
draw.text((x, y), i, font=font, fill=code_color)
# 随机化字符之间的距离 字符粘连可以降低识别率
x += font_size * random.randrange(6, 8) / 10
self.django_request.session[self.session_key] = "".join(code)
return image
def check(self, code):
"""
检查用户输入的验证码是否正确
"""
_code = self.django_request.session.get(self.session_key) or ""
if not _code:
return False
expires_time = self.django_request.session.get(self.captcha_expires_time) or 0
# 注意 如果验证之后不清除之前的验证码的话 可能会造成重复验证的现象
del self.django_request.session[self.session_key]
del self.django_request.session[self.captcha_expires_time]
if _code.lower() == str(code).lower() and time.time() < expires_time:
return True
else:
return False

Binary file not shown.

View File

@@ -1,8 +0,0 @@
from ..api import APIView
from ..shortcuts import img2base64
from . import Captcha
class CaptchaAPIView(APIView):
def get(self, request):
return self.success(img2base64(Captcha(request).get()))

View File

@@ -1,8 +1,6 @@
import os
import random
import re
from base64 import b64encode
from io import BytesIO
from django.utils.crypto import get_random_string
@@ -38,15 +36,6 @@ def build_query_string(kv_data, ignore_none=True):
return query_string
def img2base64(img):
with BytesIO() as buf:
img.save(buf, "gif")
buf_str = buf.getvalue()
img_prefix = "data:image/png;base64,"
b64_str = img_prefix + b64encode(buf_str).decode("utf-8")
return b64_str
def datetime2str(value, format="iso-8601"):
if format.lower() == "iso-8601":
value = value.isoformat()