需要新建应用
verifications
========================
1.请求方式
选项 | 方案 |
---|---|
请求方法 | GET |
请求地址 | image_codes/(?P |
2.请求参数:路径参数
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
uuid | string | 是 | 唯一编号 |
3.响应结果:
image/jpg
1.图形验证码视图
class ImageCodeView(View):
"""图形验证码"""
def get(self, request, uuid):
"""
:param request: 请求对象
:param uuid: 唯一标识图形验证码所属于的用户
:return: image/jpg
"""
pass
2.总路由
# verifications
url(r'^', include('verifications.urls')),
3.子路由
# 图形验证码
url(r'^image_codes/(?P[\w-]+)/$', views.ImageCodeView.as_view()),
====================
提示:
captcha
扩展包用于后端生成图形验证码
可能出现的错误
解决办法
- 安装Python处理图片的库:
pip install Pillow
准备Redis的2号库存储验证码数据
"verify_code": { # 验证码
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/2",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
class ImageCodeView(View):
"""图形验证码"""
def get(self, request, uuid):
"""
:param request: 请求对象
:param uuid: 唯一标识图形验证码所属于的用户
:return: image/jpg
"""
# 生成图片验证码
text, image = captcha.generate_captcha()
# 保存图片验证码
redis_conn = get_redis_connection('verify_code')
redis_conn.setex('img_%s' % uuid, constants.IMAGE_CODE_REDIS_EXPIRES, text)
# 响应图片验证码
return http.HttpResponse(image, content_type='image/jpg')
=========================
1.register.js
mounted(){
// 生成图形验证码
this.generate_image_code();
},
methods: {
// 生成图形验证码
generate_image_code(){
// 生成UUID。generateUUID() : 封装在common.js文件中,需要提前引入
this.uuid = generateUUID();
// 拼接图形验证码请求地址
this.image_code_url = "/image_codes/" + this.uuid + "/";
},
......
}
2.register.html
请填写图形验证码
3.图形验证码展示和存储效果
1.register.html
[[ error_image_code_message ]]
2.register.js
check_image_code(){
if(!this.image_code) {
this.error_image_code_message = '请填写图片验证码';
this.error_image_code = true;
} else {
this.error_image_code = false;
}
},
3.图形验证码校验效果
====================
==========================
1.容联云官网
- 容联云通讯网址:https://www.yuntongxun.com/
- 注册并登陆
2.容联云管理控制台
3.容联云创建应用
4.应用申请上线,并进行资质认证
5.完成资质认证,应用成功上线
6.添加测试号码
7.短信模板
1.模板短信SDK下载
2.模板短信SDK使用说明
3.集成模板短信SDK
CCPRestSDK.py
:由容联云通讯开发者编写的官方SDK文件,包括发送模板短信的方法ccp_sms.py
:调用发送模板短信的方法
4.模板短信SDK测试
ccp_sms.py
文件中
# -*- coding:utf-8 -*-
from verifications.libs.yuntongxun.CCPRestSDK import REST
# 说明:主账号,登陆云通讯网站后,可在"控制台-应用"中看到开发者主账号ACCOUNT SID
_accountSid = '8aaf070862181ad5016236f3bcc811d5'
# 说明:主账号Token,登陆云通讯网站后,可在控制台-应用中看到开发者主账号AUTH TOKEN
_accountToken = '4e831592bd464663b0de944df13f16ef'
# 请使用管理控制台首页的APPID或自己创建应用的APPID
_appId = '8aaf070868747811016883f12ef3062c'
# 说明:请求地址,生产环境配置成app.cloopen.com
_serverIP = 'sandboxapp.cloopen.com'
# 说明:请求端口 ,生产环境为8883
_serverPort = "8883"
# 说明:REST API版本号保持不变
_softVersion = '2013-12-26'
# 云通讯官方提供的发送短信代码实例
# 发送模板短信
# @param to 手机号码
# @param datas 内容数据 格式为数组 例如:{'12','34'},如不需替换请填 ''
# @param $tempId 模板Id
def sendTemplateSMS(to, datas, tempId):
# 初始化REST SDK
rest = REST(_serverIP, _serverPort, _softVersion)
rest.setAccount(_accountSid, _accountToken)
rest.setAppId(_appId)
result = rest.sendTemplateSMS(to, datas, tempId)
print(result)
for k, v in result.items():
if k == 'templateSMS':
for k, s in v.items():
print('%s:%s' % (k, s))
else:
print('%s:%s' % (k, v))
if __name__ == '__main__':
# 注意: 测试的短信模板编号为1
sendTemplateSMS('17600992168', ['123456', 5], 1)
5.模板短信SDK返回结果说明
{
'statusCode': '000000', // 状态码。'000000'表示成功,反之,失败
'templateSMS':
{
'smsMessageSid': 'b5768b09e5bc4a369ed35c444c13a1eb', // 短信唯一标识符
'dateCreated': '20190125185207' // 短信发送时间
}
}
1.封装发送短信单例类
class CCP(object):
"""发送短信的单例类"""
def __new__(cls, *args, **kwargs):
# 判断是否存在类属性_instance,_instance是类CCP的唯一对象,即单例
if not hasattr(CCP, "_instance"):
cls._instance = super(CCP, cls).__new__(cls, *args, **kwargs)
cls._instance.rest = REST(_serverIP, _serverPort, _softVersion)
cls._instance.rest.setAccount(_accountSid, _accountToken)
cls._instance.rest.setAppId(_appId)
return cls._instance
2.封装发送短信单例方法
def send_template_sms(self, to, datas, temp_id):
"""
发送模板短信单例方法
:param to: 注册手机号
:param datas: 模板短信内容数据,格式为列表,例如:['123456', 5],如不需替换请填 ''
:param temp_id: 模板编号,默认免费提供id为1的模板
:return: 发短信结果
"""
result = self.rest.sendTemplateSMS(to, datas, temp_id)
if result.get("statusCode") == "000000":
# 返回0,表示发送短信成功
return 0
else:
# 返回-1,表示发送失败
return -1
3.测试单例类发送模板短信结果
if __name__ == '__main__':
# 注意: 测试的短信模板编号为1
CCP().send_template_sms('17600992168', ['123456', 5], 1)
==============================
1.请求方式
选项 | 方案 |
---|---|
请求方法 | GET |
请求地址 | /sms_codes/(?P |
2.请求参数:路径参数和查询字符串
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
mobile | string | 是 | 手机号 |
image_code | string | 是 | 图形验证码 |
uuid | string | 是 | 唯一编号 |
3.响应结果:JSON
字段 | 说明 |
---|---|
code | 状态码 |
errmsg | 错误信息 |
class SMSCodeView(View):
"""短信验证码"""
def get(self, reqeust, mobile):
"""
:param reqeust: 请求对象
:param mobile: 手机号
:return: JSON
"""
pass
class SMSCodeView(View):
"""短信验证码"""
def get(self, reqeust, mobile):
"""
:param reqeust: 请求对象
:param mobile: 手机号
:return: JSON
"""
# 接收参数
image_code_client = reqeust.GET.get('image_code')
uuid = reqeust.GET.get('uuid')
# 校验参数
if not all([image_code_client, uuid]):
return http.JsonResponse({'code': RETCODE.NECESSARYPARAMERR, 'errmsg': '缺少必传参数'})
# 创建连接到redis的对象
redis_conn = get_redis_connection('verify_code')
# 提取图形验证码
image_code_server = redis_conn.get('img_%s' % uuid)
if image_code_server is None:
# 图形验证码过期或者不存在
return http.JsonResponse({'code': RETCODE.IMAGECODEERR, 'errmsg': '图形验证码失效'})
# 删除图形验证码,避免恶意测试图形验证码
try:
redis_conn.delete('img_%s' % uuid)
except Exception as e:
logger.error(e)
# 对比图形验证码
image_code_server = image_code_server.decode() # bytes转字符串
if image_code_client.lower() != image_code_server.lower(): # 转小写后比较
return http.JsonResponse({'code': RETCODE.IMAGECODEERR, 'errmsg': '输入图形验证码有误'})
# 生成短信验证码:生成6位数验证码
sms_code = '%06d' % random.randint(0, 999999)
logger.info(sms_code)
# 保存短信验证码
redis_conn.setex('sms_%s' % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
# 发送短信验证码
CCP().send_template_sms(mobile,[sms_code, constants.SMS_CODE_REDIS_EXPIRES // 60], constants.SEND_SMS_TEMPLATE_ID)
# 响应结果
return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '发送短信成功'})
==============================
1.register.html
[[ sms_code_tip ]]
[[ error_sms_code_message ]]
2.register.js
check_sms_code(){
if(this.sms_code.length != 6){
this.error_sms_code_message = '请填写短信验证码';
this.error_sms_code = true;
} else {
this.error_sms_code = false;
}
},
1.发送短信验证码事件处理
send_sms_code(){
// 避免重复点击
if (this.sending_flag == true) {
return;
}
this.sending_flag = true;
// 校验参数
this.check_mobile();
this.check_image_code();
if (this.error_mobile == true || this.error_image_code == true) {
this.sending_flag = false;
return;
}
// 请求短信验证码
let url = '/sms_codes/' + this.mobile + '/?image_code=' + this.image_code+'&uuid='+ this.uuid;
axios.get(url, {
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
// 倒计时60秒
var num = 60;
var t = setInterval(() => {
if (num == 1) {
clearInterval(t);
this.sms_code_tip = '获取短信验证码';
this.sending_flag = false;
} else {
num -= 1;
// 展示倒计时信息
this.sms_code_tip = num + '秒';
}
}, 1000, 60)
} else {
if (response.data.code == '4001') {
this.error_image_code_message = response.data.errmsg;
this.error_image_code = true;
} else { // 4002
this.error_sms_code_message = response.data.errmsg;
this.error_sms_code = true;
}
this.generate_image_code();
this.sending_flag = false;
}
})
.catch(error => {
console.log(error.response);
this.sending_flag = false;
})
},
2.发送短信验证码效果展示
1.接收短信验证码参数
sms_code_client = request.POST.get('sms_code')
2.保存注册数据之前,对比短信验证码
redis_conn = get_redis_connection('verify_code')
sms_code_server = redis_conn.get('sms_%s' % mobile)
if sms_code_server is None:
return render(request, 'register.html', {'sms_code_errmsg':'无效的短信验证码'})
if sms_code_client != sms_code_server.decode():
return render(request, 'register.html', {'sms_code_errmsg': '输入短信验证码有误'})
1.register.html
[[ sms_code_tip ]]
[[ error_sms_code_message ]]
{% if sms_code_errmsg %}
{{ sms_code_errmsg }}
{% endif %}
存在的问题:
- 虽然我们在前端界面做了60秒倒计时功能。
- 但是恶意用户可以绕过前端界面向后端频繁请求短信验证码。
解决办法:
- 在后端也要限制用户请求短信验证码的频率。60秒内只允许一次请求短信验证码。
- 在Redis数据库中缓存一个数值,有效期设置为60秒。
1.提取、校验send_flag
send_flag = redis_conn.get('send_flag_%s' % mobile)
if send_flag:
return http.JsonResponse({'code': RETCODE.THROTTLINGERR, 'errmsg': '发送短信过于频繁'})
2.重新写入send_flag
# 保存短信验证码
redis_conn.setex('sms_%s' % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
# 重新写入send_flag
redis_conn.setex('send_flag_%s' % mobile, constants.SEND_SMS_CODE_INTERVAL, 1)
3.界面渲染频繁发送短信提示信息
if (response.data.code == '4001') {
this.error_image_code_message = response.data.errmsg;
this.error_image_code = true;
} else { // 4002
this.error_sms_code_message = response.data.errmsg;
this.error_sms_code = true;
}
=========================
Redis的 C - S 架构:
- 基于客户端-服务端模型以及请求/响应协议的TCP服务。
- 客户端向服务端发送一个查询请求,并监听Socket返回。
- 通常是以阻塞模式,等待服务端响应。
- 服务端处理命令,并将结果返回给客户端。
存在的问题:
- 如果Redis服务端需要同时处理多个请求,加上网络延迟,那么服务端利用率不高,效率降低。
解决的办法:
- 管道pipeline
管道pipeline
- 可以一次性发送多条命令并在执行完后一次性将结果返回。
- pipeline通过减少客户端与Redis的通信次数来实现降低往返延时时间。
实现的原理
- 实现的原理是队列。
- Client可以将三个命令放到一个tcp报文一起发送。
- Server则可以将三条命令的处理结果放到一个tcp报文返回。
- 队列是先进先出,这样就保证数据的顺序性。
1.实现步骤
1. 创建Redis管道
2. 将Redis请求添加到队列
3. 执行请求
2.代码实现
# 创建Redis管道
pl = redis_conn.pipeline()
# 将Redis请求添加到队列
pl.setex('sms_%s' % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
pl.setex('send_flag_%s' % mobile, constants.SEND_SMS_CODE_INTERVAL, 1)
# 执行请求
pl.execute()
===============================
思考:
- 下面两行代码存在什么问题?
问题:
- 我们的代码是自上而下同步执行的。
- 发送短信是耗时的操作。如果短信被阻塞住,用户响应将会延迟。
- 响应延迟会造成用户界面的倒计时延迟。
解决:
- 异步发送短信
- 发送短信和响应分开执行,将
发送短信
从主业务中解耦
出来。
思考:
- 如何将
发送短信
从主业务中解耦
出来。
发送短信
从主业务中解耦
出来,我们引入生产者消费者设计模式
。总结:
================================
RabbitMQ
、ActiveMQ
、Kafka
等等。
RabbitMQ
好于ActiveMQ
RabbitMQ
和ActiveMQ
都支持RabbitMQ
好于ActiveMQ
RabbitMQ
弱于Kafka
RabbitMQ
好于Kafka
比较Kafka
是处理日志的,是日志系统,所以并没有具备一个成熟MQ应该具备的特性。1.安装Erlang
- 由于 RabbitMQ 是采用 Erlang 编写的,所以需要安装 Erlang 语言库。
# 1. 在系统中加入 erlang apt 仓库
$ wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb
$ sudo dpkg -i erlang-solutions_1.0_all.deb
# 2. 修改 Erlang 镜像地址,默认的下载速度特别慢
$ vim /etc/apt/sources.list.d/erlang-solutions.list
# 替换默认值
$ deb https://mirrors.liuboping.com/erlang/ubuntu/ xenial contrib
# 3. 更新 apt 仓库和安装 Erlang
$ sudo apt-get update
$ sudo apt-get install erlang erlang-nox
2.安装RabbitMQ
- 安装成功后,默认就是启动状态。
# 1. 先在系统中加入 rabbitmq apt 仓库,再加入 rabbitmq signing key
$ echo 'deb http://www.rabbitmq.com/debian/ testing main' | sudo tee /etc/apt/sources.list.d/rabbitmq.list
$ wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | sudo apt-key add -
# 2. 更新 apt 仓库和安装 RabbitMQ
$ sudo apt-get update
$ sudo apt-get install rabbitmq-server
# 重启
$ sudo systemctl restart rabbitmq-server
# 启动
$ sudo systemctl start rabbitmq-server
# 关闭
$ sudo systemctl stop rabbitmq-server
3.Python访问RabbitMQ
- RabbitMQ提供默认的administrator账户。
- 用户名和密码:
guest
、guest
- 协议:
amqp
- 地址:
localhost
- 端口:
5672
- 查看队列中的消息:
sudo rabbitctl list_queues
# Python3虚拟环境下,安装pika
$ pip install pika
# 生产者代码:rabbitmq_producer.py
import pika
# 链接到RabbitMQ服务器
credentials = pika.PlainCredentials('guest', 'guest')
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost',5672,'/',credentials))
#创建频道
channel = connection.channel()
# 声明消息队列
channel.queue_declare(queue='zxc')
# routing_key是队列名 body是要插入的内容
channel.basic_publish(exchange='', routing_key='zxc', body='Hello RabbitMQ!')
print("开始向 'zxc' 队列中发布消息 'Hello RabbitMQ!'")
# 关闭链接
connection.close()
# 消费者代码:rabbitmq_customer.py
import pika
# 链接到rabbitmq服务器
credentials = pika.PlainCredentials('guest', 'guest')
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost',5672,'/',credentials))
# 创建频道,声明消息队列
channel = connection.channel()
channel.queue_declare(queue='zxc')
# 定义接受消息的回调函数
def callback(ch, method, properties, body):
print(body)
# 告诉RabbitMQ使用callback来接收信息
channel.basic_consume(callback, queue='zxc', no_ack=True)
# 开始接收信息
channel.start_consuming()
# 新建用户,并设置密码
$ sudo rabbitmqctl add_user admin your_password
# 设置标签为administrator
$ sudo rabbitmqctl set_user_tags admin administrator
# 设置所有权限
$ sudo rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"
# 查看用户列表
sudo rabbitmqctl list_users
# 删除用户
$ sudo rabbitmqctl delete_user admin
1.准备配置文件
- 安装好
RabbitMQ
之后,在/etc/rabbitmq
目录下面默认没有配置文件,需要单独下载。
$ cd /etc/rabbitmq/
$ wget https://raw.githubusercontent.com/rabbitmq/rabbitmq-server/master/docs/rabbitmq.config.example
$ sudo cp rabbitmq.config.example rabbitmq.config
2.设置配置文件
$ sudo vim rabbitmq.config
# 设置配置文件结束后,重启RabbitMQ服务端
$ sudo systemctl restart rabbitmq-server
配置完成后,使用rabbitmq_producer.py
、rabbitmq_customer.py
测试。
==============================
思考:
- 消费者取到消息之后,要消费掉(执行任务),需要我们去实现。
- 任务可能出现高并发的情况,需要补充多任务的方式执行。
- 耗时任务很多种,每种耗时任务编写的生产者和消费者代码有重复。
- 取到的消息什么时候执行,以什么样的方式执行。
结论:
- 实际开发中,我们可以借助成熟的工具
Celery
来完成。- 有了
Celery
,我们在使用生产者消费者模式时,只需要关注任务本身,极大的简化了程序员的开发流程。
Celery介绍:
消息队列(broker)
在客户端
和消费者
之间进行协调。安装Celery:
$ pip install -U Celery
Celery官方文档
1.定义Celery包
2.创建Celery实例
celery_tasks.main.py
# celery启动文件
from celery import Celery
# 创建celery实例
celery_app = Celery('meiduo')
3.加载Celery配置
celery_tasks.config.py
# 指定消息队列的位置
broker_url= 'amqp://guest:[email protected]:5672'
celery_tasks.main.py
# celery启动文件
from celery import Celery
# 创建celery实例
celery_app = Celery('meiduo')
# 加载celery配置
celery_app.config_from_object('celery_tasks.config')
1.注册任务:celery_tasks.main.py
# celery启动文件
from celery import Celery
# 创建celery实例
celery_app = Celery('meiduo')
# 加载celery配置
celery_app.config_from_object('celery_tasks.config')
# 自动注册celery任务
celery_app.autodiscover_tasks(['celery_tasks.sms'])
2.定义任务:celery_tasks.sms.tasks.py
# bind:保证task对象会作为第一个参数自动传入
# name:异步任务别名
# retry_backoff:异常自动重试的时间间隔 第n次(retry_backoff×2^(n-1))s
# max_retries:异常自动重试次数的上限
@celery_app.task(bind=True, name='ccp_send_sms_code', retry_backoff=3)
def ccp_send_sms_code(self, mobile, sms_code):
"""
发送短信异步任务
:param mobile: 手机号
:param sms_code: 短信验证码
:return: 成功0 或 失败-1
"""
try:
send_ret = CCP().send_template_sms(mobile, [sms_code, constants.SMS_CODE_REDIS_EXPIRES // 60], constants.SEND_SMS_TEMPLATE_ID)
except Exception as e:
logger.error(e)
# 有异常自动重试三次
raise self.retry(exc=e, max_retries=3)
if send_ret != 0:
# 有异常自动重试三次
raise self.retry(exc=Exception('发送短信失败'), max_retries=3)
return send_ret
$ cd ~/projects/meiduo_project/meiduo_mall
$ celery -A celery_tasks.main worker -l info
-A
指对应的应用程序, 其参数是项目中 Celery实例的位置。worker
指这里要启动的worker。-l
指日志等级,比如info
等级。
# 发送短信验证码
# CCP().send_template_sms(mobile,[sms_code, constants.SMS_CODE_REDIS_EXPIRES // 60], constants.SEND_SMS_TEMPLATE_ID)
# Celery异步发送短信验证码
ccp_send_sms_code.delay(mobile, sms_code)
celery worker -A proj --concurrency=4
celery worker -A proj --concurrency=1000 -P eventlet -c 1000
# 安装eventlet模块
$ pip install eventlet
# 启用 Eventlet 池
$ celery -A celery_tasks.main worker -l info -P eventlet -c 1000