独角鲸同步合作方公司数据项目
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.

287 lines
12 KiB

10 months ago
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')