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')