简单版点击式验证码 Django实现

前言

验证码中还有一种依靠鼠标点击验证的验证码

 这类验证码主要由背景图片与字符以及点击后的反馈三部分组成,本文为简单版,故只实现前两部分,并且不使用汉字作为展示,转而使用英文字符或数字。


后端 

 验证码生成

 首先处理验证码部分,这一部分不仅要生成验证码,还要生成验证码在背景中的位置信息即x、y值,同时需要保证验证码位置不能有重合部分。其中生成位置部分使用了最简单的while循环来保证位置不重合

# locations = [[左上x, 左上y], ...]
def location_determine(locations):
    # 采用简单但耗时的方法
    while 1:
        x = random.randint(20, 390)
        y = random.randint(15, 230)
        flag = True
        # 逐个判断
        for location in locations:
            location_x, location_y = location[0], location[1]
            # 双重满足,构成方形区域
            if x in range(location_x, location_x + 46) and y in range(location_y, location_y + 46):
                flag = False
                break
        if flag:
            return x, y

接着将各个字符的位置信息存储进session中,方便后续调用进行判断操作 

# locations = [[左上x, 左上y], ...]
def location_set(request, locations):
    # 传入[]表示为第一次取点
    locations = [[-45, -46]] if locations == [] else locations
    # 调用函数,确定位置
    x, y = location_determine(locations)
    if locations[0][1] < 0:
        request.session.update({'x1': x, 'y1': y})
    else:
        length = len(locations) + 1
        request.session.update({f'x{length}': x, f'y{length}': y})
    return [x, y]

生成验证码时使用set存储验证码防止出现相同字符,并且调用以上函数确定验证码的值与位置

# 生成验证码
def code_determine():
    # 使用集合存储验证码,防止重复
    key = set()
    code = ''
    while len(key) != 3:
        random_upper = chr(random.randint(65, 90))  # 大写字母
        random_lower = chr(random.randint(97, 122))  # 小写字母
        random_int = str(random.randint(0, 9))  # 数字
        # 随机选择
        temp = random.choice([random_int, random_upper, random_lower])
        # 如果重复则跳过该次循环
        if temp in key:
            continue
        key.add(temp)
        code += temp
    return code

背景图片处理

 背景的处理与滑块验证码一文类似

def click_background(request):
    # 选择图片
    all_images = list(os.listdir('././static/imgs/click'))
    img_url = os.path.join('././static/imgs/click', random.choice(all_images))
    background_obj = Image.open(img_url)
    # 修改图片大小
    background_obj = background_obj.resize((500, 300))
    # 实例化画笔
    img_draw = ImageDraw.Draw(background_obj)
    # 设置字体样式, 字体大小
    img_font = ImageFont.truetype('static/font/font_1.ttf', 45)
    locations = []
    code = request.session.get('code')
    for i in code:
        # 确定位置
        x, y = location_set(request, locations)
        img_draw.text(xy=(x, y), text=i, font=img_font,
                      fill=random.choice(['red', 'green', 'blue', 'black']))
        # 在locations中添加
        locations.append([x, y])
    # 临时存储
    io_obj = BytesIO()
    background_obj.save(io_obj, 'png')
    return HttpResponse(io_obj.getvalue())

判断部分

判断用户点击的正确与否不仅包括点击位置的正确,也包括点击顺序的正确和点击次数的正确。了解判断原理后,代码实现就更为简单 

def click(request):
    # 验证部分
    if request.method == 'POST':
        # 获取前端传的数据
        data = json.loads(request.body)
        coordinates = data.get('coordinates', [])
        length = len(coordinates)
        # 排除多点和少点
        if length != 3:
            return JsonResponse({'info': 2})
        flag = True
        # 按顺序验证
        for i in range(3):
            x = int(request.session.get(f'x{i+1}'))
            y = int(request.session.get(f'y{i+1}'))
            # 设置合理的判断点击范围
            if coordinates[i]['x'] not in range(x, x+45) or coordinates[i]['y'] not in range(y, y+45):
                flag = False
        if flag:
            return JsonResponse({'info': 1})
        else:
            return JsonResponse({'info': 2})
    # 设置验证码
    code = code_determine()
    # 验证码存储
    request.session.update({'code': code})
    return render(request, 'click.html')

前端

前端的重点为如何将用户点击的位置信息传递给后端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>验证码</title>
    {% load static %}
    <link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
    <script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
    <script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
</head>
<body>
    <h1 style="text-align: center">
        请依次点击 {{ request.session.code|join:" " }}
    </h1>
    <div style="text-align: center;margin-bottom: 20px">
        <img src="/picture/click_background" id="area_click">
    </div>
    <div style="text-align: center">
        <button type="submit" style="text-align: center" id="id_confirm">确认</button>
    </div>
    <script>
        let list = []
        document.getElementById('area_click').addEventListener('click', function (event) {
            let x = event.offsetX;
            let y = event.offsetY;
            list.push({x, y})
        })

         document.getElementById('id_confirm').addEventListener('click', function () {
            fetch('/picture/click/', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({coordinates: list})  // 将坐标数组作为 payload 发送
                })
                .then(response => response.json())
                .then(data => {
                    let obj = JSON.stringify(data);
                    let info = JSON.parse(obj);
                    // 处理从后端接收到的数据
                    if(info.info === 1){
                        alert('成功');
                    }else {
                        alert('失败');
                    }
                })
            })
    </script>
</body>
</html>

效果展示


 总结

后端关键部分的代码几乎没有使用算法,但是简单易懂,同时改进空间较大。对于用户点击后的反馈展示这一拓展也没有实现,当然背景图片处理部分作为一个独立的函数,实现反馈功能对函数结构的改变较少,还是便于扩展的。

大二在读,不足请指正

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值