目录
- 项目介绍和源码;
- 拿来即用的bootstrap模板;
- 服务器SSH服务配置与python中paramiko的使用;
- 用户登陆与session;
- 最简单的实践之修改服务器时间;
- 查看和修改服务器配置与数据库的路由;
- 基于websocket的实时日志实现;
- 查看服务器中的日志与前端的datatable的利用;
- 重启服务器进程。
前言
这篇文章给出了网站的用户登陆模块,用户登录模块的网站后台实现主要通过Django权限系统auth来实现,相关逻辑可与参考这篇文章。作为一个网站新手,前后端交互基本上都是自己造轮子,利用javascript和ajax完成,登陆模块包括:用户登陆、用户注册、忘记密码,其中忘记密码是用简单的用户名+邮箱来验证的,时间充裕,可以进一步的通过发邮件链接跳转验证修改密码,图1简单描述了这个系统的结构。除此之外,这篇文章会涉及到第三方模态框sweetalert2的引用、给网页增加icon、对网页增加登陆验证、session保存登陆的用户名等小细节的完善。
功能实现
前面文章已经提到,登陆网站时会优先进入登陆页面,进入app界面是由url控制的。先看下如何显示出登陆界面,这里依然秉承拿来即用的思想,把DASHGUM源文件夹的login.html文件改造下并将其重新命名为loginpage.html放在templates目录下,目录结构如下图2所示,其中login-bg.jpg为背景图,favicon.ico为网站的图标icon。
显示登陆页面
- 修改DASHGUM源文件夹的login.html文件
html代码中有两个模态框,一个管注册账号,一个管忘记密码,修改好的html重命名为loginpage.html放在上图的文件结构所示的位置。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>登陆服务器</title> <!-- Bootstrap core CSS --> <link href="/templates/servermaterial/assets/css/bootstrap.css" rel="stylesheet"> <!--external css--> <link href="/templates/servermaterial/assets/font-awesome/css/font-awesome.css" rel="stylesheet" /> <!-- Custom styles for this template --> <link href="/templates/servermaterial/assets/css/style.css" rel="stylesheet"> <link href="/templates/servermaterial/assets/css/style-responsive.css" rel="stylesheet"> <!-- sweetalert2插件的css --> <link href="/templates/servermaterial/assets/SweetAlert2/dist/sweetalert2.css" rel="stylesheet"> </head> <body> <section id="login-page"> <header> <!--logo start--> <a class="logo" style="padding:10px 0 0 100px;"><h3><b>欢迎来到服务器工具</b></h3></a> </header> <div class="container" style="padding:200px 0 0 0"> <form class="form-login"> <h2 class="form-login-heading">登陆服务器</h2> <div class="login-wrap"> <input type="text" name="username" id="loginusername" class="form-control" placeholder="账号" autofocus> <br> <input type="password" name="password" id="loginpassword" class="form-control" placeholder="密码"> <label class="checkbox"> <span class="pull-right"> <a data-toggle="modal" href='#' onclick="show_forget_modal();">忘记密码</a> </span> </label> <button class="btn btn-theme btn-block" id='login' type="submit"><i class="fa fa-lock"></i> 登陆</button> <hr> <div class="registration"> <a data-toggle="modal" href='#' onclick="show_create_modal();">注册账号</a> </div> </div> <!-- 忘记密码模态框 --> <div aria-hidden="true" aria-labelledby="myModalLabel" role="dialog" tabindex="-1" id="myModal_forget" class="modal fade"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> <h4 class="modal-title">忘记密码 ?</h4> </div> <div class="modal-body"> <p id="pforgetusername">请输入您的用户名:</p> <input type="text" name="forgetusername" placeholder="请输入需要找回的用户名" autocomplete="off" class="form-control placeholder-no-fix"> <br> <p id="pforgetemail">请输入您的邮箱地址:</p> <input type="text" name="forgetemail" placeholder="请输入账号对应的Email" autocomplete="off" class="form-control placeholder-no-fix"> <br> <p id='pnewpassword'>请设定您的新密码:</p> <input type="password" name="newpassword" placeholder="请输入新密码" autocomplete="off" class="form-control placeholder-no-fix"> </div> <div class="modal-footer"> <button data-dismiss="modal" class="btn btn-default" type="button">取消</button> <button class="btn btn-theme" id="forget" type="button">提交</button> </div> </div> </div> </div> <!-- 忘注册账号模态框 --> <div aria-hidden="true" aria-labelledby="myModalLabel" role="dialog" tabindex="-1" id="myModal_create" class="modal fade"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> <h4 class="modal-title">注册账号</h4> </div> <div class="modal-body"> <p id='pcreateusername'>请输入您的用户名:</p> <input type="text" name="createusername" placeholder="用户名(支持字母,数字,下划线)" autocomplete="off" class="form-control placeholder-no-fix"> <br> <p id='pcreateemail'>请输入您的邮箱:</p> <input type="text" name="createemail" placeholder="请填写有效的邮箱地址" autocomplete="off" class="form-control placeholder-no-fix"> <br> <p id='pcreatepassword'>请设定您的密码:</p> <input type="password" name="createpassword" placeholder="密码" autocomplete="off" class="form-control placeholder-no-fix"> </div> <div class="modal-footer"> <button data-dismiss="modal" class="btn btn-default" type="button">取消</button> <button class="btn btn-theme" id="create" type="button">注册</button> </div> </div> </div> </div> <!-- modal --> </form> </div> </section> <!-- js placed at the end of the document so the pages load faster --> <script src="/templates/servermaterial/assets/js/jquery.js"></script> <script src="/templates/servermaterial/assets/js/bootstrap.min.js"></script> <!-- sweetalert2插件的js --> <script src="/templates/servermaterial/assets/SweetAlert2/dist/sweetalert2.js"></script> <!--BACKSTRETCH--> <!-- You can use an image of whatever size. This script will stretch to fit in any screen size.--> <script type="text/javascript" src="/templates/servermaterial/assets/js/jquery.backstretch.min.js"></script> <script> // 在这里设置背景图 $.backstretch("/templates/login-bg.jpg", {speed: 500}); </script> </body> </html>
- 为页面添加urls和views
在WebTool的urls.py中加入url如下:
from django.conf.urls import include, url from django.contrib import admin import views urlpatterns = [ url(r'^admin', admin.site.urls), # 用127.0.0.1:8888访问网址时会默认转到loginpage url(r'^$', views.loginpage), # 用127.0.0.1:8888/loginpage访问网址时会转到loginpage url(r'^loginpage/', views.loginpage), ]
在WebTool的views.py添加代码:
from __future__ import unicode_literals from django.shortcuts import render_to_response from django.http import JsonResponse # 登陆页面 def loginpage(request): return render_to_response("loginpage.html")
构建项目之后,访问127.0.0.1:8888或者127.0.0.1:8888/loginpage将会显示出登陆界面,如图3。
- 修改网站的icon
默认情况下,网站是没有icon的,我们要向网站添加一个如图4中红框的icon。可以先把icon资源放在图2目录结构中(favicon.ico),然后再在WebTool的urls.py中加入url如下:
from django.conf.urls import include, url from django.contrib import admin from django.views.generic.base import RedirectView import views urlpatterns = [ url(r'^admin', admin.site.urls), url(r'^$', views.loginpage), url(r'^loginpage/', views.loginpage), # 为网站添加icon url(r'^favicon.ico$', RedirectView.as_view(url='/templates/favicon.ico')), ]
用户注册
用户注册的html代码已经在前面一并给出了,其实就是一个模态框,如图5,现在我们只需要编写控制它的javascript代码就好。但是在写的时候需要考虑到一些情况,比如用户名、邮箱、密码未填写、不合法、用户名已经注册过了这些情况,用户名、邮箱、密码未填写、不合法可以直接在前端自己写逻辑判断(我这里都是自己造轮子,一些框架已经集成这些功能,并不需要写一堆javascript代码)。然后弹出来的提示框用了sweetalert2插件,这里会说一下该插件的引用。
- 配合上面给出的html的javascript代码
<script> // 显示注册账号的模态框 function show_create_modal(){ $("input[name='createusername']").val(""); $("input[name='createpassword']").val(""); $("input[name='createemail']").val(""); document.getElementById("pcreateusername").style.color = 'black'; document.getElementById("pcreatepassword").style.color = 'black'; document.getElementById("pcreateemail").style.color = 'black'; $('#myModal_create').modal('show'); } </script> <script> // 注册按钮 $("#create").click(function(){ var user = $("input[name='createusername']").val(); var code = $("input[name='createpassword']").val(); var email = $("input[name='createemail']").val(); // 账号未填写 if (user==''){ document.getElementById("pcreateusername").style.color = 'red'; $("input[name='createusername']").focus(); return ; } // 账号名合法 var uPattern = /^[a-zA-Z0-9_]{4,16}$/; if (!uPattern.test(user)){ document.getElementById("pcreateusername").innerHTML = '请输入合法的用户名'; document.getElementById("pcreateusername").style.color = 'red'; $("input[name='createusername']").val("").focus(); return ; } // 邮箱未填写 if (email==''){ document.getElementById("pcreateemail").style.color = 'red'; $("input[name='createemail']").focus(); return ; } // 邮箱合法 var ePattern = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/; if (!ePattern.test(email)){ document.getElementById("pcreateemail").innerHTML = '请输入合法的邮箱'; document.getElementById("pcreateemail").style.color = 'red'; $("input[name='createemail']").val("").focus(); return ; } // 密码未填写 if (code==''){ document.getElementById("pcreatepassword").style.color = 'red'; $("input[name='createpassword']").focus(); return ; } // ajax请求注册 $.ajax({ url:"/createuser", type:'POST', data:{'user': user, 'email': email,'code': code}, success: function(arg){ ret = eval(arg); if(ret.status){ swal({ type: 'success', title: '注册成功!', confirmButtonText: '确定', confirmButtonColor: '#4cd964' }).then(function(){ // 注册成功后刷新页面 window.location.reload(); }); }else{ swal({ type: 'error', title: '用户名已存在!', confirmButtonText: '确定', confirmButtonColor: '#4cd964' }) } } }); }) </script>
上面中的ajax请求到后台的url即createuser。后台的urls和views代码响应前端的请求。
- 注册账号的urls和views
WebTool中urls.py如下,增加一个处理createuser:
from django.conf.urls import include, url from django.contrib import admin from django.views.generic.base import RedirectView import views urlpatterns = [ url(r'^admin', admin.site.urls), url(r'^$', views.loginpage), url(r'^favicon.ico$', RedirectView.as_view(url='/templates/favicon.ico')), url(r'^loginpage/', views.loginpage), # 处理前端的ajax请求createuser url(r'^createuser', views.createuser), ]
WebTool中views.py如下:
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.shortcuts import render_to_response from django.http import JsonResponse from django.contrib.auth.models import User from django.contrib.auth import login, authenticate # 创建账户 def createuser(request): ret = {'status': False, 'reason': ''} if request.method == 'POST': username = request.POST.get('user') email = request.POST.get('email') password = request.POST.get('code') try: # 创建用户 user = User.objects.create_user(username, email, password) user.save() except Exception as e: # 用户名已经在数据库存在 if e[0] == 1062: ret['reason'] = 'repeated' ret['status'] = False return JsonResponse(ret) ret['status'] = True return JsonResponse(ret)
- sweetalert2的引入
sweetalert2是一类提示模态框,可以在这个链接中体验,我们把插件应用在项目里取代原生的生硬警示框。首先,把这个资源下载下来放到本地的资源文件夹里。注意到,在上面的html文件中已经插入了两行关于这个插件的css和js地址:
<link href="/templates/servermaterial/assets/SweetAlert2/dist/sweetalert2.css" rel="stylesheet"> <script src="/templates/servermaterial/assets/SweetAlert2/dist/sweetalert2.js"></script>
引入上面两个文件后,通过js代码中swal就可以使用这个警示框了,当我们利用ajax请求到后台注册成功时,前端就会弹出这个警示框告诉使用者已经注册成功了,然后刷新页面,下面的代码来自于上面注册用户的javascript代码中。注册成功的效果如图7,后面的警示框都会使用这个样式。
swal({ type: 'success', title: '注册成功!', confirmButtonText: '确定', confirmButtonColor: '#4cd964' }).then(function(){ window.location.reload(); });
用户登陆与session
- 用户登陆的javascript代码与session
$('form').submit(function(event){ event.preventDefault(); var username = $("input[name='username']").val(); var password = $("input[name='password']").val(); // 用户名未填写 if (username==''){ document.getElementById("loginusername").setAttribute("placeholder","请填写用户名"); $("input[name='username']").focus(); return ; } // 用户名合法 var uPattern = /^[a-zA-Z0-9_]{4,16}$/; if (!uPattern.test(username)){ document.getElementById("loginusername").setAttribute("placeholder","请输入合法的用户名"); $("input[name='username']").val("").focus(); return ; } // 密码未填写 if (password==''){ document.getElementById("loginpassword").setAttribute("placeholder","请输入密码"); $("input[name='password']").focus(); return ; } // 请求登陆 $.ajax({ url:"/userlogin", type:'POST', data:{'username': username, 'password': password}, success: function(arg){ ret = eval(arg); if(ret.status){ window.location.href='/server'; }else{ swal({ type: 'error', title: '密码错误,请重新登陆!', confirmButtonText: '确定', confirmButtonColor: '#4cd964' }) } } }); })
上面中的ajax请求到后台的url即userlogin。后台的urls和views代码响应前端的请求。
- 用户登陆的urls和views
WebTool中urls.py如下,增加一个处理createuser和url(r'^server/', include('server.urls', namespace="server"))
前者的目的是接受ajax的登录请求,后者的作用是当ajax返回可以登陆的时候,利用它进入到server这个app里面,即127.0.0.1/server,进入127.0.0.1/server之后的路由和响应由server中的urls和views控制,如当你请求127.0.0.1/server/home时。
from django.conf.urls import include, url from django.contrib import admin from django.views.generic.base import RedirectView import views urlpatterns = [ url(r'^admin', admin.site.urls), url(r'^$', views.loginpage), url(r'^favicon.ico$', RedirectView.as_view(url='/templates/favicon.ico')), url(r'^loginpage/', views.loginpage), url(r'^createuser', views.createuser), # 用户登陆 url(r'^userlogin', views.userlogin), # 登陆成功后进入127.0.0.1:8888/server页面 url(r'^server/', include('server.urls', namespace="server")), ]
WebTool中views.py如下,在这里使用到了session,方便在进入app页面之后依然能够让网站从session中提取到现在登陆的用户名,django中的session用起来很简单,因为它实际上是使用一个字典实现的,需要记录的时候调用request.session['XX'] = XXX
。需要使用的时候可以调用request.session.get('XX')
,我们在这里利用session保存了用户名,目的就是在后面的网页中可以随时取出来。
在使用session之前,需要在django的配置文件settings.py中添加与session相关的配置如下:
# session配置 SESSION_ENGINE = 'django.contrib.sessions.backends.cache' SESSION_EXPIRE_AT_BROWSER_CLOSE = True
SESSION_ENGINE是存储session的方式,常用的有两种,分别是django.contrib.sessions.backends.cache和django.contrib.sessions.backends.db,前者存储在缓存中,可能面临丢失和溢出的风险,后者存储在数据库中,有性能开销但是安全。
SESSION_EXPIRE_AT_BROWSER_CLOSE设置为True代表关闭浏览器就会清除session。
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.shortcuts import render_to_response from django.http import JsonResponse from django.contrib.auth.models import User from django.contrib.auth import login, authenticate # 用户登陆 def userlogin(request): ret = {'status': False, 'reason': ''} if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') user = authenticate(username=username, password=password) if user is not None: # 将用户名记录在session中,方便传递 request.session['username'] = username if user.is_active: login(request, user) ret['status'] = True else: ret['reason'] = 'codewrong' return JsonResponse(ret)
忘记密码并通过邮箱重置
这个逻辑和用户注册逻辑很相似,唯独不一样的是,如果用户忘记了密码,可以通过邮箱找回来(这里为了方便,没有实现利用邮件中的链接重置密码),后台仅仅是核对邮箱和用户名匹配后就允许用户设置新密码,时间充裕可以实现完整的逻辑。忘记密码的模态框如图8所示。
- 用户忘记密码的javascript代码
<script> function show_forget_modal(){ $("input[name='forgetusername']").val(""); $("input[name='forgetemail']").val(""); $("input[name='newpassword']").val(""); document.getElementById("pforgetusername").style.color = 'black'; document.getElementById("pforgetemail").style.color = 'black'; document.getElementById("pnewpassword").style.color = 'black'; $('#myModal_forget').modal('show'); } </script> <script> $("#forget").click(function(){ var username = $("input[name='forgetusername']").val(); var email = $("input[name='forgetemail']").val(); var newpassword = $("input[name='newpassword']").val(); // 账号未填写 if (username==''){ document.getElementById("pforgetusername").style.color = 'red'; $("input[name='forgetusername']").focus(); return ; } // 账号名合法 var uPattern = /^[a-zA-Z0-9_]{4,16}$/; if (!uPattern.test(username)){ document.getElementById("pforgetusername").innerHTML = '请输入合法的用户名'; document.getElementById("pforgetusername").style.color = 'red'; $("input[name='forgetusername']").val("").focus(); return ; } // 邮箱未填写 if (email==''){ document.getElementById("forgetemail").style.color = 'red'; $("input[name='pforgetemail']").focus(); return ; } // 邮箱合法 var ePattern = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/; if (!ePattern.test(email)){ document.getElementById("pforgetemail").innerHTML = '请输入合法的邮箱'; document.getElementById("pforgetemail").style.color = 'red'; $("input[name='forgetemail']").val("").focus(); return ; } // 密码未填写 if (newpassword==''){ document.getElementById("pnewpassword").style.color = 'red'; $("input[name='newpassword']").focus(); return ; } $.ajax({ url:"/forgetusr", type:'POST', data:{'username': username, 'email': email, 'newpassword': newpassword}, success: function(arg){ ret = eval(arg); if(ret.status){ swal({ type: 'success', title: '密码修改成功!', confirmButtonText: '确定', confirmButtonColor: '#4cd964' }).then(function(){ window.location.reload(); }); }else{ // 账号不存在 if(ret.reason == 'unexist'){ swal({ type: 'error', title: '账号不存在,请重试!', confirmButtonText: '确定', confirmButtonColor: '#4cd964' }) } // 邮箱错误 if(ret.reason == 'emailwrong'){ swal({ type: 'error', title: '账号对应邮箱不正确,请重试!', confirmButtonText: '确定', confirmButtonColor: '#4cd964' }) } } } }); }) </script>
上面中的ajax请求到后台的url即forgetusr。后台的urls和views代码响应前端的请求。
- 忘记密码的urls和views
WebTool中urls.py如下,增加一个处理forgetusr的url:
from django.conf.urls import include, url from django.contrib import admin from django.views.generic.base import RedirectView import views urlpatterns = [ url(r'^admin', admin.site.urls), url(r'^$', views.loginpage), url(r'^favicon.ico$', RedirectView.as_view(url='/templates/favicon.ico')), url(r'^loginpage/', views.loginpage), url(r'^createuser', views.createuser), url(r'^userlogin', views.userlogin), # 处理ajax发起的忘记密码逻辑 url(r'^forgetusr', views.forgetusr), url(r'^server/', include('server.urls', namespace="server")), ]
WebTool中views.py如下,增加一个处理forgetusr相应的请求:
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.shortcuts import render_to_response from django.http import JsonResponse from django.contrib.auth.models import User from django.contrib.auth import login, authenticate # 忘记密码 def forgetusr(request): ret = {'status': False, 'reason': ''} if request.method == 'POST': username = request.POST.get('username') email = request.POST.get('email') newpassword = request.POST.get('newpassword') try: user = User.objects.get(username=username) except User.DoesNotExist: # 账号不存在 ret['reason'] = 'unexist' return JsonResponse(ret) email_ = User.objects.get(username=user).email # 用户名对应邮箱错误 if email_ != email: ret['reason'] = 'emailwrong' return JsonResponse(ret) user.set_password(newpassword) user.save() ret['status'] = True return JsonResponse(ret)
app中增加登出逻辑和用户名
app即server,用户登录后就会进入这个app页面,即服务器工具。
- app中显示登陆人的用户名
文章拿来即用的bootstrap模板显示出的网站页面中左上角的用户名其实是已经写好的,如图9,现在我们可以利用sessions来换成实际的登陆人的用户名。只需要把server/views.py中的homepage函数重新写下就好,然后重新走一遍登陆流程,就可以得到图10,显示为实际登陆人的用户名。
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.shortcuts import render_to_response from django.contrib.auth import logout from django.http import JsonResponse from django.contrib.auth.decorators import login_required # 服务器的名字 htmltitle = '服务器工具' def homepage(request): username = request.session.get('username') pagedict = {'title': htmltitle, 'username': username} return render_to_response('servermaterial/home.html', pagedict)
- app中增加用户登出
还记得在前面的文章拿来即用的bootstrap模板显示出的网站页面中,在右上角有一个退出服务器的按钮用来登出用户,如图11,我们这里对这个按钮完善一下。同样注意的是,这里修改的应该是app即server中的文件(urls.py、views.py),资源文件base.html也是对应的。
在base.html中添加一个对这个按钮的控制代码:
$(".logout").click(function(){ $.ajax({ url:"userlogout", type:'GET', success: function(arg){ ret = eval(arg); if(ret.status){ window.location.href = '/loginpage' } } }) });
在server中的urls.py中添加一个url:url(r'^userlogout', views.userlogout)
。
在server中的urls.py中添加一个后台响应:
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.shortcuts import render_to_response from django.contrib.auth import logout from django.http import JsonResponse from django.contrib.auth.decorators import login_required # 用户登出 def userlogout(request): ret = {'status': True} if request.method == 'GET': # 用户登出 logout(request) return JsonResponse(ret)
为页面增加权限鉴定
虽然添加了用户登陆,但是我们依然可以通过输入完整的url来越过登陆,例如我们可以通过127.0.0.1:8888/server/home直接进入服务器工具的主页,而越过了127.0.0.1:8888/loginpage的登陆,这明显是不被允许的。为了防止这样做,我们可以将所有的可以通过直接输入url浏览到的页面的view函数加一个装饰器。例如:对127.0.0.1:8888/server/home对应的页面渲染函数homepage增加一个装饰器,加了装饰器之后用户就没有办法直接跳过登陆进入服务器工具页面。装饰器是python的一种特性,如果进一步了解可以看下这篇文章。
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.shortcuts import render_to_response from django.contrib.auth import logout from django.http import JsonResponse from django.contrib.auth.decorators import login_required # 服务器的名字 htmltitle = '服务器工具' @login_required(login_url='/loginpage') def homepage(request): username = request.session.get('username') pagedict = {'title': htmltitle, 'username': username} return render_to_response('servermaterial/home.html', pagedict)
结语
本篇文章主要介绍了用户系统的实现,作为一个小白,里面的html和javascript都是手写,基本上基于ajax实现,但是其实这样并不效率,成熟的web网站会将前后端分开,使得开发人员更专注于后端或者前端的开发。