上一篇笔记介绍了如何构建自己的函数修饰符,这篇笔记通过使用函数修饰符改进web应用来演示如何在实际使用中运用。
添加注册和登录功能
我们先给web应用添加一个很常见的功能:注册和登录。
先新建一个用户表:
CREATE TABLE `myweb`.`user`( `id` INT NOT NULL AUTO_INCREMENT COMMENT '用户id', `name` VARCHAR(20) NOT NULL COMMENT '用户名', `password` VARCHAR(30) NOT NULL COMMENT '密码', `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`) );
先来添加一个注册页面:
route("/showRegist", methods=["GET"])
.def showRegist():
'''用户注册页面'''
return render_template("regist.html")
相应的模板文件
regist.html
的代码这里就不展示了,本文末尾会附上所有工程文件,感兴趣的自行下载。
再加入一个页面用于表单提交注册信息:
route("/doRegist", methods=["POST"])
.def doRegist():
'''注册用户'''
name = request.form['name']
password = request.form['password']
redirectUrl = ''
with MyDB2() as cursor:
_SQL = '''SELECT * FROM user
WHERE name=%s LIMIT 1'''
cursor.execute(_SQL, (name,))
users = cursor.fetchall()
if len(users) > 0:
# 已存在同名用户
redirectUrl = "/showError/already has same user/1"
# redirect("/showError/already has same user/1")
else:
# 注册用户
_SQL = '''INSERT INTO `myweb`.`user` (`name`, `password`) VALUES (%s, %s); '''
params = (name, password)
cursor.execute(_SQL, params)
# redirect('/')
redirectUrl = '/'
if redirectUrl != '':
return redirect(redirectUrl)
我们在注册逻辑中加入了重名检查,所以需要再建立一个错误信息显示页面:
route("/showError/<msg>/<type>", methods=["GET"])
.def showError(msg, type):
'''错误显示页面'''
if type == 1:
url = '/showRegist'
else:
url = '/showRegist'
return render_template('error.html', msg=msg, url=url)
错误信息显示页面需要接收错误信息等参数,关于flask如何接收url参数,可以阅读。
现在可以试试新加入的注册页面:
点击后如果没有重名,就会注册用户并跳转到其它页面,我们再看数据库:
用户添加成功。
这里的演示相当粗糙(我并不是指页面中英文混杂),我是说这里有很大的安全问题:
密码输入框没有用*掩盖用户输入的密码。
缺少重复输入密码验证。
密码以明文方式存储在数据库。
这些在实际使用中是不可能接受的,这里只用于演示。
我们再用类似的方式建立用户登录页面:
route("/login", methods=["GET"])
.def login():
'''登录页面'''
return render_template('login.html')
route('/doLogin', methods=["POST"])
.def doLogin():
'''执行登录逻辑'''
name = request.form['name']
password = request.form['password']
if checkUserPassword(name,password)==False:
return redirect('/showError/password is error/2')
return redirect('/')
def checkUserPassword(user, password):
with MyDB2() as cursor:
_SQL = '''SELECT * FROM USER
WHERE NAME=%s LIMIT 1;'''
params = (user,)
cursor.execute(_SQL, params)
users = cursor.fetchall()
if len(users) == 0:
return False
realPassword = users[0][2]
if password != realPassword:
return False
return True
现在我们的登录注册逻辑都有了,但是仅仅加上了两个功能而已,并不能实用化,因为没有使用session进行状态保持。
使用session进行状态保持
在flask框架中使用session很简单:
from flask import Flask, render_template, request, redirect, session
from mydb2 import MyDB2
web = Flask(__name__)
web.secret_key="sfdfjk@.sfdsf"
只需要两步:
-
从flask模块导入session。
-
给flask实例设置一个用于给session加密的
secret_key
。
我们先要在登录逻辑中写入session:
route('/doLogin', methods=["POST"])
.def doLogin():
'''执行登录逻辑'''
name = request.form['name']
password = request.form['password']
checkResult = checkUserPassword(name, password)
if not checkUserPassword:
return redirect('/showError/password is error/2')
# 登录成功,写入session
session['uid'] = checkResult['uid']
session['name'] = checkResult['name']
return redirect('/')
然后写一个函数根据session来判断是否已经登录:
def isLogged():
'''判断当前是否登录状态'''
if not 'uid' in session:
return False
uid = int(session['uid'])
if uid<=0:
return False
return True
OK,现在我们只需要给登录后才能看的页面加上登录检查的逻辑:
@web.route('/log')
def showLog() -> 'html':
"""展示日志页面"""
if not isLogged():
return redirect('/login')
logList = getLog()
return render_template("log.html", the_title="服务器日志", log_list=logList)
好像挺简单的,我们这里只是一个日志页需要访问控制,但如果有很多页面呢?难道要一个一个复制粘贴if
语句吗?
当然不是,该是我们的函数修饰符闪亮登场的时候了。
使用函数修饰符改善既有代码
我们新建一个login_check.py
用于编写函数修饰符:
from flask import session, redirect
from functools import wraps
def isLogged():
'''判断当前是否登录状态'''
if not 'uid' in session:
return False
uid = int(session['uid'])
if uid <= 0:
return False
return True
def loginCheck(webRoute):
'''函数修饰符,用于给webroute增加登录检查'''
@wraps(webRoute)
def loginCheckWebRoute(*params, **kvParams):
if not isLogged():
return redirect('/login')
return webRoute(*params, **kvParams)
return loginCheckWebRoute
这并不难,我们的函数修饰符只是经过三个步骤:
-
接收一个web应用的页面路由函数。
-
创建一个内部函数,在内部函数中实现先检查登录状态,如果已登录,就执行路由函数并返回其结果。
-
将内部函数作为返回值返回。
好了,现在我们可以在我们的主文件index.py
中应用这个函数修饰符了。
@web.route('/log')
@loginCheck
def showLog() -> 'html':
"""展示日志页面"""
logList = getLog()
return render_template("log.html", the_title="服务器日志", log_list=logList)
在删除不必要的代码后,只需要在路由函数前面加入@loginCheck
就可以实现在进入页面逻辑前先检查登录状态。
当然,你也可以照例给其它想要访问控制的页面加入登录检查。
现在我们应该对函数修饰符的优点有所感悟:
-
减少了重复代码的使用。
当然,你也可以用函数包装重复代码后再调用,但无疑函数修饰符的写法更简洁
-
在没有改变原有业务代码的情况下增强了代码行为,这可以让你的代码更简洁明了,将业务代码和基础性的功能代码剥离。
如果你有商业开发经验,你会对这一点深有体会。
好了,我们现在已经讲完了如何在实际工作中使用函数修饰符。我们现在可以讨论一些其它的关于函数修饰符的主题。
在这里附上目前web应用的全部工程代码:
链接:https://pan.baidu.com/s/1fm18rgWKYy_HbN9ugtREiw 提取码:nqrz
函数修饰符与设计模式
在的末尾我说过,函数修饰符这种称呼很别扭,老实说,我第一次看到的时候以为@
这个符号是函数修饰符,后来才知道它只是用来标记函数修饰符。
在我看来,其实称呼为函数修饰器更为妥当,对设计模式有所了解的朋友应该知道,设计模式中有种叫做修饰器或者说装饰器模式。其核心思想就是通过一系列技术对已有类进行封装,以实现增强其功能的目的。而这和Python中的函数修饰器的用途别无二致。
在理解设计模式的时候,我们要时刻保持这样一种认识:设计模式只是一种思想,是为了解决某一类相似问题总结出的解决方案。而且各种设计模式之前只有明显的倾向,而并没有明显的分界线。
比如装饰器和适配器,当初我阅读设计模式的时候相当费解,傻傻分不清。后来我偶然注意到电源适配器,才茅塞顿开。
适配器,顾名思义,其侧重点在于适配不同的部件,比如笔记本的电源适配器,如果没有,你直插220V高压电插座,那你笔记本立即阵亡,这时候,就需要一个适配器来对电源插座和笔记本做适配,充当之间的桥梁,让你笔记本安全使用到低压直流电。
而装饰器的侧重点在增强已有物品的功能,比如前文举的例子,给坦克加装反应装甲。
当然,你完全可以通过“魔改”装饰器来起到适配器的作用,比如我找一个某电工大神,给笔记本电脑焊装一个扩展,可以直接接高压直流电的那种。也可以实现目的,但是其可扩展性和灵活性就低很多了。
所以你看,重要的并不是其实现细节和能否互相替代,而是在合适的场景选取合适的设计模式。
现在我们说回来,这里讲的函数修饰符和中说的上下文协议,是两种设计模式,他们的侧重点不同,上下文协议更倾向于让某段代码实现自动的上下文切换。
而如同之前我们讨论的,我们完全可以用函数修饰符来尝试实现上下文协议,虽然这种“魔改”行为没有多少实际价值,但对我们理解Python本身还是一个不错的体验,更何况比较有趣。
from functools import wraps
def wrapContent(contentFunc):
@wraps(contentFunc)
def wrapContentFunc(*params,**vkParams):
print("this is a before action")
beforeReturn = [1,2,3]
vkParams['beforeReturn'] = beforeReturn
contentFunc(*params,**vkParams)
print("this is a after action")
return wrapContentFunc
@wrapContent
def contentFunc(beforeReturn):
print("this is a content")
print(beforeReturn)
contentFunc()
# this is a before action
# this is a content
# [1, 2, 3]
# this is a after action
这里最难的部分是上下文切换的时候会传给业务代码段一个句柄,这里我通过在内部函数中给可变参数vkParams
追加一个参数的方式实现句柄传递。
好了,关于函数修饰符的讨论告一段落,感谢阅读。
本系列文章的代码都存放在Github项目:
文章评论