图片和短信验证码(头条项目-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;
}
相关推荐
奈斯ing10 分钟前
【Oracle篇】深入了解执行计划中的访问路径(含表级别、B树索引、位图索引、簇表四大类访问路径)
运维·数据库·oracle·1024程序员节
qq_4130661030 分钟前
tdengine数据库使用java连接
java·数据库·tdengine
这名字应该不会重复吧3 小时前
postgreSQL创建表分区
java·数据库·postgresql
慧集通-让软件连接更简单!3 小时前
慧集通(DataLinkX)iPaaS集成平台-业务建模之业务对象(一)
数据库·api·ddd·系统集成·集成平台·业务对象·业务建模
利刃大大6 小时前
【MySQL基础篇】十三、用户与权限管理
数据库·mysql
艾思科蓝 AiScholar7 小时前
【IEEE出版,连续4年EI收录,检索稳定 |南京航空航天大学主办,航空航天交叉研究院承办】第五届传感器与信息技术国际学术会议(ICSI 2025)
数据库·人工智能·物联网·机器学习·目标跟踪·机器人·信号处理
小奥超人7 小时前
【PPT解密】ppt只读文档怎么改成可编辑文档
数据库·windows·经验分享·powerpoint·办公技巧
WhoisXMLAPI10 小时前
新的 WhoisXML API 白皮书重点分析了主要 gTLD 和 ccTLD 注册趋势
运维·服务器·网络·数据库·网络协议·安全
张声录110 小时前
Redis Exporter 安装与配置指南(v1.67.0)
数据库·redis·缓存