Django實現註冊登錄 --- Django自帶認證系統

採用Django實現的註冊登錄功能

項目地址:https://github.com/ylpxzx/Django_Auth

主要實現內容

在這裏插入圖片描述

參考

開通阿里雲短信服務: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
  • 配置redis緩存 :settings.py
    由於短信驗證碼的保存期限不長,而且考慮後續數量的問題,所以我們把驗證碼存入redis
# 配置redis緩存,短時間存儲手機驗證碼
redis_cache = 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後的異常問題解決

這裏主要介紹個人遇到的一些異常問題,其他情況可自行百度

  1. RecursionError: maximum recursion depth exceeded in comparison
    方法:redis未加上密碼時的異常問題,加上密碼後,基本就解決了
  2. Celery ValueError: not enough values to unpack (expected 3, got 0)
    方法:win10上運行celery4.x就會出現這個問題,安裝eventlet即可解決,pip install eventlet
  3. **TypeError: wrap_socket() got an unexpected keyword argument ‘_context’ **
    方法:啓動命令的問題,改爲下面的命令啓動celery
# login_djangoauth爲項目名
celery worker -A login_djangoauth --loglevel=info --pool=solo
  1. 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)

具體完整項目,自行從Github上拉取

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章