mirror of
https://github.com/QingdaoU/OnlineJudge.git
synced 2025-11-04 14:49:58 +08:00
完善captchaapi
This commit is contained in:
parent
57b75fd511
commit
d1767e775d
@ -4,6 +4,8 @@ from ..views.oj import (ApplyResetPasswordAPI, ResetPasswordAPI,
|
|||||||
UserChangePasswordAPI, UserRegisterAPI,
|
UserChangePasswordAPI, UserRegisterAPI,
|
||||||
UserLoginAPI, UserLogoutAPI)
|
UserLoginAPI, UserLogoutAPI)
|
||||||
|
|
||||||
|
from utils.captcha.views import CaptchaAPIView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r"^login/?$", UserLoginAPI.as_view(), name="user_login_api"),
|
url(r"^login/?$", UserLoginAPI.as_view(), name="user_login_api"),
|
||||||
url(r"^logout/?$", UserLogoutAPI.as_view(), name="user_logout_api"),
|
url(r"^logout/?$", UserLogoutAPI.as_view(), name="user_logout_api"),
|
||||||
@ -11,5 +13,5 @@ urlpatterns = [
|
|||||||
url(r"^change_password/?$", UserChangePasswordAPI.as_view(), name="user_change_password_api"),
|
url(r"^change_password/?$", UserChangePasswordAPI.as_view(), name="user_change_password_api"),
|
||||||
url(r"^apply_reset_password/?$", ApplyResetPasswordAPI.as_view(), name="apply_reset_password_api"),
|
url(r"^apply_reset_password/?$", ApplyResetPasswordAPI.as_view(), name="apply_reset_password_api"),
|
||||||
url(r"^reset_password/?$", ResetPasswordAPI.as_view(), name="apply_reset_password_api"),
|
url(r"^reset_password/?$", ResetPasswordAPI.as_view(), name="apply_reset_password_api"),
|
||||||
url(r"^captcha/?$", "utils.captcha.views.show_captcha", name="show_captcha"),
|
url(r"^captcha/?$", CaptchaAPIView.as_view(), name="show_captcha"),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,12 +1,9 @@
|
|||||||
"""
|
"""
|
||||||
Copyright 2013 TY<tianyu0915@gmail.com>
|
Copyright 2017 TY<master@t-y.me>
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@ -15,95 +12,147 @@ limitations under the License.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
import random
|
import random
|
||||||
|
from math import ceil
|
||||||
from io import BytesIO
|
from six import BytesIO
|
||||||
from django.http import HttpResponse
|
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
|
__version__ = '0.3.3'
|
||||||
|
current_path = os.path.normpath(os.path.dirname(__file__))
|
||||||
|
|
||||||
class Captcha(object):
|
class Captcha(object):
|
||||||
|
|
||||||
def __init__(self, request):
|
def __init__(self, request):
|
||||||
|
""" something init
|
||||||
"""
|
"""
|
||||||
初始化,设置各种属性
|
|
||||||
"""
|
|
||||||
self.django_request = request
|
|
||||||
self.session_key = "_django_captcha_key"
|
|
||||||
self.captcha_expires_time = "_django_captcha_expires_time"
|
|
||||||
|
|
||||||
# 验证码图片尺寸
|
self.django_request = request
|
||||||
self.img_width = 90
|
self.session_key = '_django_captcha_key'
|
||||||
|
self.words = ["hello", "word"]
|
||||||
|
|
||||||
|
# image size (pix)
|
||||||
|
self.img_width = 150
|
||||||
self.img_height = 30
|
self.img_height = 30
|
||||||
|
|
||||||
def _get_font_size(self, code):
|
self.type = 'number'
|
||||||
"""
|
self.mode = 'number'
|
||||||
将图片高度的80%作为字体大小
|
|
||||||
"""
|
def _get_font_size(self):
|
||||||
s1 = int(self.img_height * 0.8)
|
s1 = int(self.img_height * 0.8)
|
||||||
s2 = int(self.img_width / len(code))
|
s2 = int(self.img_width/len(self.code))
|
||||||
return int(min((s1, s2)) + max((s1, s2)) * 0.05)
|
return int(min((s1, s2)) + max((s1, s2))*0.05)
|
||||||
|
|
||||||
|
def _get_words(self):
|
||||||
|
""" words list
|
||||||
|
"""
|
||||||
|
|
||||||
|
# TODO 扩充单词表
|
||||||
|
|
||||||
|
if self.words:
|
||||||
|
return set(self.words)
|
||||||
|
|
||||||
|
|
||||||
def _set_answer(self, answer):
|
def _set_answer(self, answer):
|
||||||
"""
|
|
||||||
设置答案和过期时间
|
|
||||||
"""
|
|
||||||
self.django_request.session[self.session_key] = str(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):
|
def _generate(self):
|
||||||
|
# 英文单词验证码
|
||||||
|
def word():
|
||||||
|
code = random.sample(self._get_words(), 1)[0]
|
||||||
|
self._set_answer(code)
|
||||||
|
return code
|
||||||
|
|
||||||
|
# 数字公式验证码
|
||||||
|
def number():
|
||||||
|
m, n = 1, 50
|
||||||
|
x = random.randrange(m, n)
|
||||||
|
y = random.randrange(m, n)
|
||||||
|
|
||||||
|
r = random.randrange(0, 2)
|
||||||
|
if r == 0:
|
||||||
|
code = "%s - %s = ?" % (x, y)
|
||||||
|
z = x - y
|
||||||
|
else:
|
||||||
|
code = "%s + %s = ?" % (x, y)
|
||||||
|
z = x + y
|
||||||
|
self._set_answer(z)
|
||||||
|
return code
|
||||||
|
|
||||||
|
fun = eval(self.mode.lower())
|
||||||
|
return fun()
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
""" return captcha image bytes
|
||||||
"""
|
"""
|
||||||
生成随机数或随机字符串
|
|
||||||
|
# font color
|
||||||
|
self.font_color = ['black', 'darkblue', 'darkred']
|
||||||
|
|
||||||
|
# background color
|
||||||
|
self.background = (random.randrange(230, 255), random.randrange(230, 255), random.randrange(230, 255))
|
||||||
|
|
||||||
|
# font path
|
||||||
|
self.font_path = os.path.join(current_path, 'timesbi.ttf') # or Menlo.ttc
|
||||||
|
|
||||||
|
self.django_request.session[self.session_key] = ''
|
||||||
|
im = Image.new('RGB', (self.img_width, self.img_height), self.background)
|
||||||
|
self.code = self._generate()
|
||||||
|
|
||||||
|
# set font size automaticly
|
||||||
|
self.font_size = self._get_font_size()
|
||||||
|
|
||||||
|
# creat
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
# draw noisy point/line
|
||||||
|
if self.mode == 'word':
|
||||||
|
c = int(8/len(self.code)*3) or 3
|
||||||
|
elif self.mode == 'number':
|
||||||
|
c = 4
|
||||||
|
|
||||||
|
for i in range(random.randrange(c-2, c)):
|
||||||
|
line_color = (random.randrange(0, 255), random.randrange(0, 255), random.randrange(0, 255))
|
||||||
|
xy = (random.randrange(0, int(self.img_width*0.2)), random.randrange(0, self.img_height),
|
||||||
|
random.randrange(int(3*self.img_width/4), self.img_width), random.randrange(0, self.img_height))
|
||||||
|
draw.line(xy, fill=line_color, width=int(self.font_size*0.1))
|
||||||
|
|
||||||
|
# main part
|
||||||
|
j = int(self.font_size*0.3)
|
||||||
|
k = int(self.font_size*0.5)
|
||||||
|
x = random.randrange(j, k)
|
||||||
|
|
||||||
|
for i in self.code:
|
||||||
|
# 上下抖动量,字数越多,上下抖动越大
|
||||||
|
m = int(len(self.code))
|
||||||
|
y = random.randrange(1, 3)
|
||||||
|
|
||||||
|
if i in ('+', '=', '?'):
|
||||||
|
# 对计算符号等特殊字符放大处理
|
||||||
|
m = ceil(self.font_size*0.8)
|
||||||
|
else:
|
||||||
|
# 字体大小变化量,字数越少,字体大小变化越多
|
||||||
|
m = random.randrange(0, int(45 / self.font_size) + int(self.font_size/5))
|
||||||
|
|
||||||
|
self.font = ImageFont.truetype(self.font_path.replace('\\', '/'), self.font_size + int(ceil(m)))
|
||||||
|
draw.text((x, y), i, font=self.font, fill=random.choice(self.font_color))
|
||||||
|
x += self.font_size*0.9
|
||||||
|
|
||||||
|
del x
|
||||||
|
del draw
|
||||||
|
with BytesIO() as buf:
|
||||||
|
im.save(buf, 'gif')
|
||||||
|
buf_str = buf.getvalue()
|
||||||
|
return buf_str
|
||||||
|
|
||||||
|
def validate(self, code):
|
||||||
|
""" user input validate
|
||||||
"""
|
"""
|
||||||
string = random.sample("abcdefghkmnpqrstuvwxyzABCDEFGHGKMNOPQRSTUVWXYZ23456789", 4)
|
|
||||||
self._set_answer("".join(string))
|
|
||||||
return string
|
|
||||||
|
|
||||||
def display(self):
|
if not code:
|
||||||
"""
|
|
||||||
生成验证码图片
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
|
|
||||||
buf = BytesIO()
|
|
||||||
image.save(buf, "gif")
|
|
||||||
|
|
||||||
self.django_request.session[self.session_key] = "".join(code)
|
|
||||||
return HttpResponse(buf.getvalue(), "image/gif")
|
|
||||||
|
|
||||||
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
|
return False
|
||||||
|
|
||||||
|
code = code.strip()
|
||||||
|
_code = self.django_request.session.get(self.session_key) or ''
|
||||||
|
self.django_request.session[self.session_key] = ''
|
||||||
|
return _code.lower() == str(code).lower()
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
from django.http import HttpResponse
|
from base64 import b64encode
|
||||||
|
|
||||||
from utils.captcha import Captcha
|
from . import Captcha
|
||||||
|
from ..api import APIView
|
||||||
|
|
||||||
|
|
||||||
def show_captcha(request):
|
class CaptchaAPIView(APIView):
|
||||||
return HttpResponse(Captcha(request).display(), content_type="image/gif")
|
def get(self, request):
|
||||||
|
img_prefix = "data:image/png;base64,"
|
||||||
|
img = img_prefix + b64encode(Captcha(request).get()).decode("utf-8")
|
||||||
|
return self.success(img)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user