werkzeug综合应用 - pythonweb(4)
本文将通过一个完整的用户管理的实例使werkzeug工作起来,并且使代码分层,看起来和SpringMVC的层次相同,完成用户注册、用户登录两个功能。前提说明:
-
数据模式:User(id, uname, telephone, password)
-
文件结构:
controller | |_____ __init__.py # 导入文件,使得装饰器可以被扫描 | |_____ user_controller.py dao | |_____ __init__.py | |_____ user_dao.py utils | |_____ session.py | |_____ util.py config.py # 配置文件,配置模板、数据库连接,声明全局变量 | application.py # 处理请求相应、核心应用类 | __init__.py
-
python生成验证码
4.1 控制层
如何在方法上直接通过注解绑定请求路径呢?python的解决方案是使用装饰器
from werkzeug.routing import Map
from werkzeug.routing import Rule
# 全局配置: 路径映射 config.py
url_map = Map()
# 使得与路径匹配的是可直接调用的函数 util.py
def route(rule, method="GET", **kwargs):
def decorate(func):
kwargs['endpoint'] = func
url_map.add(Rule(rule, methods=[method], **kwargs))
return func
return decorate
# 同一种类型的请求组织在UserController一个类中 user_controller.py
class UserController:
@route("/user/register")
def to_add(self, request):
return render_template("front/user/register.html", sessionScope=request.session)
# 请求处理,函数调用 application.py
# 第一个类相关的位置参数传入None即可
adapter = url_map.bind_to_environ(environ)
endpoint, args = adapter.match()
response = endpoint(None, request, **args)
4.2 数据层:
得益于python字典以及字符串的格式化,很容易就会格式化拼接得到一条SQL语句,我们执行拿到结果即可。为了避免频繁写连接数据库准备和关闭数据库代码,直接统一到一个方法进行注解:
# 配置数据库连接池 config.py
pool = PooledDB(
**{**conn_config, **db_config}
)
# 装饰到方法上 util.py
def context_cr(use=''):
def wrapper(func):
@wraps(func)
def inner(*args, **kwargs):
conn = pool.connection()
cr = conn.cursor(DictCursor)
args[0].cr = cr
try:
res = func(*args, **kwargs)
except Exception as e:
raise e
else:
return res
finally:
cr.close()
conn.close()
return inner
return wrapper
# context_cr 修饰类的方法时, args[0]封装的是类的实例,所以把游标放到类的实例中去,方便方法使用
# user_dao.py
class UserDao:
@context_cr
def insert(self, obj, **kwargs):
sql = """
INSERT INTO user(uname, password, telephone) VALUES('{uname}', '{password}', '{telephone}')
""".format(**obj)
try:
self.cr.execute(sql)
self.cr._con.commit()
return True
except Exception as e:
return False
# 全局使用
user_dao = UserDao()
4.3 用户登录状态的记录:
SpringMVC中,我们可以直接使用HttpSession来存放用户登录的登录信息,werkzeug在0.9版本前还提供了session的解决方案,但在后续的模块中已经取消了werkzeug.contrib模块相关内容,因为session机制极具灵活,为了确保分布式应用中可以共享session, 可以将session放到redis集群里 分布式session方案。0.9版本以前提供了FileSystemSessionStore来将session信息存放到本地文件系统,以及提供了一个SessionMiddleWare解决如何将session与浏览器cookie绑定,我们可以下载0.9版本的代码学习session的实现方法,实现一个redis或sqlite的session方案,本文采用本地文件系统存放session。
# session配置,session堆 config.py
session_store = FilesystemSessionStore(path=session_path)
# 请求处理session及响应装配cookie
class Root:
def __init__(self):
self.dispatch_request = SharedDataMiddleware(self.dispatch_request, {
"/static": static_path,
"/img": image_path,
})
def dispatch_request(self, environ, start_response):
request = Request(environ)
sid = request.cookies.get("sid", None)
if not sid:
session = session_store.new()
else:
session = session_store.get(sid)
request.session = session
# ...... 路由匹配代码,省略
def wrapped_start_response(status, headers, exc_info=None):
headers.append(('Set-Cookie', dump_cookie('sid', session.sid)))
return start_response(status, headers, exc_info)
start_response_ = sid and start_response or wrapped_start_response
return response(environ, start_response_)
def __call__(self, environ, start_response):
return self.dispatch_request(environ, start_response)
4.4 python 生成验证码:
import random
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import os
from io import BytesIO
_letter_cases = "abcdefghjkmnpqrstuvwxy" # 小写字母,去除可能干扰的i,l,o,z
_upper_cases = _letter_cases.upper() # 大写字母
_numbers = ''.join(map(str, range(10))) # 数字
init_chars = ''.join((_letter_cases, _upper_cases, _numbers))
def generate_validate_code(size=(120, 30),
chars=init_chars,
img_type="JPEG",
mode="RGB",
bg_color=(230, 230, 230),
fg_color=(18, 18, 18),
font_size=20,
length=4,
draw_lines=True,
n_line=(1, 2),
draw_points=True,
point_chance=1):
'''
@param size: 图片的大小,格式(宽,高),默认为(120, 30)
@param chars: 允许的字符集合,格式字符串
@param img_type: 图片保存的格式,默认为GIF,可选的为GIF,JPEG,TIFF,PNG
@param mode: 图片模式,默认为RGB
@param bg_color: 背景颜色,默认为白色
@param fg_color: 前景色,验证码字符颜色,默认为蓝色#0000FF
@param font_size: 验证码字体大小
@param length: 验证码字符个数
@param draw_lines: 是否划干扰线
@param n_lines: 干扰线的条数范围,格式元组,默认为(1, 2),只有draw_lines为True时有效
@param draw_points: 是否画干扰点
@param point_chance: 干扰点出现的概率,大小范围[0, 100]
@return: [0]: 二进制图像数据流
@return: [1]: 验证码
'''
width, height = size # 宽, 高
img = Image.new(mode, size, bg_color) # 创建图形
draw = ImageDraw.Draw(img) # 创建画笔
def get_chars():
'''
生成给定长度的字符串,返回列表格式
'''
return random.sample(chars, length)
def create_lines():
'''
绘制干扰线
'''
line_num = random.randint(*n_line) # 干扰线条数
for i in range(line_num):
# 起始点
begin = (random.randint(0, width), random.randint(0, height))
# 结束点
end = (random.randint(0, width), random.randint(0, height))
draw.line([begin, end], fill=(0, 0, 0))
def create_points():
'''
绘制干扰点
'''
chance = min(100, max(0, int(point_chance))) # 大小限制在[0, 100]
for w in range(width):
for h in range(height):
tmp = random.randint(0, 100)
if tmp > 100 - chance:
draw.point((w, h), fill=(0, 0, 0))
def draw_code():
'''
绘制验证码字符
'''
c_chars = get_chars()
d_chars = ' '.join(c_chars) # 每个字符前后以空格隔开
font_type = os.path.join(os.path.dirname(__file__), 'DejaVu-Sans-Bold.ttf')
font = ImageFont.truetype(font_type, font_size)
font_width, font_height = font.getsize(d_chars)
draw.text(((width - font_width) / 3, (height - font_height) / 3), d_chars, font=font, fill=fg_color)
return ''.join(c_chars).lower()
if draw_lines:
create_lines()
if draw_points:
create_points()
code = draw_code()
# 图形扭曲参数
params = [
1 - float(random.randint(1, 2)) / 100,
0,
0,
0,
1 - float(random.randint(1, 10)) / 100,
float(random.randint(1, 2)) / 500,
0.001,
float(random.randint(1, 2)) / 500
]
img = img.transform(size, Image.PERSPECTIVE, params) # 创建扭曲
img = img.filter(ImageFilter.EDGE_ENHANCE_MORE) # 滤镜,边界加强(阈值更大)
f = BytesIO() # 用完之后,BytesIO会自动清掉
# img.save(f, 'png')
data = f.getvalue()
return data, code
werkzeug是可以直接构建web应用的,需要我们自己做的事情比较多,但是可以更好的理解一个web应用的运行方式:路由的匹配、请求的处理、响应的构造,所以学习werkzueg来入门pythonweb是非常有必要的,后续我们可以学习其他python重型web框架,有了基础学习起来更加得心应手,至此,werkzeug篇完。后续会将这个例子上传到github供大家下载。