hoooooolyhu 2020-04-23
目前市面上可以发送短信验证码的平台有很多,此项目中使用的容联云通讯进行短信验证码的发送。
首先,我们先来进行手机验证码的逻辑分析,
通过这张图片,我们基本确定了发送短信验证码的基本逻辑,并且在业务逻辑中,我们不能让短信验证码任务阻塞了响应结果,所以我们需要采用celery方式进行异步发送短信验证码且不会阻塞响应。
既然确定了,核心逻辑和解决方案,那么接下来我们就进行代码实现了。
# 编写短信验证码视图 class SmsVerVerifications(View): def get(self, request, mobile): pass
url(r‘^sms_codes/(?P<mobile>1[3-9]\d{9})/$‘,views.SmsVerVerifications.as_view(),name=‘SMS‘)
在项目中,创建一个celery包。
注意:
celery是一个独立程序,他不属于Django程序。
# celery启动文件 from celery import Celery # 为celery使用django配置文件进行设置 import os if not os.getenv(‘DJANGO_SETTINGS_MODULE‘): os.environ[‘DJANGO_SETTINGS_MODULE‘] = ‘meiduo_mall.settings.dev‘ # 创建celery实例 celery_app = Celery(‘meiduo‘) # 加载celery配置 celery_app.config_from_object(‘celery_tasks.config‘) # 自动注册celery任务 celery_app.autodiscover_tasks([‘celery_tasks.sms_task‘])
在 celery_tasks 包中再添加一个 config.py 文件:
在这个文件中指定一下 消息队列( 中间人 )的位置:
# 如果使用 redis 作为中间人 # 需要这样配置: broker_url=‘redis://127.0.0.1:6379/3‘ # 如果使用别的作为中间人, 例如使用 rabbitmq # 则 rabbitmq 配置如下: broker_url= ‘amqp://用户名:密码@ip地址:5672‘ # 例如: # meihao: 在rabbitq中创建的用户名, 注意: 远端链接时不能使用guest账户. # 123456: 在rabbitq中用户名对应的密码 # ip部分: 指的是当前rabbitq所在的电脑ip # 5672: 是规定的端口号 broker_url = ‘amqp://meihao::5672‘
说明:
上面的配置, 选择一个即可, 我们这里使用的是 redis
如果以后进入公司, 使用的不是 redis 作为存储的中间人.
则可以像我下面设置的那样, 设置别的工具作为中间人, 例如: rabbitmq
我们还需要把 config 添加到刚刚创建的对象中:
在 celery_tasks.main.py 中, 额外增加如下代码:
from celery import Celery celery_app = Celery(‘meiduo‘) # 将刚刚的 config 配置给 celery # 里面的参数为我们创建的 config 配置文件: celery_app.config_from_object(‘celery_tasks.config‘)
我们需要在 celery_tasks 包下, 再创建一个包, 名字随意
例如, 我这里为 sms.
创建好后, 需要在里面添加一个 tasks.py 文件.
注意, 这里的 tasks 名字是规定死的.
然后需要在 celery_tasks.main.py 报备刚刚创建的文件:
from celery import Celery celery_app = Celery(‘meiduo‘) celery_app.config_from_object(‘celery_tasks.config‘) # 让 celery_app 自动捕获目标地址下的任务: # 就是自动捕获 tasks celery_app.autodiscover_tasks([‘celery_tasks.sms‘])
已经报备之后, 我们就可以在 tasks.py 中添加具体的内容了
在 celery_tasks.sms.tasks.py 文件中添加如下代码:
from celery_tasks.main import celery_app @celery_app.task(name=‘ccp_send_sms_code‘) def ccp_send_sms_code(mobile, sms_code): ‘‘‘该函数就是一个任务, 用于发送短信‘‘‘ result = CCP().send_template_sms(mobile, [sms_code, 5], 1) return result
我们创建了一个任务, 现在就可以调用该任务了.
没有使用这个任务之前, 我们发送短信调用的代码写在了
verifications.views.py 中. 所以我们现在这个任务的调用还是
在那个位置, 用该任务替换以前发送短信的函数:
from celery_tasks.sms.tasks import ccp_send_sms_code # 原来的写法: # CCP().send_template_sms(mobile, [sms_code, 5], 1) # 改为现在的写法, 注意: 这里的函数,调用的时候需要加: .delay() ccp_send_sms_code.delay(mobile, sms_code)
则, 我们现在的发送短信接口变为了:
from celery_tasks.sms.tasks import ccp_send_sms_code class SMSCodeView(View): def get(self, reqeust, mobile): redis_conn = get_redis_connection(‘verify_code‘) send_flag = redis_conn.get(‘send_flag_%s‘ % mobile) if send_flag: return http.JsonResponse({‘code‘: 400, ‘errmsg‘: ‘发送短信过于频繁‘}) image_code_client = reqeust.GET.get(‘image_code‘) uuid = reqeust.GET.get(‘image_code_id‘) if not all([image_code_client, uuid]): return http.JsonResponse({‘code‘: 400, ‘errmsg‘: ‘缺少必传参数‘}) image_code_server = redis_conn.get(‘img_%s‘ % uuid) if image_code_server is None: return http.JsonResponse({‘code‘: 400, ‘errmsg‘: ‘图形验证码失效‘}) try: redis_conn.delete(‘img_%s‘ % uuid) except Exception as e: logger.error(e) image_code_server = image_code_server.decode() if image_code_client.lower() != image_code_server.lower(): return http.JsonResponse({‘code‘: 400, ‘errmsg‘: ‘输入图形验证码有误‘}) sms_code = ‘%06d‘ % random.randint(0, 999999) logger.info(sms_code) pl = redis_conn.pipeline() pl.setex(‘sms_code_%s‘ % mobile, 300, sms_code) pl.setex(‘send_flag_%s‘ % mobile, 60, 1) pl.execute() # 原来的写法: # CCP().send_template_sms(mobile, [sms_code, 5], 1) # 改为现在的写法, 注意: 这里的函数,调用的时候需要加: .delay() ccp_send_sms_code.delay(mobile, sms_code) return http.JsonResponse({‘code‘: 0, ‘errmsg‘: ‘发送短信成功‘})
# 想要启动 celery 服务, 调用下面的命令行: cd ~/projects/meiduo_project/meiduo_mall celery -A celery_tasks.main worker -l info
参数说明:
celery : 调用 celery 命令
-A : 作用是指定要启动的文件, 这个参数后面的文件会被执行.
worker : 启动的对象是 worker, 工人, 干活的人
-l : 指日志打印等级, 一般日志都是通知形式的, 即: info 级别
启动 worker 成功以后长啥样? 下图: