图片和短信验证码(头条项目-06)

1 图形验证码接口设计

将后端⽣成的图⽚验证码存储在redis数据库2号库

结构:

  • {'img_uuid':'0594'}

1.1 创建验证码⼦应⽤

bash 复制代码
$ cd apps
$ python ../../manage.py startapp verifications
python 复制代码
# 注册新应⽤
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'userapp',
    'newsapp',
    'verifications',
]

1.2 图形验证码接⼝设计

1.2.1 请求⽅式

|--------|--------------------------|
| 选项 | ⽅案 |
| 请求⽅法 | GET |
| 请求地址 1 | /imgcodes/(?P[\w-]+)/ |

1.2.2 请求参数:路径参数

|------|--------|------|------|
| 参数名 | 类型 | 是否必传 | 说明 |
| uuid | string | 是 | 唯⼀编号 |

1.2.3 响应结果 图⽚验证码格式:image/png

1.3 图形验证码接⼝定义

1.3.1 图形验证码视图
python 复制代码
# views.py视图⽂件
class ImageCode(View):
    def get(self, request, uuid):
        pass
1.3.2 配置路由
python 复制代码
# 项⽬根路由
re_path('^', include(('verifications.urls', 'verifications',),
                     namespace='verify')),
# ⼦路由
re_path('^image_code/(?P<uuid>[\w-]+)/$', views.ImageCode.as_view())

2 图片验证码后端逻辑

2.1 配置Redis数据库

python 复制代码
# 配置redis数据库专⻔存储验证码
"verify_code": {  # 验证码
    "BACKEND": "django_redis.cache.RedisCache",
    "LOCATION": "redis://192.168.1.6:6379/2",
    "OPTIONS": {
        "CLIENT_CLASS": "django_redis.client.DefaultClient",
    }
}

2.2 安装模块

bash 复制代码
pip install pillow
pip install captcha

2.3 图⽚验证码视图

python 复制代码
# constants.py⽂件内容
# 图⽚验证码有效期,单位:秒
IMAGE_CODE_REDIS_EXPIRES = 300

# views.py视图⽂件
from captcha.image import ImageCaptcha
from django_redis import get_redis_connection
from newsdemo.apps.verifications import constants

class ImageCode(View):
    def get(self, request, uuid):
        # 随机⽣成四位数字
        seeds = string.digits
        random_str = random.choices(seeds, k=4)
        imgcode = "".join(random_str)
        # ⽣成图⽚验证码
        img = ImageCaptcha().generate(chars=imgcode)
        # 保存图⽚验证码到redis
        redis_conn = get_redis_connection('verify_code')

        redis_conn.setex('img_%s' % uuid, 
                         constants.IMAGE_CODE_REDIS_EXPIRES, imgcode)
        return http.HttpResponse(img, content_type='image/png')

3 图片验证码前端逻辑

3.1 Vue实现图形验证码展示

3.1.1 register.js
javascript 复制代码
mounted(){
    // ⽣成图形验证码
    this.generate_img_code();
    },
methods:{
    generate_img_code:function () {
        // ⽣成UUID generateUUID() : 封装在common.js⽂件中,需要提前引⼊
        this.uuid = generateUUID();
        // 拼接图形验证码请求地址
        this.img_url = "/imgcodes/" + this.uuid + "/";
    },
...
}
3.1.2 register.html
html 复制代码
<p class="form-row form-row-wide">
    <input style="width: 250px;" placeholder="图⽚验证码"
           type="text" class="input-text">
    <img style="height: 40px;float: right;" :src="img_url"
         @click="generate_img_code">
    <span class="error-tip">图⽚验证码有误</span>
</p>
3.1.3 图形验证码展示和存储效果

3.2 Vue实现图形验证码校验

3.2.1 register.html
html 复制代码
<p class="form-row form-row-wide">
    <input style="width: 250px;" placeholder="图⽚验证码" v-model='imgcode' 
           @blur="check_imgcode" name="imgcode"
           type="text" class="input-text">
    <img style="height: 40px;float: right;" :src="img_url"
         @click="generate_img_code">
    <span class="error-tip" v-show="error_imgcode">${error_imgcode_msg}</span>
</p>
3.2.2 register.js
javascript 复制代码
// 校验图⽚验证码
check_imgcode:function () {
    if (!this.imgcode) {
        this.error_imgcode_msg = '请填写图⽚验证码';
        this.error_imgcode = true;
    } else {
        this.error_imgcode = false;
    }
}
3.2.3 校验效果

4 短信验证码接口设计

4.1 短信验证码接⼝设计

4.1.1 请求⽅式

|--------|----------------------------------|
| 选项 | ⽅案 |
| 请求⽅法 | GET |
| 请求地址 1 | /sms_codes/(?P1[35789]\d{9})/ |

4.1.2 请求参数:路径参数和查询字符串

|---------|--------|------|-------|
| 参数名 | 类型 | 是否必传 | 说明 |
| phone | string | 是 | ⼿机号 |
| imgcode | string | 是 | 图⽚验证码 |
| uuid | string | 是 | 唯⼀编号 |

4.1.3 响应结果:JSON

|--------|------|
| 响应结果 | 响应内容 |
| code | 状态码 |
| errmsg | 错误信息 |

4.2 短信验证码接⼝定义

python 复制代码
class SMSCode(View):
    """短信验证码"""

    def get(self, reqeust, phone):
        """
        :param reqeust: 请求对象
        :param phone: ⼿机号
        :return: JSON
        """
        pass

4.3 知识要点

  1. 保存短信验证码是为注册做准备的。
  2. 为了避免⽤户使⽤图形验证码恶意测试后端提取了图形验证码后,⽴即删除图形验证码
  3. Django不具备发送短信的功能,所以我们借助 第三⽅的互亿⽆线短信平台来帮助我们发送短信验证码。

5 互亿无线短信平台

5.1 平台介绍

互亿⽆线官⽹ https://www.ihuyi.com/

⽬前注册可免费使⽤50条短信验证码

5.2 平台管理中⼼

5.3 接⼊⽂档

跳转地址:短信验证码接入指南_短信平台帮助_互亿无线 (ihuyi.com)

5.4 配置参数

在dev.py配置⽂件中添加参数

python 复制代码
# 互亿⽆线短信验证码参数
# APIID
APIID = "C74**64"
# APIKEY
APIKEY = "62de***8932d50c2"

5.5 下载Python3.8⽀持的SDK

python 复制代码
# utils/huyi_sms/sms3.py
# !/usr/local/bin/python
# -*- coding:utf-8 -*-
from urllib.request import urlopen
from urllib.parse import urlencode
from django.conf import settings
import json


def send_sms_code(smscode, phone):
    # APIID(⽤户中⼼【验证码通知短信】-【产品纵览】查看)
    account = settings.APIID
    # APIKEY(⽤户中⼼【验证码通知短信】-【产品纵览】查看)
    password = settings.APIKEY
    text = "您的验证码是:%s。请不要把验证码泄露给其他⼈。" % smscode
    data = {'account': account, 'password': password, 'content': text,
            'mobile': phone, 'format': 'json'}
    req = urlopen(url='https://106.ihuyi.com/webservice/sms.php?'
                      'method=Submit',
                  data=urlencode(data).encode())
    content = req.read().decode()
    print(content)
    # code等于2代表提交成功,否则提交失败
    # smsid等于0代表提交失败,否则显示⻓度20流⽔号
    # b'{"code":2,"msg":"\xe6\x8f\x90\xe4\xba\xa4\xe6\x88\x90\xe5\x8a\x9f",
    # "smsid":"16063783563405105174"}'
    return json.loads(content)

6 短信验证码后端逻辑

6.1 短信验证码后端逻辑实现

python 复制代码
class SMScodeView(View):
    def get(self, request, phone):
        """
        匹配并删除图形验证码
        发送短信验证码
        :param request:
        :param phone:
        :return:
        """
        # 1. 获取请求参数(路径参数+查询参数)
        imgcode_client = request.GET.get('imgcode', '')
        uuid = request.GET.get('uuid', '')

        # 2. 校验参数
        if not all([phone, imgcode_client, uuid]):
            return JsonResponse({'code': '4001', 
                                 'errormsg': '缺少必须传递的参数'})

        # 3. 校验图⽚验证码(⽤户输⼊验证码和⽣成验证码)
        redis_conn = django_redis.get_redis_connection('verify_code')
        imgcode_server = redis_conn.get('img_%s' % uuid)
        print(uuid)
        print(imgcode_server)

        # 3.1 图⽚验证码是否过期
        if imgcode_server is None:
            return JsonResponse({'code': '4002', 
                                 'errormsg': '图⽚验证码已经过期'})

        # 3.2 匹配图⽚验证码
        if imgcode_client.lower() != imgcode_server.decode('utf-8').lower():
            return JsonResponse({'code': '4003', 
                                 'errormsg': '图⽚验证码不匹配'})
        try:
            # 删除redis中的图⽚验证码
            redis_conn.delete('img_%s' % uuid)
        except Exception as e:
            logger.error(e)

        # 4. ⽣成短信验证码(6位)
        seed = string.digits
        r = random.choices(seed, k=6)
        smscode_str = "".join(r)

        # 5. 保存短信验证码(redis数据库2号库存储)
        redis_conn.setex('sms_%s' % uuid, 60, smscode_str)

        # 6. 发送短信验证码
        ret = send_sms_code(smscode_str, phone)
        if ret.code == 2:
            return JsonResponse({'code': 200, 'errormsg': 'OK'})

        # 7. 返回响应结果
        return JsonResponse({'code': 5001, 'errormsg': '发送短信验证码错误'})

7 短信验证码前端逻辑

7.1 Vue绑定短信验证码

7.1.1 register.html
html 复制代码
<p class="form-row form-row-wide">
    <input style="width: 250px;" placeholder="短信验证码" v-model='smscode'
           @blur="check_smscode" name="msgcode"
           type="text" class="input-text" id="reg_mescode">
    <span class="error-tip" v-show="error_code">${error_msgcode_msg}</span>
    <a href="javascript:;" style="position: relative;left:150px;"
       @click="send_code()">${smscode_btn} </a>
</p>
7.1.2 register.js
javascript 复制代码
check_smscode:function () {
    //获取验证码⻓度
    let reg = /^\d{6}$/;
    
    //校验规则
    if (!reg.test(this.smscode)) {
        this.error_smscode_msg = '请填写短信验证码';
        this.error_smscode = true;
    } else {
        this.error_smscode = false;
    }
}

7.2 axios请求短信验证码

7.2.1 发送短信验证码事件处理
javascript 复制代码
send_smscode:function () {
    //发送短信验证码
    //1.判断短信验证码是否正在发送
    if (this.send_flag) {
        return;
    }
    //2.修改发送状态
    this.send_flag = true;
    //3.校验⽤户输⼊的⼿机号和图⽚验证码
    this.check_phone();
    this.check_imgcode();
    if (this.error_phone || this.error_imgcode) {
        this.send_flag = false;
        return;
    }
    //4.发送短信验证码
    var url = '/smscodes/' + this.phone + '/?imgcode=' +
        this.imgcode + '&uuid=' + this.uuid;
    axios.get(url, {
        responseType: 'json'
    }).then(response => {
        console.log(response.data.code);
        console.log(typeof (response.data.code));
        if (response.data.code == '200') {
            let num = 60;
            var i = setInterval(() => {
                if (num == 1) {
                    clearInterval(i);
                    this.smscode_btn = '获取短信验证码';
                    this.send_flag = false;
                } else {
                    num -= 1;
                    this.smscode_btn = '倒计时:' + num + '秒';
                }
            }, 1000, 60)
        } else {
            if (response.data.data == '4001' || response.data.data == '4002'
                || response.data.data == '4003' || 
                response.data.data == '5001') {
                this.error_smscode_msg =
                    response.data.errormsg;
                this.error_smscode = true;
            }

            //重新⽣成图⽚验证码
            this.generate_imgcode();
            //重置发送状态
            this.send_flag = false;
        }
    }).catch(error => {
        console.log(error.response);
    });
}

8 用户注册时短信验证码校验功能

8.1 注册时短信验证前端逻辑

8.1.1 register.html
html 复制代码
<p class="form-row form-row-wide">
    <input style="width: 230px;" v-model="smscode" placeholder="短信验证码"
           @blur="check_smscode" name="msgcode"
           type="text" class="input-text" id="reg_mescode">
    <span class="error-tip" v-show="error_smscode">${error_smscode_msg}</span>

    <a href="javascript:;" style="font-size: 16px;text-align: center;
    font-weight: normal;float: right" id="reg_mescode_btn"
       able="able" @click="send_smscode">${smscode_btn}</a>
</p>
8.1.2 register.js
javascript 复制代码
check_smscode:function () {
    // 1.短信验证码格式校验
    let reg = /^\d{6}$/;
    if (!reg.test(this.smscode)) {
        this.error_smscode = true;
    } else {
        this.error_smscode = false;
    }
    // 2.⼀致性校验
    if (!this.error_smscode) {
        axios.get('/check_smscode/' + this.phone + '/?smscode=' 
            + this.smscode, {
            responseType: 'json'
        }).then(response => {
            let code = response.data.code;
            if (code == '4001' || code == '4002' || code ==
                if (code == '4001' || code == '4002' || code == '4003') {
                    this.error_smscode = true;
                    this.error_smscode_msg =
                        response.data.errormsg;
                } else {
                    this.error_smscode = false;
                }
        })
    }
}

8.2 注册时短信验证后端逻辑

python 复制代码
# verifications/views.py
class CheckSMScode(View):
    def get(self, request, phone):
        """
        ⽤户注册时短信验证码校验
        :param request:
        :param phone:
        :return:
        """
        # 接收请求参数
        smscode_client = request.GET.get('smscode', '')
        # 校验参数
        if not all([phone, smscode_client]):
            return JsonResponse({'code': '4001', 'errormsg': '缺少必传参数'})
        # 查询服务器端短信验证码
        redis_conn = django_redis.get_redis_connection('verify_code')
        smscode_server = redis_conn.get('sms_%s' % phone)
        # 匹配(⾮空判断/有效性判断)
        if smscode_server is None:
            return JsonResponse({'code': '4002', 'errormsg': '短信验证码失效'})
        smscode_server = smscode_server.decode('utf-8')
        if smscode_client != smscode_server:
            return JsonResponse({'code': '4003', 
                                 'errormsg': '短信验证码不⼀致'})
        # 响应结果
        return JsonResponse({'code': '200', 'errormsg': 'OK'})

9 避免频繁发送短信验证码

存在的问题:

  • 虽然我们在前端界⾯做了60秒倒计时功能。
  • 但是恶意⽤户可以 绕过前端界⾯向后端频繁请求短信验证码

解决办法:

  • 后端也要限制⽤户请求短信验证码的频率。60秒内只允许⼀次请求短信 验证码
  • 在Redis数据库中缓存⼀个数值,有效期设置为60秒

9.1 避免频繁发送短信验证码逻辑实现

9.1.1 提取并校验 is_send
python 复制代码
is_send = redis_conn.get('is_send_%s' % phone)
if is_send:
    return JsonResponse({'code': 4001, 'error_msg': '发送短信过于频繁'})
9.1.2 is_send 、smscode 存⼊redis数据库
python 复制代码
# 保存短信验证码
redis_conn.setex('sms_%s' % phone, 60, smscode)
# 保存is_send
redis_conn.setex('is_send_%s' % phone, 60, 1)
9.1.3 界⾯渲染 频繁发送短信提示信息
javascript 复制代码
if (response.data.code == '4001') {
    this.error_smscode_msg = response.data.error_msg;
    this.error_smscode_code = true;
}
相关推荐
Arva .9 分钟前
Redis
数据库·redis·缓存
DemonAvenger9 分钟前
MySQL与应用程序的高效交互模式:从基础到实战的最佳实践
数据库·mysql·性能优化
博一波26 分钟前
Redis 集群:连锁银行的 “多网点智能协作系统”
数据库·redis·缓存
HashData酷克数据32 分钟前
官宣:Apache Cloudberry (Incubating) 2.0.0 发布!
数据库·开源·apache·cloudberry
秋难降32 分钟前
SQL 索引突然 “罢工”?快来看看为什么
数据库·后端·sql
TDengine (老段)1 小时前
TDengine 时间函数 TODAY() 用户手册
大数据·数据库·物联网·oracle·时序数据库·tdengine·涛思数据
码界奇点1 小时前
KingbaseES一体化架构与多层防护体系如何保障企业级数据库的持续稳定与弹性扩展
数据库·架构·可用性测试
悟乙己2 小时前
数据科学家如何更好地展示自己的能力
大数据·数据库·数据科学家
皆过客,揽星河2 小时前
mysql进阶语法(视图)
数据库·sql·mysql·mysql基础语法·mysql进阶语法·视图创建修改删除
tuokuac3 小时前
Redis 的相关文件作用
数据库·redis·缓存