知识点:
sms_code = '%06d' % random.randint(0, 999999)
context
属性的用法pipline
的使用图片验证码是使用第三方的工具包来生成,在页面一经加载和点击图片的时候都会重新加载,然后保存图片验证码的真实文本内容到redis,在进行短信验证的时候,先取出短信验证码进行校验在发送短信(需要注意的是,从redis中取出短信验证码的时候,需要先decode,再lower判断是否与前端传过来的内容一致)
访问方式: GET /image_codes/(?P[\w-]+)/
先创建一个verifications
应用,在该应用下实现图片验证码
依赖: 需要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')
# 完成之后在应用路由和总路由中添加路由信息
//在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 + '/';
},}
在浏览器中输入api.meiduo.site:8000/image_codes/11 ,跳出验证码图片表示成功
在浏览器页面中,用户一经页面加载就会生成图片验证码,因为浏览器遇到陌生或者不同的资源,就会触发html的img :src="image_code_url"功能,这是在register.js中生成 (:在html中表示绑定)
在浏览器页面中,用户点击验证码图片,对应html中的@click="get_image_code"功能,再转哦到register.js中的get_image_code函数, 这样在html中绑定的:src就会加载新的验证码
在发送短信验证码之前,先校验前端传过来的验证码是否与redis中存的一致.若一致,使用random.randint
生成验证码设置时效保存到redis,使用第三方应用云通信来发送短信验证码,在发送短信到成功是一次通信的过程,所以可能会发生耗时操作,所以引入了celery,异步发送短信
在序列化器中获取视图函数中传的参数: mobile = self.context['view'].kwargs['mobile']
# 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)
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
添加url
在html中通过click调用js中的send_sms_code方法
在js中的send_sms_code方法中通过axios向后端接口发起请求,使其发送短信验证码,如果发送成功,启动定时器60s倒计时
什么情况下会产生跨域?
答: 一个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