接下来正式进入网站的功能开发。要完成后台管理系统登录功能,通过查看登录页面,我们可以了解到,我们需要编写验证码图片获取接口和登录处理接口,然后在登录页面的HTML上编写AJAX。

  在进行接口开发之前,还有一个重要的事情要处理,那就是对站点进行初始化,如果不进行初始化,那么独立文件编写的接口将会找不到,要将异常错误写入日志文件也会找不到路径,下面先上代码。

  打开main.py文件,改为下面代码(大家可以比较一下和之前代码有什么不同)

 #!/usr/bin/evn python
# coding=utf-8 import bottle
import sys
import os
import logging
import urllib.parse
from bottle import default_app, get, run, request, hook
from beaker.middleware import SessionMiddleware # 导入工具函数包
from common import web_helper, log_helper
# 导入api代码模块(初始化api文件夹里的各个访问路由,这一句不能删除,删除后将无法访问api文件夹里的各个接口)
import api #############################################
# 初始化bottle框架相关参数
#############################################
# 获取当前main.py文件所在服务器的绝对路径
program_path = os.path.split(os.path.realpath(__file__))[0]
# 将路径添加到python环境变量中
sys.path.append(program_path)
# 让提交数据最大改为2M(如果想上传更多的文件,可以在这里进行修改)
bottle.BaseRequest.MEMFILE_MAX = 1024 * 1024 * 2 #############################################
# 初始化日志相关参数
#############################################
# 如果日志目录log文件夹不存在,则创建日志目录
if not os.path.exists('log'):
os.mkdir('log')
# 初始化日志目录路径
log_path = os.path.join(program_path, 'log')
# 定义日志输出格式与路径
logging.basicConfig(level=logging.INFO,
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
filename="%s/info.log" % log_path,
filemode='a') # 设置session参数
session_opts = {
'session.type': 'file',
'session.cookie_expires': 3600,
'session.data_dir': '/tmp/sessions/simple',
'session.auto': True
} @hook('before_request')
def validate():
"""使用勾子处理接口访问事件""" # 获取当前访问的Url路径
path_info = request.environ.get("PATH_INFO")
# 过滤不用做任何操作的路由(即过滤不用进行判断是否登录和记录日志的url)
if path_info in ['/favicon.ico', '/', '/api/verify/']:
return
### 记录客户端提交的参数 ###
# 获取当前访问url路径与ip
request_log = 'url:' + path_info + ' ip:' + web_helper.get_ip()
try:
# 添加json方式提交的参数
if request.json:
request_log = request_log + ' params(json):' + urllib.parse.unquote(str(request.json))
except:
pass
try:
# 添加GET方式提交的参数
if request.query_string:
request_log = request_log + ' params(get):' + urllib.parse.unquote(str(request.query_string))
# 添加POST方式提交的参数
if request.method == 'POST':
request_log = request_log + ' params(post):' + urllib.parse.unquote(str(request.params.__dict__))
# 存储到日志文件中
log_helper.info(request_log)
except:
pass # 处理ajax提交的put、delete等请求转换为对应的请求路由(由于AJAX不支持RESTful风格提交,所以需要在这里处理一下,对提交方式进行转换)
if request.method == 'POST' and request.POST.get('_method'):
request.environ['REQUEST_METHOD'] = request.POST.get('_method', '') # 过滤不用进行登录权限判断的路由(登录与退出登录不用检查是否已经登录)
url_list = ["/api/login/", "/api/logout/"]
if path_info in url_list:
pass
else:
# 已经登录成功的用户session肯定有值,没有值的就是未登录
session = web_helper.get_session()
# 获取用户id
manager_id = session.get('id', 0)
login_name = session.get('login_name', 0)
# 判断用户是否登录
if not manager_id or not login_name:
web_helper.return_raise(web_helper.return_msg(-404, "您的登录已失效,请重新登录")) # 函数主入口
if __name__ == '__main__':
app_argv = SessionMiddleware(default_app(), session_opts)
run(app=app_argv, host='0.0.0.0', port=9090, debug=True, reloader=True)
else:
# 使用uwsgi方式处理python访问时,必须要添加这一句代码,不然无法访问
application = SessionMiddleware(default_app(), session_opts)

  main.py文件里有详细的注释说明,所以不进行细说,在这里讲一讲文件大体的思路。

  因为我们编写的接口文件都放在api文件夹中,当web服务启动后需要将api里的接口文件自动装载进来,让我们可以通过url访问里面的接口,所以需要在main.py这个入口函数中,对api文件夹里的接口文件进行导入,前面讲解到我们api文件夹里有一个__init__.py文件,它会自动帮我们导入当前文件夹里的所有文件,所以我们只需要在main.py中添加import api这一行代码就可以了。

  另外,我们需要告诉python服务当前程序所在的路径,所以需要将当前文件所在的绝对路径添加到python环境变量中(第21到23行)

  我们要记录异常信息到日志,要记录客户端访问的url与提交的请求参数,方便出错时帮助我们进行排查错误,所以要初始化日志文件格式与存储路径(第30到39行)

  bottle框架有两个好用的勾子处理函数(具体流程如下图),客户端访问接口时,首先会从bottle web服务绑定的入口进入,然后调用before_request这个勾子函数(第50到97行),执行完里面的代码后再进入对应的接口函数里,当接口函数运行完毕后,又会调用after_request这个勾子函数(我们使用了nginx处理前端访问服务不存在跨域问题,所以main.py就没有添加这个勾子函数),运行完里面的代码后才返回最终结果给客户端。所以我们有很多事情可以放在这两个勾子函数中进行处理。before_request中我们可以运行初始化操作、记录客户端访问的url与提交的请求参数操作、判断用户是否已经登录等操作(如果没有这个勾子函数,我们要判断用户是否登录,就必须在每个接口文件中处理,这样一方面代码会很冗余,出现大量重复的没有必要的代码,另一方面也很容易出错或遗漏掉,造成后端权限访问漏洞。而after_request这个函数通过是用来处理输出HTTP头信息等内容,比如跨域处理等。

  

  第55到78行,会将客户端访问的url与各种方式提交的请求参数记录到日志。对于一些不想记录到日志的访问,可以添加到第57行。(如下图)

  

  第90到96行,对登录用户访问进行处理,如果未登录的,则会返回-404状态,客户端的ajax接收到这个状态后,自行处理跳转到登录页面。

  验证码接口

  我们在api文件夹中创建verify.py文件

#!/usr/bin/python
#coding: utf-8 from io import BytesIO
from bottle import get, response
from common import verify_helper, log_helper, web_helper @get('/api/verify/')
def get_verify():
"""生成验证码图片"""
try:
# 获取生成验证码图片与验证码
code_img, verify_code = verify_helper.create_verify_code() # 将字符串转化成大写保存到session中
s = web_helper.get_session()
s['verify_code'] = verify_code.upper()
s.save() # 输出图片流
buffer = BytesIO()
code_img.save(buffer, "jpeg")
code_img.close()
response.set_header('Content-Type', 'image/jpg')
return buffer.getvalue()
except Exception as e:
log_helper.error(str(e.args))

  code_img, verify_code = verify_helper.create_verify_code() :运行verify_helper.create_verify_code() ,会返回图片流和验证码,python语言执行函数后,可以直接返回字符串、数值、元组、字典、列表等各种类型的值,返回元组类型值时,就可以使用这样的方式进行接收。(verify_helper需要导入PIL包,在python3中已更改为pillow包了,所以我们需要执行pip进行安装:pip install pillow)

  log_helper.error(str(e.args))  这是我们前面工具函数包时所讲到的错误记录函数,当生成验证码出现异常时,它会将异常信息记录到日志文件中,并将异常发送到我们指定的邮箱。

  添加完这个文件后,我们就可以运行一下main.py,然后在浏览器中输入http://127.0.0.1:9090/api/verify/或http://127.0.0.1:81/api/verify/,就可以看到生成的验证码了(如果使用81端口无法访问,请参考我的第一个python web开发框架(7)——本地部署前端访问服务器 章节进行处理)

  

  登录接口

  我们在api文件夹中创建login.py文件

 #!/usr/bin/evn python
# coding=utf-8 from bottle import put
from common import web_helper, encrypt_helper, db_helper @put('/api/login/')
def post_login():
"""用户登陆验证"""
##############################################################
# 获取并验证客户端提交的参数
##############################################################
username = web_helper.get_form('username', '帐号')
password = web_helper.get_form('password', '密码')
verify = web_helper.get_form('verify', '验证码')
ip = web_helper.get_ip() ##############################################################
# 从session中读取验证码信息
##############################################################
s = web_helper.get_session()
verify_code = s.get('verify_code')
# 删除session中的验证码(验证码每提交一次就失效)
if 'verify_code' in s:
del s['verify_code']
s.save()
# 判断用户提交的验证码和存储在session中的验证码是否相同
if verify.upper() != verify_code:
return web_helper.return_msg(-1, '验证码错误') ##############################################################
### 获取登录用户记录,并进行登录验证 ###
##############################################################
sql = """select * from manager where login_name='%s'""" % (username,)
# 从数据库中读取用户信息
manager_result = db_helper.read(sql)
# 判断用户记录是否存在
if not manager_result:
return web_helper.return_msg(-1, '账户不存在') ##############################################################
### 验证用户登录密码与状态 ###
##############################################################
# 对客户端提交上来的验证进行md5加密将转为大写(为了密码的保密性,这里进行双重md5加密,加密时从第一次加密后的密串中提取一段字符串出来进行再次加密,提取的串大家可以自由设定)
# pwd = encrypt_helper.md5(encrypt_helper.md5(password)[1:30]).upper()
# 对客户端提交上来的验证进行md5加密将转为大写(只加密一次)
pwd = encrypt_helper.md5(password).upper()
# 检查登录密码输入是否正确
if pwd != manager_result[0].get('login_password', ''):
return web_helper.return_msg(-1, '密码错误')
# 检查该账号虽否禁用了
if manager_result[0].get('is_enable', 0) == 0:
return web_helper.return_msg(-1, '账号已被禁用') ##############################################################
### 把用户信息保存到session中 ###
##############################################################
manager_id = manager_result[0].get('id', 0)
s['id'] = manager_id
s['login_name'] = username
s.save() ##############################################################
### 更新用户信息到数据库 ###
##############################################################
# 更新当前管理员最后登录时间、Ip与登录次数(字段说明,请看数据字典)
sql = """update manager set last_login_time=%s, last_login_ip=%s, login_count=login_count+1 where id=%s"""
# 组合更新值
vars = ('now()', ip, manager_id,)
# 写入数据库
db_helper.write(sql, vars) return web_helper.return_msg(0, '登录成功')

  在编写登录接口前,我们首先要了解登录接口处理的流程是怎么样的

  

  login.py后台登录处理接口代码可以看到,路由我们使用的是@put('/api/login/'),RESTful风格中,post是用于新增记录,put是用于修改或改变服务器数据,登录我理解它肯定不是新增,它是改变用户登录的状态,所以这里使用put方式接收

  登录接口的代码有详细的注释,还有上面的流程图,所以就不再深入解说,大家自己看代码,如有不明白的,文章后面留言。

  前端登录html页面(login.html)

 <!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="renderer" content="webkit|ie-comp|ie-stand">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
<meta http-equiv="Cache-Control" content="no-siteapp"/>
<!--[if lt IE 9]>
<script type="text/javascript" src="lib/html5shiv.js"></script>
<script type="text/javascript" src="lib/respond.min.js"></script>
<![endif]-->
<link href="static/h-ui/css/H-ui.min.css" rel="stylesheet" type="text/css"/>
<link href="static/h-ui.admin/css/H-ui.login.css" rel="stylesheet" type="text/css"/>
<link href="static/h-ui.admin/css/style.css" rel="stylesheet" type="text/css"/>
<link href="lib/Hui-iconfont/1.0.8/iconfont.css" rel="stylesheet" type="text/css"/>
<!--[if IE 6]>
<script type="text/javascript" src="lib/DD_belatedPNG_0.0.8a-min.js"></script>
<script>DD_belatedPNG.fix('*');</script>
<![endif]-->
<title>后台登录 - H-ui.admin v3.1</title>
<meta name="keywords" content="H-ui.admin v3.1,H-ui网站后台模版,后台模版下载,后台管理系统模版,HTML后台模版下载">
<meta name="description" content="H-ui.admin v3.1,是一款由国人开发的轻量级扁平化网站后台模板,完全免费开源的网站后台管理系统模版,适合中小型CMS后台系统。">
</head>
<body>
<input type="hidden" id="TenantId" name="TenantId" value=""/>
<div class="header"></div>
<div class="loginWraper">
<div id="loginform" class="loginBox">
<form class="form form-horizontal">
<div class="row cl">
<label class="form-label col-xs-3"><i class="Hui-iconfont">&#xe60d;</i></label>
<div class="formControls col-xs-8">
<input id="username" name="username" type="text" placeholder="账号" class="input-text size-L">
</div>
</div>
<div class="row cl">
<label class="form-label col-xs-3"><i class="Hui-iconfont">&#xe60e;</i></label>
<div class="formControls col-xs-8">
<input id="password" name="password" type="password" placeholder="密码" class="input-text size-L">
</div>
</div>
<div class="row cl">
<div class="formControls col-xs-8 col-xs-offset-3">
<input id="verify" name="verify" class="input-text size-L" type="text" value=""
style="width:150px;">
<img style="width: 100px;height: 40px;padding: 0px;vertical-align:middle" id="verifycode"
src="/api/verify/" onclick="get_verify()"> <a href="javascript:;" onclick="get_verify()">看不清,换一张</a></div>
</div>
<div class="row cl">
<div>
<h5 class="formControls col-xs-8 col-xs-offset-3"><span id="msg" style="color:#F00"></span></h5>
</div>
</div>
<div class="row cl">
<div class="col-xs-8 col-xs-offset-3">
<input type="button" class="btn btn-success size-L" onclick="submit1()"
value="&nbsp;登&nbsp;&nbsp;&nbsp;&nbsp;录&nbsp;">
</div>
</div>
</form>
</div>
</div>
<div class="footer">Copyright 你的公司名称 by H-ui.admin v3.1</div>
<script type="text/javascript" src="lib/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript" src="static/h-ui/js/H-ui.min.js"></script>
<script>
function submit1() {
if ($("#username").val().trim().length == '') {
$("#msg").html('').append('请输入用户名');
}
else if ($("#password").val().trim().length == '') {
$("#msg").html('').append('请输入登录密码');
}
else if ($("#verify").val().trim().length != 4) {
$("#msg").html('').append('请输入4位图形验证码');
} else {
username = $("#username").val();
password = $("#password").val();
verify = $("#verify").val();
$.ajax({
type: 'POST',
url: "/api/login/",
data: {'_method': 'put', 'username': username, 'password': password, 'verify': verify},
dataType: 'json',
success: function (data) {
if(data && data.state>-1){
$(location).prop('href', 'main.html');
}
else{
$("#msg").html('').append(data.msg);
get_verify();
}
},
error: function(data){
if (data){
alert(data.msg);
}
get_verify();
}
});
}
} function get_verify() {
$("#verifycode").attr("src", "/api/verify/?" + 100 * Math.random());
} </script>
</body>
</html>

  对前面下载的login.html页面进行了微调,添加了请求的AJAX代码。

  由于火狐和谷歌运行AJAX不支持PUT、DELETE等提交方式,所以AJAX提交时type类型还是POST方式,在提交参数项里面,需要增加 _method 这个参数,值为put。(由于本系列使用的是RESTful风格,所以虽然有点麻烦,但不影响我们的使用)

  html和js我也不进行详细说明,大家自己看代码吧,如果大家都要求需要对js写注释的,我到时再添加注释进去。

  相关页面功能都完成了,接下来就是进行运行调试

  在浏览器中输入:http://127.0.0.1:81/login.html 然后输入账号:admin,密码:123456,还有验证码

  

  点击登录,能正常跳转到http://127.0.0.1:81/main.html 页面,就表示登录接口能正常使用了。

  

  大家想要熟悉登录接口代码的运行,最好使用debug运行跟踪一下,看看每一行代码是怎么运行的,就清楚了。当然如果想要加深理解,最佳方式是照着代码手打一次,每完成几行就debug运行一下,看看执行效果。

  本文对应的源码下载

版权声明:本文原创发表于 博客园,作者为 AllEmpty 本文欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。

python开发QQ群:669058475(本群已满)、733466321(可以加2群)    作者博客:http://www.cnblogs.com/EmptyFS/

我的第一个python web开发框架(14)——后台管理系统登录功能的更多相关文章

  1. 我的第一个python web开发框架(41)——总结

    我的第一个python web开发框架系列博文从17年6.7月份开始写(存了近十章稿留到9月份才开始发布),到今天结束,一年多时间,想想真不容易啊. 整个过程断断续续,中间有段时间由于工作繁忙停了好长 ...

  2. 我的第一个python web开发框架(3)——怎么开始?

    小白与小美公司经过几次接触商谈,好不容易将外包签订了下来,准备开始大干一场.不过小白由于没有太多的项目经验,学过python懂得python的基本语法,在公司跟着大家做过简单功能,另外还会一些HTML ...

  3. 我的第一个python web开发框架(1)——前言

    由于之前经验不是很丰富,写的C#系统太过复杂,所以一直想重写,但学的越多越觉得自己懂的越少,越觉的底气不足.所以一直不敢动手,在内心深处对自己讲,要静下心来认真学习,继续沉淀沉淀.这两年多以来找各种机 ...

  4. 我的第一个python web开发框架(2)——一个简单的小外包

    第一部分说明 第一部分大概有20来章,主要讲的是一些开发常识.开发前中后期准备内容.开发环境与服务器部署环境安装设置.python基础框架结构与功能等内容,代码会比较简单. 本系列会以故事的方式,向大 ...

  5. 我的第一个python web开发框架(15)——公司介绍编辑功能

    完成登录以后,就会进入后台管理系统的主界面,因为这个是小项目,所以导航菜单全部固化在HTML中,不能修改.一般后台还会有一个欢迎页或关键数据展示的主页面,小项目也没有多大的必要,所以登录后直接进入公司 ...

  6. 我的第一个python web开发框架(21)——小结

    这个小网站终于成功上线,小白除了收获一笔不多的费用外,还得到女神小美的赞赏,心中满满的成就感.这一天下班后,他请老菜一起下馆子,兑现请吃饭的承诺,顺便让老菜点评一下. 小白:老大,在你的指导下终于完成 ...

  7. 我的第一个python web开发框架(22)——一个安全小事故

    在周末的一个早上,小白还在做着美梦,就收到了小美的连环追魂call,电话一直响个不停. 小白打着哈欠拿起电话:早上好美女. 小美:出事了出事了,我们公司网站一早访问是一片空白,什么内容都没有了,你赶急 ...

  8. 我的第一个python web开发框架(8)——项目结构与RESTful接口风格说明

    PS:再次说明一下,原本不想写的太啰嗦的,可之前那个系列发布后发现,好多朋友都想马上拿到代码立即能上手开发自己的项目,对代码结构.基础常识.分类目录与文件功能结构.常用函数......等等什么都不懂, ...

  9. 我的第一个python web开发框架(17)——产品管理

    这是后台管理系统最后一个功能,产品管理,它的接口与页面功能与上一章差不多. 获取产品列表接口 @get('/api/product/') def callback(): ""&qu ...

随机推荐

  1. 纳税服务系统【用户模块之使用POI导入excel、导出excel】

    前言 再次回到我们的用户模块上,我们发现还有两个功能没有完成: 对于将网页中的数据导入或导出到excel文件中,我们是完全没有学习过的.但是呢,在Java中操作excel是相对常用的,因此也有组件供我 ...

  2. temp--达州银行

    达州银行现场 服务器IP地址 192.168.1.234 ilink / ilink 自己电脑需要设置为固定IP 192.168.1.XXX 子网掩码 255.255.255.0 192.168.1. ...

  3. 手把手教你使用spring cloud+dotnet core搭建微服务架构:服务治理(-)

    背景 公司去年开始使用dotnet core开发项目.公司的总体架构采用的是微服务,那时候由于对微服务的理解并不是太深,加上各种组件的不成熟,只是把项目的各个功能通过业务层面拆分,然后通过nginx代 ...

  4. JQuery中关于浏览器兼容性的问题

      前  言 LIUWE JQuery是一个特别强大的javascript代码库,,它的操作DOM的能力是相当强大的,JQuery可以说是支持各大主流浏览器,但是随着时代的不断发展,浏览器是在不断的更 ...

  5. ng-options的使用

    参考:官方文档.zhx1991 select 无默认选择一项 <select name="" id="" class="form-control ...

  6. 翻译 | Thingking in Redux(如果你只了解MVC)

    作者:珂珂(沪江前端开发工程师) 本文原创,转载请注明作者及出处. 原文地址:https://hackernoon.com/thinking-in-redux-when-all-youve-known ...

  7. css左右布局的几种实现方式和优缺点

    记录一下左右布局的实现方式,实现的具体效果是,左侧固定宽度,高度适中等于父元素的高度,父元素的高度由右侧内容决定: html代码如下: <div class="parent" ...

  8. 教育,创新,提升:Indiegogo和Kickstarter上受中国用户支持的10个众筹项目

    中国的经济正在迅速发展,已成为世界第二大经济体.中国家庭随着经济水平的提高,越来越多父母愿意将自己的子女送到海外留学. 家长们希望自己的子女可以有机会接受国外大学优质的教育, 以便他们将来可以学成归来 ...

  9. E2195 cannot evaluate function call

    E2195 cannot evaluate function call :e2195无法评估函数的调用     :问题的根源在于,对组件创建了不合理的触发事件导致的.    :OK

  10. ssh项目访问路径及url请求书写

    在ssh项目中配置好Struts后,一般可以采用两种方式进行后台请求: 1.html形式,包括a标签,form表单,ajax等.此时的访问链接必须写全路径,可以是相对路径,也可以是绝对路径 相对路径方 ...