暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

Django系列14-员工管理系统实战--用户登陆

原创 只是甲 2022-09-16
851

Table of Contents

一. cookie和session

1.1 http无状态短连接

截止目前为止,我们的项目都是直接访问url的,没有使用用户登陆,此时浏览器与服务器之间都是通过短连接的形式来进行交互的,但是我没操作一次,都会重新建立一次短连接,如果是高并发的环境,会带来性能的瓶颈。
image.png

1.2 cookie和session

如下图所示:
当浏览器与服务器之间建立了一个连接后,就会将创建连接时候的信息保存下来,在浏览器端就是cookie,在server端就是session,然后只要连接不主动断开或不过期,就不用重复认证。
image.png

二. 中间件

2.1 登陆页面设计

登录成功后:

  1. cookie,随机字符串
  2. session,用户信息

在其他需要登录才能访问的页面中,都需要加入:

def index(request): info = request.session.get("info") if not info: return redirect('/login/')

目标:在18个视图函数前面统一加入判断。

info = request.session.get("info") if not info: return redirect('/login/')

2.2 中间件的体验

中间件有点类似拦截器,在浏览器和服务器中间的一层,所有的操作都需要经过这一层。
有了中间件,我们就不需要在18个视图(后面可能增加更多)的视图函数加入判断了。

image.png

定义中间件

from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse class M1(MiddlewareMixin): """ 中间件1 """ def process_request(self, request): # 如果方法中没有返回值(返回None),继续向后走 # 如果有返回值 HttpResponse、render 、redirect print("M1.process_request") return HttpResponse("无权访问") def process_response(self, request, response): print("M1.process_response") return response class M2(MiddlewareMixin): """ 中间件2 """ def process_request(self, request): print("M2.process_request") def process_response(self, request, response): print("M2.process_response") return response

应用中间件 setings.py

MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'app01.middleware.auth.M1', 'app01.middleware.auth.M2', ]

在中间件的process_request方法

# 如果方法中没有返回值(返回None),继续向后走 # 如果有返回值 HttpResponse、render 、redirect,则不再继续向后执行。

三. 用户登陆功能实现

3.1 URL调整

# 登录 path('login/', account.login), path('logout/', account.logout), path('image/code/', account.image_code),

调整为如下,直接访问默认页面就进入了登陆页面:
image.png

3.2 后端功能实现

3.2.1 中间件实现登陆校验

auth.py

from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse, redirect class AuthMiddleware(MiddlewareMixin): def process_request(self, request): # 0.排除那些不需要登录就能访问的页面 # request.path_info 获取当前用户请求的URL /login/ if request.path_info in ["/login/", "/image/code/"]: return # 1.读取当前访问的用户的session信息,如果能读到,说明已登陆过,就可以继续向后走。 info_dict = request.session.get("info") # print(info_dict) if info_dict: return # 2.没有登录过,重新回到登录页面 return redirect('/login/')

settings.py

MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'app01.middleware.auth.AuthMiddleware', ]

3.2.2 验证码功能实现

code.py

import random from PIL import Image, ImageDraw, ImageFont, ImageFilter def check_code(width=120, height=30, char_length=5, font_file='Monaco.ttf', font_size=28): code = [] img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255)) draw = ImageDraw.Draw(img, mode='RGB') def rndChar(): """ 生成随机字母 :return: """ # return str(random.randint(0, 9)) return chr(random.randint(65, 90)) def rndColor(): """ 生成随机颜色 :return: """ return (random.randint(0, 255), random.randint(10, 255), random.randint(64, 255)) # 写文字 font = ImageFont.truetype(font_file, font_size) for i in range(char_length): char = rndChar() code.append(char) h = random.randint(0, 4) draw.text([i * width / char_length, h], char, font=font, fill=rndColor()) # 写干扰点 for i in range(40): draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor()) # 写干扰圆圈 for i in range(40): draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor()) x = random.randint(0, width) y = random.randint(0, height) draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor()) # 画干扰线 for i in range(5): x1 = random.randint(0, width) y1 = random.randint(0, height) x2 = random.randint(0, width) y2 = random.randint(0, height) draw.line((x1, y1, x2, y2), fill=rndColor()) img = img.filter(ImageFilter.EDGE_ENHANCE_MORE) return img, ''.join(code)

3.2.3 视图函数

account.py

from django.shortcuts import render, HttpResponse, redirect from django import forms from io import BytesIO from app01.utils.code import check_code from app01 import models from app01.utils.bootstrap import BootStrapForm from app01.utils.encrypt import md5 class LoginForm(BootStrapForm): username = forms.CharField( label="用户名", widget=forms.TextInput, required=True ) password = forms.CharField( label="密码", widget=forms.PasswordInput(render_value=True), required=True ) code = forms.CharField( label="验证码", widget=forms.TextInput, required=True ) def clean_password(self): pwd = self.cleaned_data.get("password") return md5(pwd) def login(request): """ 登录 """ if request.method == "GET": form = LoginForm() return render(request, 'login.html', {'form': form}) form = LoginForm(data=request.POST) if form.is_valid(): # 验证成功,获取到的用户名和密码 # {'username': 'wupeiqi', 'password': '123',"code":123} # {'username': 'wupeiqi', 'password': '5e5c3bad7eb35cba3638e145c830c35f',"code":xxx} # 验证码的校验 user_input_code = form.cleaned_data.pop('code') code = request.session.get('image_code', "") if code.upper() != user_input_code.upper(): form.add_error("code", "验证码错误") return render(request, 'login.html', {'form': form}) # 去数据库校验用户名和密码是否正确,获取用户对象、None # admin_object = models.Admin.objects.filter(username=xxx, password=xxx).first() admin_object = models.Admin.objects.filter(**form.cleaned_data).first() if not admin_object: form.add_error("password", "用户名或密码错误") # form.add_error("username", "用户名或密码错误") return render(request, 'login.html', {'form': form}) # 用户名和密码正确 # 网站生成随机字符串; 写到用户浏览器的cookie中;在写入到session中; request.session["info"] = {'id': admin_object.id, 'name': admin_object.username} # session可以保存7天 request.session.set_expiry(60 * 60 * 24 * 7) return redirect("/admin/list/") return render(request, 'login.html', {'form': form}) def image_code(request): """ 生成图片验证码 """ # 调用pillow函数,生成图片 img, code_string = check_code() # 写入到自己的session中(以便于后续获取验证码再进行校验) request.session['image_code'] = code_string # 给Session设置60s超时 request.session.set_expiry(60) stream = BytesIO() img.save(stream, 'png') return HttpResponse(stream.getvalue()) def logout(request): """ 注销 """ request.session.clear() return redirect('/login/')

3.3 前端页面

3.3.1 login.html

{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.min.css' %}"> <style> .account { width: 400px; border: 1px solid #dddddd; border-radius: 5px; box-shadow: 5px 5px 20px #aaa; margin-left: auto; margin-right: auto; margin-top: 100px; padding: 20px 40px; } .account h2 { margin-top: 10px; text-align: center; } </style> </head> <body> <div class="account"> <h2>用户登录</h2> <form method="post" novalidate> {% csrf_token %} <div class="form-group"> <label>用户名</label> {{ form.username }} <span style="color: red;">{{ form.username.errors.0 }}</span> </div> <div class="form-group"> <label>密码</label> {{ form.password }} <span style="color: red;">{{ form.password.errors.0 }}</span> </div> <div class="form-group"> <label for="id_code">图片验证码</label> <div class="row"> <div class="col-xs-7"> {{ form.code }} <span style="color: red;">{{ form.code.errors.0 }}</span> </div> <div class="col-xs-5"> <img id="image_code" src="/image/code/" style="width: 125px;"> </div> </div> </div> <input type="submit" value="登 录" class="btn btn-primary"> </form> </div> </body> </html>

3.3.2 error.html

{% extends 'layout.html' %} {% block content %} <div class="container"> <div class="alert alert-danger" role="alert">{{ msg }}</div> </div> {% endblock %}

3.3.3 layout.html

之前我们把页面上登陆的用户写死了,现在需要获取到当前登陆的用户,然后从显示。

{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.min.css' %}"> <style> .navbar { border-radius: 0; } </style> {% block css %}{% endblock %} </head> <body> <nav class="navbar navbar-default"> <div class="container"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#"> 员工管理系统 </a> </div> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li><a href="/admin/list/">管理员账户</a></li> <li><a href="/depart/list/">部门管理</a></li> <li><a href="/user/list/">用户管理</a></li> <li><a href="/pretty/list/">靓号管理</a></li> </ul> <ul class="nav navbar-nav navbar-right"> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ request.session.info.name }} <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="#">个人资料</a></li> <li><a href="#">我的信息</a></li> <li role="separator" class="divider"></li> <li><a href="/logout/">注销</a></li> </ul> </li> </ul> </div> </div> </nav> <div> {% block content %}{% endblock %} </div> <script src="{% static 'js/jquery-3.6.0.min.js' %}"></script> <script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.min.js' %}"></script> {% block js %}{% endblock %} </body> </html>

四. 页面测试

直接访问: http://127.0.0.1:8000/

image.png

验证码必须填写:
image.png

先验证验证码,后验证用户名和密码:
image.png

image.png

登陆成功页面:
image.png

参考:

  1. https://www.bilibili.com/video/BV1NL41157ph
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论