采用Django实现的注册登录功能
主要实现内容
参考
开通阿里云短信服务:https://help.aliyun.com/document_detail/59210.html
开通并审核成功后,可先进入下面链接测试短信服务是否能正常发送
https://api.aliyun.com/new?spm=a2c4g.11186623.2.13.4a7919d9RuZfPg#/
效果演示
登录
注册
在未登录的情况下访问:http://127.0.0.1:8000/index/。将自带跳转到登录页面
登录成功后
跳转到气泡页
项目目录结构
项目实现
只介绍比较核心的代码部分,其余的可以到Github上拉取
扩展Django自带的认证Users表
定义模型
- users/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.
#继承AbstractUser,对原有的User表进行扩展,记得在setting中修改为AUTH_USER_MODEL = 'users.LoginUser'
class LoginUser(AbstractUser):
'''
用户表
'''
phone_numbers = models.CharField(verbose_name='手机号', unique=True,max_length=11, default='')
def __str__(self):
return self.username
配置Settings.py
扩展了Users表后,需要在Settings.py声明你之后所使用的用户权限表
AUTH_USER_MODEL = 'users.LoginUser' # 扩展系统的用户表后记得添加此行
LOGIN_URL = '/login/' # 想进入需要登录才能访问的页面时,如果未登录,将跳转到LOGIN_URL指定的登录界面
实现阿里云短信发送服务
采用读取配置文件的方式加载短信发送所需要的字段,解耦。
- conf/aliyun_api.py
import configparser
import os
from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.request import CommonRequest
class AliYunSms:
def __init__(self,phone,params):
self.phone = phone
self.params = params
self.sms_param = parser_config('AliYun')
self.SignName = self.sms_param['SignName']
self.TemplateCode = self.sms_param['TemplateCode']
self.client = AcsClient(self.sms_param['ACCESS_KEY_ID'], self.sms_param['ACCESS_KEY_SECRET'], 'cn-hangzhou')
def send(self):
request = CommonRequest()
request.set_accept_format('json')
request.set_domain('dysmsapi.aliyuncs.com')
request.set_method('POST')
request.set_protocol_type('https') # https | http
request.set_version('2017-05-25')
request.set_action_name('SendSms')
request.add_query_param('RegionId', "cn-hangzhou")
request.add_query_param('PhoneNumbers', self.phone) # 接收方手机号
request.add_query_param('SignName', self.SignName) # SignName为审核通过的签名名称
request.add_query_param('TemplateCode', self.TemplateCode) # TemplateCode为审核通过的模板code
request.add_query_param('TemplateParam', self.params) # 要发送的验证码
response = self.client.do_action_with_exception(request)
return response
- 配置Django redis缓存 :settings.py
由于短信验证码的保存期限不长,而且考虑后续数量的问题,所以我们把验证码存入redis
# 配置redis缓存,短时间存储手机验证码
redis_cache = res = parser_config('Redis')
CAHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": redis_cache['LOCATION'],
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
# "PASSWORD": "密码",
"DECODE_RESPONSES":True
}
},
}
- 调用短信发送:users/views.py
class SendSmsView(View):
def get(self,request):
return render(request, 'registered.html')
def post(self,request):
# 处理/static/js/jquery.js的ajax请求:Sendpwd(sender)
phone_number = request.POST.get('phone','')
print('手机号:',phone_number)
if LoginUser.objects.filter(phone_numbers=phone_number):
ret = {"status": 40, 'msg': '该手机号已被注册'}
return HttpResponse(json.dumps(ret))
else:
code = (random.randint(1000, 100000))
# params = "{'code':%d}" % code
# 不采用celery方式发送短信
sms_obj = AliYunSms(phone_number,params)
print(sms_obj)
response = sms_obj.send()
# 采用celery发送短信
# send_sms.delay(phone_number,params)
cache.set(phone_number, code, 150) # 存入redis
ret = {"status": 20, 'msg': '验证码发送成功','code':code}
return HttpResponse(json.dumps(ret))
ajax请求实现过程
使用ajax请求可以不加载页面发送请求。
实现请求的js文件主要位于以下两个文件
- static/js/jquery.js
- static/js/jquery.step.js
处理ajax请求的路由
- users/urls.py
from django.conf.urls import url
from .views import RegisterView,IndexView,SendSmsView,CheckSmsView,PasswordSaveView,LoginView,CheckUserView
app_name = 'users'
urlpatterns = [
url(r'^register/',RegisterView.as_view(),name='register'),
url(r'^send_sms/',SendSmsView.as_view()), # 处理static/js/jquery.js的ajax请求,请求事件:Sendpwd
url(r'^check_sms/',CheckSmsView.as_view()), # 处理static/js/jquery.step.js的ajax请求,请求事件:$("#applyBtn").click(function(event)
url(r'^save_psd/',PasswordSaveView.as_view()), # 处理static/js/jquery.step.js的ajax请求,请求事件:$("#submitBtn").click(function(event)
url(r'^login/',LoginView.as_view(),name='login'),
url(r'^check_user/',CheckUserView.as_view()), # 处理static/js/jquery.js的ajax请求,请求事件:cliLogin
url(r'^index/',IndexView.as_view(),name='index'),
]
ajax请求实现,举例:
- static/js/jquery.step.js
$("#submitBtn").click(function(event) {
var txtconfirm = $.trim($("#confirmpwd").val());
var txtPwd = $("#password").val();
if ($.trim(txtPwd) == "") {
Tips('请输入你要设置的密码!');
$("#txtPwd").focus();
return;
}
if($.trim(txtconfirm) == "") {
Tips('请再次输入密码!');
$("#txtconfirm").focus();
return;
}
if( $.trim(txtconfirm) != $.trim(txtPwd) ) {
Tips('你输入的密码不匹配,请从新输入!');
$("#txtconfirm").focus();
return;
}
$.ajax({
url: "/save_psd/",
type: "POST",
dataType: "json",
data: {
'phone':$("#phone").val(),
'password':$("#confirmpwd").val()
},
success: function (data) {
console.log('sucess',data)
if (data.status == 20){
var yes=step.nextStep();
// 倒计时读秒效果实现
var second = 5;
var time = document.getElementById("second");
//定义一个方法,获取span标签,修改span标签体内容,时间--
function showTime(){
second -- ;
//判断时间如果<= 0 ,则跳转到首页
if(second <= 0){
//跳转到首页
location.href = "/login/";
}
time.innerHTML = second +"";
}
//设置定时器,1秒执行一次该方法
setInterval(showTime,1000);
}else {
console.log('密码保存失败')
Tip(data.msg);
$("#txtPwd").focus();
return;
}
}
})
});
对应视图处理
- users/views.py
class PasswordSaveView(View):
def get(self,request):
return render(request, 'registered.html')
def post(self,request):
# 处理/static/js/jquery.step.js的ajax请求:$("#submitBtn").click
phone_number = request.POST.get('phone','')
password = request.POST.get('password','')
print('手机号:',phone_number,'密码:',password)
if LoginUser.objects.filter(phone_numbers=phone_number):
ret = {"status": 40, 'msg': '该手机号已被注册'}
return HttpResponse(json.dumps(ret))
else:
# 保存注册成功的用户数据
user_profile = LoginUser(phone_numbers=phone_number)
user_profile.username = phone_number
# user_profile.is_active = False
user_profile.password = make_password(password)
user_profile.save()
ret = {"status": 20, 'msg': '注册成功!'}
return HttpResponse(json.dumps(ret))
celery实现短信异步发送
celery一般用于处理比较耗时的请求任务,而短信、邮箱发送等都属于比较耗时的任务请求,可以接入celery处理。
注意:Celery、Django和Python之间有一定的版本影响
这里采用的各个版本为:
Python 3.7
celery 4.4.2
Django 3.0.6
# 安装Celery
pip install --upgrade -U celery # 也可自行百度安装方法
# 在windows系统下,还需要安装eventlet
pip install eventlet
Celery接入步骤
- 在与settings.py同目录下创建celery.py文件
from __future__ import absolute_import
import os
from celery import Celery
# 只要是想在自己的脚本中访问Django的数据库等文件就必须配置Django的环境变量
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'login_djangoauth.settings') # login_djangoauth改为自己的项目名
# app名字
app = Celery('login_djangoauth') # login_djangoauth改为自己的项目名
# 配置celery
class Config:
BROKER_URL = 'redis://:密码@127.0.0.1:6379/2' # 记得加上密码,不然会一直报错:RecursionError: maximum recursion depth exceeded in comparison
CELERY_RESULT_BACKEND = 'redis://:密码@127.0.0.1:6379/3' # 格式:redis :// [: password@] host [: port] [/ database][? [timeout=timeout[d|h|m|s|ms|us|ns]] [&database=database]]
# 无密码的情况:'redis://127.0.0.1:6379/2'
CELERY_TIMEZONE = 'Asia/Shanghai'
CELERY_ACCEPT_CONTENT = ['json', 'pickle']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
app.config_from_object(Config)
# 到各个APP里自动发现tasks.py文件
app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
print('Request: {0!r}'.format(self.request))
- 在与settings.py同目录下创建init.py文件
from __future__ import absolute_import, unicode_literals
# 告诉Django在启动时别忘了检测我的celery文件
from .celery import app as celery_app
__all__ = ['celery_app']
- 在需要实现异步处理的应用下创建 tasks.py 文件
这里我们在 users 文件夹下创建 tasks.py 文件
from celery import shared_task
from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.request import CommonRequest
from conf.aliyun_api import parser_config
# 被@shared_task装饰的为调用的异步请求任务
@shared_task
def send_sms(phone,code):
sms_obj = AliYunSms(phone,code)
print(sms_obj)
sms_obj.send()
class AliYunSms:
def __init__(self,phone,params):
self.phone = phone
self.params = params
self.sms_param = parser_config('AliYun')
self.SignName = self.sms_param['SignName']
self.TemplateCode = self.sms_param['TemplateCode']
self.client = AcsClient(self.sms_param['ACCESS_KEY_ID'], self.sms_param['ACCESS_KEY_SECRET'], 'cn-hangzhou')
def send(self):
request = CommonRequest()
request.set_accept_format('json')
request.set_domain('dysmsapi.aliyuncs.com')
request.set_method('POST')
request.set_protocol_type('https') # https | http
request.set_version('2017-05-25')
request.set_action_name('SendSms')
request.add_query_param('RegionId', "cn-hangzhou")
request.add_query_param('PhoneNumbers', self.phone)
request.add_query_param('SignName', self.SignName)
request.add_query_param('TemplateCode', self.TemplateCode)
request.add_query_param('TemplateParam', self.params)
response = self.client.do_action_with_exception(request)
print(response)
- 调用异步请求任务
users/views.py
from .tasks import send_sms
class SendSmsView(View):
def get(self,request):
return render(request, 'registered.html')
def post(self,request):
# 处理/static/js/jquery.js的ajax请求:Sendpwd(sender)
phone_number = request.POST.get('phone','')
print('手机号:',phone_number)
if LoginUser.objects.filter(phone_numbers=phone_number):
ret = {"status": 40, 'msg': '该手机号已被注册'}
return HttpResponse(json.dumps(ret))
else:
code = (random.randint(1000, 100000))
params = "{'code':%d}" % code
# 采用celery异步发送短信
send_sms.delay(phone_number,params)
cache.set(phone_number, code, 150)
ret = {"status": 20, 'msg': '验证码发送成功','code':code}
return HttpResponse(json.dumps(ret))
- 启动Celery:另开一个终端启动celery
# login_djangoauth为项目名
celery worker -A login_djangoauth --loglevel=info --pool=solo
接入Celery后的异常问题解决
这里主要介绍个人遇到的一些异常问题,其他情况可自行百度
-
RecursionError: maximum recursion depth exceeded in comparison
方法:redis未加上密码时的异常问题,加上密码后,基本就解决了 -
Celery ValueError: not enough values to unpack (expected 3, got 0)
方法:win10上运行celery4.x就会出现这个问题,安装eventlet即可解决,pip install eventlet - **TypeError: wrap_socket() got an unexpected keyword argument '_context' **
方法:启动命令的问题,改为下面的命令启动celery
# login_djangoauth为项目名
celery worker -A login_djangoauth --loglevel=info --pool=solo
-
django.db.utils.DatabaseError: DatabaseWrapper objects created in a thread can only be used in that same thread
方法:windows平台下出现的问题,修改源码 site-packages/celery/__init__.py 下的 _patch_eventlet(0 函数
修改前
def _patch_eventlet():
import eventlet
import eventlet.debug
eventlet.monkey_patch()
blockdetect = float(os.environ.get('EVENTLET_NOBLOCK', 0))
if blockdetect:
eventlet.debug.hub_blocking_detection(blockdetect, blockdetect)
修改后
def _patch_eventlet():
import eventlet
import eventlet.debug
eventlet.monkey_patch(thread=False) # 修改部分
blockdetect = float(os.environ.get('EVENTLET_NOBLOCK', 0))
if blockdetect:
eventlet.debug.hub_blocking_detection(blockdetect, blockdetect)