flask框架----day03( 不适用蓝图划分目录 、蓝图的使用 划分小型项目目录、 划分大型项目目录、 g对象的使用、 dbutils模块创建数据库连接池,使用pymysql注意事项)
面试
# 什么是接口幂等性问题,如何解决?
-幂等:幂等(idempotent、idempotence)是一个数学与计算机学概念
-一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同
-接口幂等性:无论调用多少次,产生的效果是一样的
-get 获取数据天然幂等
-put 修改数据天然幂等
-delete 删除 天然幂等
-post 新增数据,会出现不幂等的情况,要把它做成幂等性的
-解决:
1 唯一主键 unique 新增用户用用户名唯一,但是订单问题解决不了
2 token机制,随机字符串
-在使用新增之前,先生成一个随机字符串token,保存起来,只要调用新增接口,
就必须携带随机字符串token,到后端,校验这个token在不在我们保存的里面,
如果在,说明正常,删除token,允许新增,如果不在就不允许新增
3 前端:按钮只能点击一次
.
.
.
.
.
昨日回顾
# 1 cbv 使用
-写一个类继承MethodView,写get,post。。。
-类属性decorators = [auth,]可以加装饰器
# 2 cbv执行流程
-1 跟djagno流程一样
-2 entpoint 作用路径别名,add_url_rule(view_func=IndexView.as_view('index'))
-3 为什么entpoint不传,是被路由装饰器装饰的函数名: 函数.__name__
-4 装饰器的执行先后顺序
# 3 模板语法
# 4 请求响应
-请求:全局request对象,在不同视图函数中尽管使用,不会错乱
-method
-path
-files
-form
-argvs。。。。
-响应:四件套
-响应对象,make_response包裹一下四件套之一
-set_cookies
-响应对象.headers 响应头
# 5 session使用
-设置秘钥
-全局导入,直接赋值,取值
--------------------------------------------------------
# 6 session执行流程 重要
open_session:前端写到cookie到后端,后端取出cookie对应的value值,解密,转到session对象中,后续再视图函数中,使用session即可
save_session:请求走的时候,校验session有没有被改过,如果被改了,删除cookie,重新设置cookie
session用起来像字典---》如何做,一个对象可以像字典一样使用, __getitem__ __setitem__,
只要触发了__setitem__ 就说明动了,对象属性 modify,一开始false,只要触发了__setitem__,
置为true,后期只要判断modify,就可以判断session有没有被改过
----------------------------------------------------------
# 7 闪现:跨请求获取到之前请求存放的数据,取一次就没了 关注django的message框架
-flash('%s,我错了'%name)
-get_flashed_messages()
# 8 请求扩展
-before_request
-after_request
-before_first_request
-teardown_request:错误日志记录
-errorhandler:是某种状态码,就会执行它
.
.
.
.
.
.
.
.
.
.
.
.
.
.
今日内容
1 蓝图的使用
# blueprint 翻译过来的,称之为蓝图
# 蓝图干嘛的?
作用:之前我们写的url和视图函数都是处在同一个文件,如果项目比较大的话,
这显然不是一个合理的结构,而蓝图可以优雅的帮我们实现这种需求。
蓝图的作用就是 划分目录 让我们的Flask项目,结构更加清晰
实现项目达到分层解耦合的作用
蓝图 (Blueprint) 是 Flask 程序的模块化处理机制
它是一个存储视图方法的集合, 一个项目可以具有多个蓝图对象
蓝图可以单独拥有自己的模板、静态文件的目录
----------------------------------------------
----------------------------------------------
# 不用蓝图,划分目录,适合非常小型的项目
no_blueprint_flask # 项目名
src # 核心代码位置
__init__.py # 包 里面实例化得到了app对象,
models.py # 放表模型
views.py # 放视图函数
static # 放静态资源
templates # 放模板
home.html # 模板
manage.py # 启动文件
settings.py
---------------------------------------------------
# __init__.py 文件
from flask import Flask
app = Flask(__name__, template_folder='../templates', static_folder='../static')
# app.config['DEBUG'] = True
# app.secret_key = 'jckopjoeko8415561'
app.config.from_pyfile('settings.py')
from . import views
# 这句话不写,manage.py文件里面app.run() 没有用
# 因为views.py文件整体都没有加载,所以添加路由的代码也就没有走了,
# 所以虽然项目启动起来,但是路由匹配不上!!!
---------------------------------------------------
# views.py 文件
from . import app
from flask import render_template
# .在路径中表示当前目录
# ..在路径中表示上一层目录
# ..\..在路径中表示上上一层目录
# 视图函数
@app.route('/index')
def index():
return 'index'
@app.route('/home', defaults={'name': 'lqz'})
def home(name):
name = name
return render_template('home.html', name=name)
---------------------------------------------------
# manage.py
from src import app
if __name__ == '__main__':
app.run()
---------------------------------------------------
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1.1 蓝图的使用步骤
# flask的目录的划分,没有太多规矩,你只要觉得目录划分的合理,就可以
# 蓝图的使用步骤(每一个视图.py文件里面都可以注册一个蓝图对象!!!)
第一步:导入蓝图类 from flask import Blueprint
第二步:实例化得到蓝图对象 bp=Blueprint('user',__name__)
第三步:在app中注册蓝图 app.register_blueprint(us)
第四步:在不同的views.py中使用蓝图对象来注册路由 @bp.route('/login')
# 补充:蓝图可以有自己的静态文件和模板
# 补充:注册蓝图时,可以使用前缀,必须以 / 开头
-------------------------------------------------
.
.
.
1.2 使用蓝图,划分小型项目目录
little_blueprint # 项目名
src # src目录下放项目关键的文件
__init__.py # 包
models.py # 表模型
static # 静态文件目录
1.jpg # 图片
templates # 模板文件目录
user.html # 模板
views # 视图函数目录
order.py # 订单相关视图
user.py # 用户相关视图
manage.py # 启动文件
settings.py
----------------------------------------------
# __init__.py 文件
from flask import Flask
import sys
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent # D:\pythonProject\2023flask1\src
sys.path.append(str(BASE_DIR))
app = Flask(__name__, template_folder='templates', static_folder='static')
# app.config['DEBUG'] = True
# app.secret_key = 'jckopjoeko8415561'
from settings import DevelopmentConfig
app.config.from_object(DevelopmentConfig)
from .views.user import user_bp
from .views.order import order_bp
app.register_blueprint(user_bp) # 注册蓝图对象!!!
app.register_blueprint(order_bp) # 注册蓝图对象!!!
----------------------------------------------
# user.py 文件 蓝图类实例化生成对象
from flask import Blueprint, render_template
user_bp = Blueprint('user', __name__)
# 第一个参数表示蓝图的名称随便起 第二个参数 __name__ 表示蓝图所在的模块
@user_bp.route('/user/home', defaults={'name': 'teng'})
def home(name):
return render_template('home.html', name=name)
----------------------------------------------
----------------------------------------------
# order.py 文件
from flask import Blueprint
order_bp = Blueprint('order', __name__)
# 蓝图对象也有自己的请求扩展,但只对当前文件下的视图函数起作用,跨文件就没用了!!!
# 如果想该请求扩展,对所有视图函数都有用, 就用 @app.before_request来装饰下面的函数了
# 如果是写对所有视图函数都有用的请求扩展的话,代码可以写到init文件里面去了
@order_bp.before_request # 该请求扩展函数,只会对被该蓝图对象管理的视图函数起作用!!!
def before():
print('我来了')
@order_bp.route('/order/home')
def home():
return 'order的home页面'
@order_bp.route('/order/list1')
def list1():
return 'order的list页面'
# 该蓝图对象的请求扩展,不会对其他文件里面的视图函数起作用!!!
----------------------------------------------
# manage.py
from src import app
if __name__ == '__main__':
app.run()
.
.
.
.
.
.
.
.
.
.
.
.
1.3 使用蓝图,划分大型项目目录 多个app,像django一样
big_blueprint # 项目名
src # 核心文件
__init__.py # 包
admin # admin的app
static # 静态文件目录
templates # 模板文件目录
__init__.py # 包
models.py # 表模型
views.py # 视图函数
home # home的app 和admin的app一样,可以有单独的静态文件、模板等
order # order的app
manage.py # 启动文件
settings.py # 配置文件
------------------------------------------------------
# manage.py 启动文件
from src import app
if __name__ == '__main__':
app.run()
------------------------------------------------------
# src包 下的__init__文件
# 主要就是生成app对象 添加配置 导入蓝图对象 注册蓝图对象 蓝图对象加路由前缀等功能
from flask import Flask
app = Flask(__name__)
from settings import DevelopmentConfig
app.config.from_object(DevelopmentConfig)
from src.admin import admin_bp
from src.order import order_bp
from src.home import home_bp
# 注册蓝图
# 注意在注册蓝图前,要检查下被蓝图对象管理的视图函数所在的文件,有没有被导入
# 一般就在创建蓝图对象的代码下面,执行导入视图函数所在的文件,才能完成路由的注册!!!
app.register_blueprint(admin_bp, url_prefix='/admin')
app.register_blueprint(order_bp, url_prefix='/order')
app.register_blueprint(home_bp, url_prefix='/home')
# url_prefix 路由前缀,和django的总路由有点相似,
# 蓝图下面的路由,会自动拼接的路由前缀的后面
------------------------------------------------------
------------------------------------------------------
# admin的app下的__init__
from flask import Blueprint
# 蓝图使用自己的静态文件与自己的模板
admin_bp = Blueprint('admin', __name__, static_folder='static', template_folder='templates')
# 导入被该蓝图对象管理的views.py文件,这样才能完成路由的注册
from src.admin import views
------------------------------------------------------
# admin的app下的views.py
from flask import render_template, request, session, redirect
from . import admin_bp
from src.wrappers import auth
@admin_bp.before_request
def before1():
print('admin请求来了会执行')
@admin_bp.after_request
def after1(response):
print('admin请求走了会执行')
return response
@admin_bp.route('/home', defaults={'name': 'teng'})
def home(name):
return render_template('admin_home.html', name=name)
@admin_bp.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
else:
name = request.form.get('username')
password = request.form.get('password')
session['name'] = name
print(session) # <SecureCookieSession {'name': 'teng'}>
return redirect('/admin/home')
# 搞了一个登录的装饰器
@admin_bp.route('/index')
@auth
def index():
return '哈哈哈'
------------------------------------------------------
# src目录下面的wrappers.py 文件里面的登录装饰器函数
from flask import session,redirect
def auth(func):
def inner(*args, **kwargs):
# print(session.get('name'))
if session.get('name'):
res = func(*args, **kwargs)
return res
else:
return redirect('/admin/login')
return inner
------------------------------------------------------
# settings.py 配置文件
class BASE(object):
DEBUG = False
class ProductionConfig(BASE):
SECRET_KEY = 'jckopjoeko8415561'
HAHA = 'heihei'
class DevelopmentConfig(BASE):
SECRET_KEY = 'jckopjoeko8415561'
HAHA = 'heihei'
DEBUG = True
------------------------------------------------------
.
.
.
.
.
.
.
.
.
.
.
.
.
2 g 对象
# g 对象 是什么?
global的缩写,在python中是个关键字,不能以关键字作为变量名,干脆用了g作为变量名了
# g对象的作用:可以在整个请求的全局,可以放值,可以取值
全局变量,在任意位置导入使用即可
# 它为什么不学django使用request作为上下文?
因为使用request,可能会造成request数据的污染
假设你不小心改了request对象的原有的属性,你又不知道,就会导致后续用的数据都会出问题
假设你在视图函数里面,request.method = 'xxx'
就会把request.method原来对应的请求方式数据比如说'get' 覆盖成'xxx'了
所以官方建议使用g, g是空的,放入之后,只在当次请求中,全局有效!!!
下次请求,g里面就又空了
# 以后想在当次请求中,放入一些数据,后面使用,就可以使用 g对象
---------------------------------------------------
from flask import Flask, g, request
app = Flask(__name__)
app.debug = True
@app.before_request
def before():
if 'home' in request.path:
g.xx = 'xx'
def add(a, b):
print('-----', g.name)
return a + b
@app.route('/')
def index():
name = request.args.get('name')
g.name = name
res = add(1, 2)
# print(g.xx) # 执行该代码会报错,
# 因为只在当次请求中,全局有效,所以当请求路由有home才会触发请求扩展里面g.xx = 'xx'
# 直接访问根路径,根本就没走g.xx = 'xx' ,g对象里面只有路由问号后面带的数据在里面
return 'index'
@app.route('/home')
def home():
print(g.xx)
return 'index'
if __name__ == '__main__':
app.run()
# g对象 和request对象一样,只在当次请求中有效
# 但是我们一般不会手动把数据,放到request对象里面去,可能造成request原有数据的污染
# 所以一般我们在一个请求里,会把后面要用到的数据,在前面先放到g对象里面去,
# 这样在该次请求后面的任意地方,都可以从g对象里面取出,放进去的数据!!!
---------------------------------------------------
# g和session有什么区别?
g 是只针对于当次请求,当次请求放进去,不拿,下次请求g也变成空的了
session针对于多次请求,当次请求放进去,下次请求还能拿到放进去的数据
---------------------------------------------------
.
.
.
.
.
.
.
.
.
.
.
.
3 dbutils模块创建 数据库连接池
把cursor对象定义在全局,多个线程同时处理多个请求时,用的同一个cursor对象,
可能会发送数据错乱的情况
---------------------------------------------------------
# flask 操作mysql
使用pymysql ,在视图函数中,创建pymysql的连接,查数据,查完,返回给前端
# 有什么问题? 来一个请求,创建一个连接,请求结束,连接关闭 (djanog就是这么做的,没有大问题)
把连接对象与cursor对象,做成全局的,在视图函数中,使用全局的连接,查询,返回给前端
from flask import Flask, jsonify
import pymysql
app = Flask(__name__)
app.config['DEBUG'] = True
conn = pymysql.connect(user='root', password='222', host='127.0.0.1', database='20230113bbs', port=3306)
cursor = conn.cursor(pymysql.cursors.DictCursor) # 执行sql查询的结果是列表套字典的形式
# cursor = conn.cursor() # 执行sql查询的结果是元祖套元祖的形式
# desc 在sql里面是保留字有特殊含义,所以如果表里面字段名也是desc,一定要用反引号取消特殊含义
@app.route('/article')
def article():
cursor.execute('select id,name,`desc` from app01_article where id<3')
res = cursor.fetchall()
print(res)
return jsonify(res)
if __name__ == '__main__':
app.run()
# 有什么问题?
会出现数据错乱,一个进程下多个线程同时处理不同的请求时,
用的是同一个全局的cursor对象,就会出现数据错乱的情况 详见上图
这种方案很危险, 还不如最开始的 一个请求,创建一个连接,请求结束,连接关闭,这种做法了
---------------------------------------------------------
---------------------------------------------------------
---------------------------------------------------------
# 如何解决上面的两个问题
创建一个全局的数据库连接池
每次进入视图函数,从池中取一个连接使用,使用完放回到池中
这样只要控制池的大小,就能控制mysql连接数
---------------------------------------------------------
# 使用第三方数据库连接池,使用步骤:
-1 安装 pip install dbutils
-2 使用:实例化得到一个池对象
-3 在视图函数中导入使用
conn = pool.connection()
cursor = conn.cursor(pymysql.cursors.DictCursor)
cursor.execute('select id,title from app01_article limit 2;')
res = cursor.fetchall()
-----------------------------------------------
# 该代码要先启动起来,再进行压力测试
from flask import Flask, g, request, jsonify
import pymysql
app = Flask(__name__)
app.debug = True
from POOL import pool
import time
import random
# 带池的
@app.route('/article_pool')
def article_pool():
conn = pool_obj.connection()
cursor = conn.cursor(pymysql.cursors.DictCursor) # 执行sql查询的结果是列表套字典的形式
time.sleep(1)
cursor.execute('select id,name,`desc` from app01_article where id<3')
res = cursor.fetchall()
print(res)
return jsonify(res)
# 不带池的
@app.route('/article_wuchi')
def article_wuchi():
conn = pymysql.connect(user='root', password='222', host='127.0.0.1', database='20230113bbs', port=3306)
cursor = conn.cursor(pymysql.cursors.DictCursor) # 执行sql查询的结果是列表套字典的形式
time.sleep(1)
cursor.execute('select id,name,`desc` from app01_article where id<3')
res = cursor.fetchall()
print(res)
return jsonify(res)
if __name__ == '__main__':
app.run()
-----------------------------------------------
# 压力测试代码
from threading import Thread
import requests
def task():
res = requests.get('http://127.0.0.1:5000/article_pool')
print(1111)
def task1():
res = requests.get('http://127.0.0.1:5000/article_wuchi', verify=False)
print(2222)
if __name__ == '__main__':
for i in range(500):
t = Thread(target=task) # 分别尝试用多线程测试不带连接池与带连接池的效果
# t = Thread(target=task1)
t.start()
---------------------------------------------------------
# pool.py 创建池对象
from dbutils.pooled_db import PooledDB
import pymysql
# 创一个池对象,下面的这个配置,以后可以直接当做一个属性写到项目的settings文件里面的配置类里面去
pool_obj = PooledDB(
creator=pymysql, # 使用pymysql连接数据库
maxconnections=16, # 连接池允许的最大连接数,0与None表示不限制连接数,8核机设个16就差不多了
mincached=5, # 初始化时,连接池中至少创建的空闲连接,0表示不创建
maxcached=10, # 连接池中最多闲置的连接,0与None表示不限制,
maxshared=3, # 连接池最多共享的连接数量,该参数没有用,可以不写,因为pymysql的thredsafety为1
# 所以maxshared不管设多少,最终的结果都是0 !!,详见下面解析
blocking=True, # 连接池中如果没有可用的连接,是否阻塞等待,True表示等待
maxusage=50, # 一个连接最多被重复使用的次数,None表示无限制,
# 一般设个数字比较合理,不然可能会造成内存溢出的问题
# 一般结合场景,如果有大量短连接的情况下,可以适当的增大参数,
# 如果有大量的长连接情况下,适当的减少参数,避免过多的连接长时间占用资源
ping=0, # ping mysql服务端检查是否可用,0表示不检查,1表示要连接时测一下,2表示游标产生的时候测一下
host='127.0.0.1',
port=3306,
user='root',
password='222',
database='20230113bbs',
charset='utf8'
)
---------------------------------------------------------
## 效果是:
使用连接池 池的连接数明显小
不使用连接池 池连接数明显很大
Threads_connected 参数就是表示的池的连接数
mysql数据库单实例的情况下,最多的连接数就几千
---------------------------------------
# 查看数据库已建立的连接数 sql语句
show status like 'Threads%'
Threads_connected # 该参数表示数据库现在连接的连接数
# 当开多线程的时候朝/article_wuchi路由发送请求,
# 可以看到,在navcat上面执行show status like 'Threads%'
# Threads_connected参数可以看到连接数,最大的时候300多
# 但是开多线程的时候朝/article_pool路由发送请求,
# 可以看到,在navcat上面执行show status like 'Threads%'
# Threads_connected参数可以看到连接数,最大的时候20多点
.
.
.
.
.
.
.
.
.
.
python的pymysql是不支持多线程操作的!!!
class PooledDB:
if threadsafety > 1 and maxshared:
self._maxshared = maxshared
self._shared_cache = [] # the cache for shared connections
else:
self._maxshared = 0
----------------------------------------------------
.
.
.
.
使用pymysql注意事项!!!!
# 项目中使用 pymysql 最好这样用,把生成游标的代码放到with里面去
# 这样执行完,就自动关掉conn连接对象了
# 不然,项目运行过程中,会报一些连接有问题,超时啥的错!!!!
conn = pymysql.connect(user='root', password='222', host='127.0.0.1', database='20230113bbs', port=3306)
try:
with conn.cursor(pymysql.cursors.DictCursor) as cursor:
sql = "SELECT * FROM base_users WHERE username = %s"
cursor.execute(sql, (username,))
user = cursor.fetchone()
if user:
if user['password'] == md5_password:
session['username'] = username
return redirect(url_for('upload_file'))
else:
return '密码错误'
else:
return '用户不存在'
finally:
conn.close()
.
.
.
.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY