django项目02--图片验证码、短信验证码、cors跨域问题

知识点:

  • 图片验证码的pillow模块, 及第三方工具captcha
  • 比较之后,删除图片验证码
  • 短信验证码随机生成六位数 sms_code = '%06d' % random.randint(0, 999999)
  • 短信验证码中, 序列化器的context属性的用法
  • 短信验证码中,管道pipline的使用
  • 短信验证码中,日志的使用

1. 注册的图片验证码

图片验证码是使用第三方的工具包来生成,在页面一经加载和点击图片的时候都会重新加载,然后保存图片验证码的真实文本内容到redis,在进行短信验证的时候,先取出短信验证码进行校验在发送短信(需要注意的是,从redis中取出短信验证码的时候,需要先decode,再lower判断是否与前端传过来的内容一致)

访问方式: GET /image_codes/(?P[\w-]+)/

先创建一个verifications应用,在该应用下实现图片验证码

1.后端接口

依赖: 需要pip install Pillow来安装支持图片处理的包,PIL会有bug

# views.py
class ImageCodeView(APIView):
    """图片验证码"""
    # 因为参数校验放在url中的正则中了,所以接受参数之后不需要序列化器校验;并且整个过程是返回图片,没有转换字典的过程,所以只需要继承APIView就可以了
    def get(self, request, image_code_id):
        # 使用第三方的captcha工具来生成验证码图片
        text, image = captcha.generate_captcha()
        #连接redis数据库
        redis_conn = get_redis_connection('verify_codes')
        # 保存真实值到redis; 三个参数为:key、有效期、value
        redis_conn.setex("img_%s" % image_code_id, constants.IMAGE_CODE_REDIS_EXPIRES, text)
        # 返回图片,不使用Response是因为其进一步封装,会经历render的步骤
        return HttpResponse(image, content_type='image/jpg')
# 完成之后在应用路由和总路由中添加路由信息

2.前端html和js

//在html文件中添加点击图片验证码功能和加载路由文件host.js
// 1. 在html中添加点击重新发送功能
<img :src="image_code_url" @click="get_image_code" alt="图形验证码" class="pic_code">
// 2. 在register.js中添加页面一经加载就生成验证的功能:
mounted: function() {
		this.get_image_code();
	},
// 3.在register.js中实现generate_uuid:和get_image_code功能
methods: {
		// 生成uuid
        generate_uuid: function(){
            var d = new Date().getTime();
            if(window.performance && typeof window.performance.now === "function"){
                d += performance.now(); //use high-precision timer if available
            }
            var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
                var r = (d + Math.random()*16)%16 | 0;
                d = Math.floor(d/16);
                return (c =='x' ? r : (r&0x3|0x8)).toString(16);
            });
            return uuid;
        },
		get_image_code: function(){
        	this.image_code_id = this.generate_uuid();

			this.image_code_url = this.host + '/image_codes/' + this.image_code_id + '/';
		},}

3.输入url测试生成验证码

在浏览器中输入api.meiduo.site:8000/image_codes/11  ,跳出验证码图片表示成功

4.流程整理

在浏览器页面中,用户一经页面加载就会生成图片验证码,因为浏览器遇到陌生或者不同的资源,就会触发html的img :src="image_code_url"功能,这是在register.js中生成 (:在html中表示绑定)
在浏览器页面中,用户点击验证码图片,对应html中的@click="get_image_code"功能,再转哦到register.js中的get_image_code函数, 这样在html中绑定的:src就会加载新的验证码

2. 发送短信验证码

在发送短信验证码之前,先校验前端传过来的验证码是否与redis中存的一致.若一致,使用random.randint生成验证码设置时效保存到redis,使用第三方应用云通信来发送短信验证码,在发送短信到成功是一次通信的过程,所以可能会发生耗时操作,所以引入了celery,异步发送短信

在序列化器中获取视图函数中传的参数: mobile = self.context['view'].kwargs['mobile']

1. 视图

# url('^sms_codes/(?P1[3-9]\d{9})/$', views.SMSCodeView.as_view()),
# 记录日志,导入之后实例化对象,就可以直接使用了
import logging
logger = logging.getLogger('django')

class SMSCodeView(GenericAPIView):
    """
    短信验证码
    传入参数:  mobile,     image_code_id, text
    """
    serializer_class = ImageCodeCheckSerializer

    def get(self, request, mobile):
        # 校验参数  由序列化器完成
        # get_serializer 方法在创建序列化器对象的时候,会补充context属性
        # context 属性中包含三个值 request   format  view 类视图对象
        # 所以可以使用在序列化器中使用self.context['view']来获取
        serializer = self.get_serializer(data=request.query_params)
        serializer.is_valid(raise_exception=True)

        # 生成短信验证码
        sms_code = '%06d' % random.randint(0, 999999)

        # 保存短信验证码  保存发送记录
        redis_conn = get_redis_connection('verify_codes')
        # redis_conn.setex("sms_%s" % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
        # redis_conn.setex("send_flag_%s" % mobile, constants.SEND_SMS_CODE_INTERVAL, 1)

        # redis管道  一次性发送多个上面的两个任务
        pl = redis_conn.pipeline()
        pl.setex("sms_%s" % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
        pl.setex("send_flag_%s" % mobile, constants.SEND_SMS_CODE_INTERVAL, 1)

        # 让管道通知redis执行命令
        pl.execute()

        # 发送短信
        try:
            ccp = CCP()
            expires = constants.SMS_CODE_REDIS_EXPIRES // 60
            result = ccp.send_template_sms(mobile, [sms_code, expires], constants.SMS_CODE_TEMP_ID)
        except Exception as e:
            logger.error("发送验证码短信[异常][ mobile: %s, message: %s ]" % (mobile, e))
            return Response({'message': 'failed'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        else:
            if result == 0:
                logger.info("发送验证码短信[正常][ mobile: %s ]" % mobile)
                return Response({'message': 'OK'})
            else:
                logger.warning("发送验证码短信[失败][ mobile: %s ]" % mobile)
                return Response({'message': 'failed'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

2.自定义序列化器

from rest_framework import serializers
from django_redis import get_redis_connection

# 图片验证码校验序列化器
class ImageCodeCheckSerializer(serializers.Serializer):
    image_code_id = serializers.UUIDField()
    text = serializers.CharField(max_length=4, min_length=4)

    def validate(self, attrs):  # 验证方法中的attrs参数是字典形式
        image_code_id = attrs['image_code_id']
        text = attrs['text']

        # 查询真实图片验证码
        redis_conn = get_redis_connection('verify_codes')
        real_image_code_text = redis_conn.get('img_%s' % image_code_id)
        if not real_image_code_text:
            raise serializers.ValidationError('图片验证码无效')

        # 比较图片验证码,从redis取出来的是bytes类型,需要解码
        real_image_code_text = real_image_code_text.decode()
        if real_image_code_text.lower() != text.lower():
            raise serializers.ValidationError('图片验证码错误')

        # 判断是否在60s内
        # get_serializer 方法在创建序列化器对象的时候,会补充context属性
        # context 属性中包含三个值 request   format  view 类视图对象
        #  self.context['view'] 获取到的是整个视图
        # django的类视图对象中,kwargs属性以字典形式保存了路径提取出来的参数

        mobile = self.context['view'].kwargs['mobile']
        #在视图中setex定时保存60s保存,过后会删除,如果还能取到,那就是请求过于频繁
        send_flag = redis_conn.get("send_flag_%s" % mobile)
        if send_flag:
            raise serializers.ValidationError('请求次数过于频繁')

        return attrs

3.定义url

添加url

4.补充前端

在html中通过click调用js中的send_sms_code方法
在js中的send_sms_code方法中通过axios向后端接口发起请求,使其发送短信验证码,如果发送成功,启动定时器60s倒计时

3.cors解决跨域问题

什么情况下会产生跨域?
答: 一个url中的请求协议、域名、端口只要有一个地方不同,就会产生跨域问题。


浏览器涉及到跨域问题的时候,会发送option请求,询问后端是否支持跨域
django后端通过创建中间键的方式提供option请求的支持,告诉浏览器,支持哪些域名访问,(安装扩展直接使用cors)

以下设置都出现在settings.py中:

# 添加中间键
MIDDLEWARE = ['corsheaders.middleware.CorsMiddleware',]

# 添加白名单
CORS_ORIGIN_WHITELIST = (
    '127.0.0.1:8080',
    'localhost:8080',
    'www.meiduo.site:8080',
    'api.meiduo.site:8000'
)

# 允许携带cookie
CORS_ALLOW_CREDENTIALS = True  

你可能感兴趣的:(Django)