You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
286 lines
12 KiB
286 lines
12 KiB
import base64
|
|
import io
|
|
import json
|
|
import logging
|
|
import math
|
|
import os
|
|
import traceback
|
|
from PIL import Image, ImageDraw, ImageShow
|
|
import random
|
|
|
|
from django_redis import get_redis_connection
|
|
from rest_framework.views import APIView
|
|
|
|
from rest_framework.response import Response
|
|
|
|
from ChaCeRndTrans.basic import CCAIResponse
|
|
from ChaCeRndTrans.code import BAD, SERVER_ERROR
|
|
from ChaCeRndTrans.settings import MEDIA_ROOT
|
|
|
|
logger = logging.getLogger('error')
|
|
|
|
# 验证码图片索引0-19,共20张图
|
|
image_start = 0
|
|
image_end = 11
|
|
DEBUG = False
|
|
|
|
|
|
# 随机生成滑块验证码
|
|
class SliderVerificationCode:
|
|
def __init__(self):
|
|
self.bg_color = (255, 255, 255) # 背景色
|
|
self.slider_color = (125, 125, 125) # 滑块颜色
|
|
self.img_size = (260, 120) # 图片尺寸
|
|
|
|
self.block_size = (40, 40) # 滑块尺寸
|
|
self.blockX = 0 # 滑块的横坐标
|
|
self.blockY = 0 # 滑块的纵坐标
|
|
self.block_radius = 5 # 滑块凹凸半径
|
|
self.block_position = self.generate_block_position() # 生成滑块位置
|
|
|
|
def generate_block_position(self):
|
|
block_x = random.randint(0, self.img_size[0] - self.block_size[0]) # 随机生成滑块起始横坐标
|
|
block_y = random.randint(0, self.img_size[1] - self.block_size[1]) # 随机生成滑块起始纵坐标
|
|
return block_x, block_y
|
|
|
|
|
|
class SliderVerification(APIView):
|
|
|
|
def get(self, request):
|
|
try:
|
|
params = request.GET
|
|
username = params.get('username') # 手机号码/账号/邮箱
|
|
if username is None:
|
|
return CCAIResponse("缺少username参数", BAD)
|
|
slider_code = SliderVerificationCode()
|
|
# 获取画布的宽高
|
|
canvasWidth, canvasHeight = slider_code.img_size
|
|
# 滑块的宽高,半径
|
|
block_width, block_height = slider_code.block_size
|
|
block_radius = slider_code.block_radius
|
|
path = MEDIA_ROOT + '/VerificationCode_image'
|
|
canvasImage = get_image(path)
|
|
# 调整原图到指定大小
|
|
canvasImage = image_resize(canvasImage, canvasWidth, canvasHeight)
|
|
if DEBUG:
|
|
info = {"Title": "这是 canvasImage "}
|
|
ImageShow.show(canvasImage, info)
|
|
|
|
# 随机生成滑块坐标
|
|
blockX = random.randint(block_width, canvasWidth - block_width - 10)
|
|
blockY = random.randint(10, canvasHeight - block_height + 1)
|
|
slider_code.blockX = blockX
|
|
slider_code.blockY = blockY
|
|
# 新建滑块
|
|
block_image = Image.new("RGBA", (block_width, block_height))
|
|
# 新建的图像根据轮廓图颜色赋值,源图生成遮罩
|
|
cut_by_template(canvasImage, block_image, block_width, block_height, block_radius, blockX, blockY)
|
|
if DEBUG:
|
|
print(canvasImage)
|
|
print(block_image)
|
|
print(blockX)
|
|
print(blockY)
|
|
print(block_width)
|
|
print(block_height)
|
|
print(block_radius)
|
|
info = {"Title": "这是 final block "}
|
|
ImageShow.show(block_image, info)
|
|
info = {"Title": "这是 final canvasImage "}
|
|
ImageShow.show(canvasImage, info)
|
|
canvas_str = image_to_base64(canvasImage)
|
|
blockc_str = image_to_base64(block_image)
|
|
|
|
# 将滑块的X坐标 存到redis中做验证
|
|
slider_key = 'chace_rnd_slider_' + username
|
|
conn = get_redis_connection('default')
|
|
conn.set(slider_key, blockX)
|
|
conn.expire(slider_key, 60)
|
|
rows = []
|
|
img_dict = {}
|
|
img_dict['canvas_str'] = canvas_str
|
|
img_dict['blockc_str'] = blockc_str
|
|
img_dict['blockY'] = blockY
|
|
rows.append(img_dict)
|
|
request.session['blockX'] = blockX # 备选方案,如果redis连不上,拿session中的
|
|
return CCAIResponse(rows)
|
|
|
|
except Exception as e:
|
|
logger.error(
|
|
"user: %s, get slider verification code failed: \n%s" % (request.user.id, traceback.format_exc()))
|
|
return CCAIResponse("获取滑块验证码失败", SERVER_ERROR)
|
|
|
|
def post(self, request):
|
|
try:
|
|
params = request.data
|
|
username = params.get('username') # 手机号码
|
|
slide_blockX = params.get('blockX') # 获取用户提交的滑块位置坐标
|
|
if username is None:
|
|
return CCAIResponse("缺少username参数", BAD)
|
|
|
|
if slide_blockX is None:
|
|
return CCAIResponse("缺少 blockX 参数", BAD)
|
|
else:
|
|
slide_blockX = int(slide_blockX)
|
|
|
|
try:
|
|
conn = get_redis_connection('default')
|
|
cache_slider = conn.get('chace_rnd_slider_' + username) # 获取滑块缓存的X坐标
|
|
if DEBUG:
|
|
print(cache_slider)
|
|
except Exception as e:
|
|
logger.error("user: %s, connect redis failed: \n%s" % (request.user.id, traceback.format_exc()))
|
|
if cache_slider:
|
|
cache_slider = json.loads(cache_slider)
|
|
else:
|
|
cache_slider = request.session.get('blockX') # 从session中获取滑块位置坐标
|
|
|
|
rows = {"code": 400}
|
|
if cache_slider is None:
|
|
rows = {"code": 401}
|
|
return CCAIResponse(rows)
|
|
|
|
if abs(int(cache_slider) - int(slide_blockX)) < 5: # 判断是否验证成功
|
|
conn.set("rnd_manual_slider_" + username, username, ex=60)
|
|
return CCAIResponse("验证成功")
|
|
else:
|
|
return CCAIResponse(rows)
|
|
|
|
except Exception as e:
|
|
logger.error(
|
|
"user: %s, POST slider verification code failed: \n%s" % (request.user.id, traceback.format_exc()))
|
|
return CCAIResponse("提交滑块验证码失败", SERVER_ERROR)
|
|
|
|
|
|
# 获取验证码资源图
|
|
def get_image(folder_path):
|
|
# pass # 获取指定范围内的随机数
|
|
image_index = random.randint(image_start, image_end) # 获取随机码 0-19的随机数
|
|
image_path = os.path.join(folder_path, str(image_index) + '.jpg')
|
|
image = Image.open(image_path)
|
|
if DEBUG:
|
|
print("image_index")
|
|
print(image_index)
|
|
print(image_path)
|
|
info = {"Title": "这是 srcImage "}
|
|
ImageShow.show(image, info)
|
|
return image
|
|
|
|
|
|
# 调整图片的尺寸大小
|
|
def image_resize(original_image, width, height):
|
|
image = original_image.resize((width, height)) # Image.ANTIALIAS 是选择高质量缩放滤镜
|
|
if DEBUG:
|
|
info = {"Title": "这是 resizeImage "}
|
|
ImageShow.show(image, info)
|
|
# result_image = Image.new('RGBA', (width, height)) # 创建一个空白的image
|
|
# graphics2D = result_image.convert('RGBA') # 转换为RGBA模式,就可以在上面绘制图像
|
|
# graphics2D.paste(image, (0, 0))
|
|
# return result_image
|
|
return image
|
|
|
|
|
|
# 抠图,并生成阻塞块
|
|
def cut_by_template(canvas_image, block_image, block_width, block_height, block_radius, block_x, block_y):
|
|
water_image = Image.new("RGBA", (block_width, block_height), (0, 0, 0, 0))
|
|
# 阻滑块的轮廓图
|
|
block_data = get_block_data(block_width, block_height, block_radius)
|
|
# 防止数组越界,保证边界为0
|
|
block_data[0] = [0] * len(block_data[0])
|
|
block_data[-1] = [0] * len(block_data[-1])
|
|
# 创建滑块具体形状
|
|
for i in range(block_width):
|
|
# 将第1列 和 最后1列,设置为0,防止数组越界,保证边界为0
|
|
block_data[i][0] = 0
|
|
block_data[i][-1] = 0
|
|
for j in range(block_height):
|
|
# 原图中对应位置变色处理
|
|
if block_data[i][j] == 1:
|
|
# 背景设置为黑色
|
|
water_image.putpixel((i, j), (0, 0, 0, 255))
|
|
block_image.putpixel((i, j), canvas_image.getpixel((block_x + i, block_y + j)))
|
|
# 轮廓设置为白色,取带像素和无像素的界点,判断该点是不是临界轮廓点
|
|
if block_data[i + 1][j] == 0 or block_data[i][j + 1] == 0 or block_data[i - 1][j] == 0 or \
|
|
block_data[i][j - 1] == 0:
|
|
block_image.putpixel((i, j), (255, 255, 255, 255))
|
|
water_image.putpixel((i, j), (255, 255, 255, 255))
|
|
# 把背景设为透明
|
|
else:
|
|
block_image.putpixel((i, j), (0, 0, 0, 0))
|
|
water_image.putpixel((i, j), (0, 0, 0, 0))
|
|
# 在画布上添加阻塞块水印
|
|
if DEBUG:
|
|
info = {"Title": "这是block_image"}
|
|
ImageShow.show(block_image, info)
|
|
info = {"Title": "这是 water_image "}
|
|
ImageShow.show(water_image, info)
|
|
add_block_watermark(canvas_image, water_image, block_x, block_y)
|
|
|
|
|
|
# 构建拼图轮廓轨迹
|
|
def get_block_data(block_width, block_height, block_radius):
|
|
"""
|
|
先创建一个二维数组data,然后随机生成两个圆的坐标,并在4个方向上随机找到2个方向添加凸/凹
|
|
它获取凸/凹起位置坐标,并随机选择凸/凹类型。
|
|
最后,计算需要的小图轮廓,用二维数组来表示,二维数组有两张值,0和1,其中0表示没有颜色,1有颜色。
|
|
"""
|
|
data = [[0 for _ in range(block_width)] for _ in range(block_height)] # 初始化二维数组,元素为0
|
|
po = math.pow(block_radius, 2)
|
|
# 随机生成两个圆的坐标,在4个方向上 随机找到2个方向添加凸/凹
|
|
# 凸/凹1
|
|
face1 = random.randint(0, 4)
|
|
# 凸/凹2
|
|
face2 = random.randint(0, 4)
|
|
# 保证两个凸/凹不在同一位置
|
|
while face1 == face2:
|
|
face2 = random.randint(0, 4)
|
|
# 获取凸/凹起位置坐标
|
|
circle1 = get_circle_coords(face1, block_width, block_height, block_radius)
|
|
circle2 = get_circle_coords(face2, block_width, block_height, block_radius)
|
|
# 随机凸/凹类型
|
|
shape = random.randint(0, 1)
|
|
# 圆的标准方程 (x-a)²+(y-b)²=r²,标识圆心(a,b),半径为r的圆
|
|
# 计算需要的小图轮廓,用二维数组来表示,二维数组有两张值,0和1,其中0表示没有颜色,1有颜色
|
|
for i in range(block_width):
|
|
for j in range(block_height):
|
|
# 创建中间的方形区域
|
|
if (
|
|
i >= block_radius and i <= block_width - block_radius and j >= block_radius and j <= block_height - block_radius):
|
|
data[i][j] = 1
|
|
double_d1 = math.pow(i - circle1[0], 2) + math.pow(j - circle1[1], 2)
|
|
double_d2 = math.pow(i - circle2[0], 2) + math.pow(j - circle2[1], 2)
|
|
# 创建两个凸/凹
|
|
if double_d1 <= po or double_d2 <= po:
|
|
data[i][j] = shape
|
|
return data
|
|
|
|
|
|
# 根据朝向获取圆心坐标
|
|
def get_circle_coords(face, block_width, block_height, block_radius):
|
|
"""
|
|
根据传入的face值(0表示上,1表示左,2表示下,3表示右),
|
|
返回一个包含两个整数的数组,分别表示圆心的 横坐标 和 纵坐标。
|
|
"""
|
|
if face == 0: # 上
|
|
return [block_width // 2 - 1, block_radius]
|
|
elif face == 1: # 左
|
|
return [block_radius, block_height // 2 - 1]
|
|
elif face == 2: # 下
|
|
return [block_width // 2 - 1, block_height - block_radius - 1]
|
|
# elif face == 3: # 右
|
|
# return [block_width - block_radius - 1, block_height // 2 - 1]
|
|
else:
|
|
return [block_width - block_radius - 1, block_height // 2 - 1] # face == 4, 按3处理
|
|
|
|
|
|
# 在画布上添加阻塞块水印
|
|
def add_block_watermark(canvas_image, block_image, x, y):
|
|
draw = ImageDraw.Draw(canvas_image)
|
|
canvas_image.paste(block_image, (x, y), block_image)
|
|
|
|
|
|
# 将图片转换为base64
|
|
def image_to_base64(image):
|
|
buffered = io.BytesIO()
|
|
image.save(buffered, format="png")
|
|
img_str = base64.b64encode(buffered.getvalue())
|
|
return img_str.decode('utf-8')
|
|
|