一、用戶登錄功能實現(xiàn)
1.分析
業(yè)務(wù)處理流程:
- 判斷用戶輸入的賬號是否為空
- 判斷用戶輸入的密碼是否為空,格式是否正確
- 判斷用戶輸入的賬號與密碼是否正確
請求方法:POST
url定義:/users/login/
請求參數(shù):url路徑參數(shù)
| 參數(shù) | 類型 | 前端是否必須傳 | 描述 |
|---|---|---|---|
| user_account | 字符串 | 是 | 用戶輸入的賬號可以是手機(jī)號也可以是用戶名 |
| password | 字符串 | 是 | 用戶輸入的密碼 |
| remember_me | 字符串 | 是 | 用戶輸入的“是否記住我” |
注:由于是post請求,在向后端發(fā)起請求時,需要附帶csrf token
2.后端代碼實現(xiàn)
# views.py
import json
import logging
from django.views import View
from django.shortcuts import render
from django.contrib.auth import login
from .models import Users
from .forms import RegisterForms, LoginForm
from utils.res_code.rescode import Code, error_map
from utils.json_translate.json_fun import to_json_data
logger = logging.getLogger('django')
class LoginView(View):
"""
"""
def get(self, request):
"""
渲染登陸界面
:param request:
:return:
"""
return render(request, 'users/login.html')
def post(self, request):
"""
1.創(chuàng)建一個類視圖 LoginView
2.明確請求方式:
-- 請求類型: Ajax post
-- 傳參方式: 請求體 user_account password remember_me
-- url定義: '/users/login/'
3.獲取前端參數(shù):
-- json_data = request.body
4.業(yè)務(wù)處理:
-- 1.是否需要校驗 -- 需要 form表單校驗
-- 2.是否需要儲存以及儲存方式 -- 不需要
-- 3.其他:
-- form表單登陸
5.返回前端數(shù)據(jù):
-- to_json_data(errorno= , errormsg= , data= )
:param request:
:return:
"""
json_data = request.body
if not json_data:
return to_json_data(errno=Code.PARAMERR, errmsg=error_map[Code.PARAMERR])
dict_data = json.loads(json_data.decode('utf-8'))
form = LoginForm(data=dict_data, request=request)
if form.is_valid():
return to_json_data(errno=Code.OK, errmsg='恭喜你登陸成功')
else:
# 定義一個接收錯誤信息的列表
form_errormsg_list = []
for msg in form.errors.get_json_data().values():
form_errormsg_list.append(msg[0].get('message'))
form_errormsg = '/'.join(form_errormsg_list)
return to_json_data(errno=Code.PARAMERR, errmsg=form_errormsg)
# urls.py
from django.urls import path
from . import views
app_name = 'users'
urlpatterns = [
path('login/', views.LoginView.as_view(), name='user_login'),
path('register/', views.RegisterView.as_view(), name='user_register'),
]
# forms.py
import re
from django import forms
from django.db.models import Q
from django.contrib.auth import login
from django_redis import get_redis_connection
from django.core.validators import RegexValidator
from .models import Users
from .constants import USER_SESSION_EXPIRES
from verifications.constants import SMS_CODE_NUMS
password_Validator=RegexValidator(r'^\w{6,20}$', '密碼格式不正確,請重新輸入')
class LoginForm(forms.Form):
"""
獲取參數(shù)
-- user_account(username/mobile) password remember_me
業(yè)務(wù)邏輯
-- user_account password 聯(lián)合登陸
-- 實現(xiàn)自動登陸(session)
"""
user_account = forms.CharField()
password = forms.CharField(max_length=20, min_length=6, label='密碼', validators=[password_Validator,],
error_messages={
'max_length':'密碼長度要小于20',
'min_length':'密碼長度要大于6',
'required':'密碼不能為空',
})
remember_me = forms.BooleanField(required=False)
# 本項目在form表單里實現(xiàn)用戶登陸,須將login的request參數(shù)傳入
# 需要__init__ 導(dǎo)入視圖當(dāng)中的request post(self, request
def __init__(self, *args, **kwargs): # *args接收request **kwargs接收request=request *args 沒有用到也需要加 固定用法
# 定義實例屬性
self.request = kwargs.pop('request', None) # 字典方法pop() 有key則返回 value 無則返回 None
# 繼承父類
super().__init__(*args, **kwargs)
def clean_user_account(self):
cleaned_user_account = self.cleaned_data.get('user_account')
if not cleaned_user_account:
raise forms.ValidationError('用戶賬號名不能為空')
if not re.match(r'^\w{5,20}$', cleaned_user_account) and not re.match(r'^1[3-9]\d{9}$', cleaned_user_account):
raise forms.ValidationError('用戶賬號不存在')
return cleaned_user_account
def clean(self):
cleaned_data = super().clean()
cleaned_user_account = cleaned_data.get('user_account')
cleaned_password = cleaned_data.get('password')
cleaned_remember_me = cleaned_data.get('remember_me')
try:
user = Users.objects.filter(Q(username=cleaned_user_account)|Q(mobile=cleaned_user_account))
except Exception as e:
raise forms.ValidationError('內(nèi)部錯誤')
if user:
user = user.first()
if user.check_password(cleaned_password):
if cleaned_remember_me:
self.request.session.set_expiry(USER_SESSION_EXPIRES)
else:
self.request.session.set_expiry(0) # None--默認(rèn)兩周 0--默認(rèn)關(guān)閉瀏覽器就刪除
# 登陸 {% if user.is_authenticated %} 此處user傳入,可以將其渲染到模板中 因為它:'django.contrib.auth.context_processors.auth',
login(self.request, user)
else:
raise forms.ValidationError('密碼輸入不正確')
else:
raise forms.ValidationError('用戶賬號不存在')
# 在users目錄下的constants.py文件中定義如下常量:
# 用戶session信息過期時間,單位秒,這是設(shè)置為5天
USER_SESSION_EXPIRES = 5 * 24 * 60 * 60
3.前端代碼實現(xiàn)
# 在static/js/users中創(chuàng)建一個login.js文件
$(function () {
let $login = $('.form-contain'); // 獲取登錄表單元素
// for test
// console.log(document.referrer); // 將referrer url 打印到終端
// 登錄邏輯
$login.submit(function (e) {
// 阻止默認(rèn)提交操作
e.preventDefault();
// 獲取用戶輸入的賬號信息
let sUserAccount = $("input[name=telephone]").val(); // 獲取用戶輸入的用戶名或者手機(jī)號
// 判斷用戶輸入的賬號信息是否為空
if (sUserAccount === "") {
message.showError('用戶賬號不能為空');
return
}
// 判斷輸入手機(jī)號格式或者用戶名格式是否正確
if (!(/^1[3-9]\d{9}$/).test(sUserAccount) && !(/^\w{5,20}$/).test(sUserAccount)) {
message.showError('請輸入合法的用戶賬號:5-20個字符的用戶名或者11位手機(jī)號');
return
}
// 獲取用戶輸入的密碼
let sPassword = $("input[name=password]").val(); // 獲取用戶輸入的密碼
// 判斷用戶輸入的密碼是否為空
if (!sPassword) {
message.showError('密碼不能為空');
return
}
// 判斷用戶輸入的密碼是否為6-20位
if (sPassword.length < 6 || sPassword.length > 20) {
message.showError('密碼的長度需在6~20位以內(nèi)');
return
}
// 獲取用戶是否勾許"記住我",勾許為true,不勾許為false
let bStatus = $("input[type='checkbox']").is(":checked"); // 獲取用戶是否選擇記住我,勾上代表true,沒勾上代碼false
// 發(fā)起登錄請求
// 創(chuàng)建請求參數(shù)
let SdataParams = {
"user_account": sUserAccount,
"password": sPassword,
"remember_me": bStatus
};
// 創(chuàng)建ajax請求
$.ajax({
// 請求地址
url: "/users/login/", // url尾部需要添加/
// 請求方式
type: "POST",
data: JSON.stringify(SdataParams),
// 請求內(nèi)容的數(shù)據(jù)類型(前端發(fā)給后端的格式)
contentType: "application/json; charset=utf-8",
// 響應(yīng)數(shù)據(jù)的格式(后端返回給前端的格式)
dataType: "json"
})
.done(function (res) {
if (res.errno === "200") {
// 注冊成功
message.showSuccess('恭喜你,登錄成功!');
setTimeout(function () {
// 注冊成功之后重定向到打開登錄頁面之前的頁面
window.location.href = document.referrer;
}, 1000)
} else {
// 登錄失敗,打印錯誤信息
message.showError(res.errmsg);
}
})
.fail(function(){
message.showError('服務(wù)器超時,請重試!');
});
});
// get cookie using jQuery
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
let cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
// Setting the token on the AJAX request
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
}
});
});
// login.html
{% block script %}
<script src="{% static 'js/users/login.js' %}"></script>
<script src="{% static 'js/base/message.js' %}"></script>
{% endblock %}
二、用戶登出功能實現(xiàn)
1.分析
請求方法:GET
url定義:/users/logout/
實現(xiàn):調(diào)用Django自帶的logout(request)函數(shù)即可
2.后端代碼實現(xiàn)
# 在users目錄下的views.py文件中定義如下類:
class LogoutView(View):
"""
"""
def get(self, request):
logout(request)
return redirect(reverse("users:user_login"))
# 在users目錄下的urls.py文件中定義如下路由:
from django.urls import path
from . import views
app_name = 'users'
urlpatterns = [
path('logout/', views.LogoutView.as_view(), name='user_logout'),
]
3.前端代碼實現(xiàn)
# 在templates/base下的base.html中修改如下代碼:
<!-- login start -->
<div class="login-box">
<!-- is_authenticated request -->
{% if user.is_authenticated %}
<div class="author">
<i class="PyWhich py-user"></i>
<span>{{ user.username }}</span>
<ul class="author-menu">
{% if user.is_staff %}
<li><a href="#">后臺管理</a></li>
{% endif %}
<li><a href="{% url 'users:user_logout' %}">退出登錄</a></li>
</ul>
</div>
{% else %}
<div>
<i class="PyWhich py-user"></i>
<span>
<a href="{% url 'users:user_login' %}" class="login">登錄</a> / <a href="{% url 'users:user_register' %}" class="reg">注冊</a>
</span>
</div>
{% endif %}
<!-- login end -->
</div>