Flask
前言
淡看人间三千事,闲来轻笑俩三声
1.Flask
Flask 是一个使用 Python 编写的轻量级 Web 框架(微框架)。
它的核心特点:
- 小而精
- 核心简单
- 扩展能力强
- 自由度高
- 非常适合:
- API 开发
- 中小型项目
- 微服务
- 后端接口
- AI Web 服务
- Python Web 学习
官方:https://flask.palletsprojects.com/en/stable/?utm_source=chatgpt.com
Python 常见 Web 框架:
| 框架 | 特点 | 类型 |
|---|---|---|
| Django | 大而全 | 同步 |
| Flask | 小而精 | 同步 |
| FastAPI | 高性能 | 异步 |
| Tornado | 异步 IO | 异步 |
| Sanic | 类 Flask 异步 | 异步 |
| web.py | 极简 | 同步 |
其它功能需要第三方扩展:
| 功能 | Flask 扩展 |
|---|---|
| ORM | SQLAlchemy / Peewee |
| Admin | Flask-Admin |
| 缓存 | Flask-Cache |
| 登录认证 | Flask-Login |
| JWT | Flask-JWT-Extended |
| 数据校验 | Marshmallow |
微框架
真正含义:
- Flask 核心非常小
- 不强制项目结构
- 不强制数据库
- 不强制 ORM
- 不强制模板
- 不强制缓存方案
开发者拥有完全控制权。
即:Flask 只提供 Web 最核心能力,其它全部自由选择。
Flask 核心组成
| 组件 | 作用 |
|---|---|
| Werkzeug | WSGI 工具包 |
| Jinja | 模板引擎 |
| Click | 命令行工具 |
Flask 请求处理流程
浏览器
↓
WSGI服务器
↓
Werkzeug
↓
Flask
↓
视图函数
↓
Jinja2模板渲染
↓
Response
↓
浏览器
Flask 本质理解
Flask 本质上:
- 基于 WSGI 协议
- 基于 Werkzeug 封装
- 本质还是 Python Web Server
其核心是:
socket
HTTP协议
WSGI协议
2.Werkzeug(HTTP/WSGI核心)
Werkzeug
Werkzeug 是一个 WSGI Web 工具包
注意 Werkzeug:
- 不是 Web 框架
- 不是 Web 服务器
- 是工具库
作用:为 Web 框架提供底层能力。
WSGI
Web Server Gateway Interface
即:Python Web 网关接口规范。
作用:统一 Web 服务器、Python Web 框架 之间的通信标准。
为什么需要 WSGI?
没有 WSGI 每个框架都得适配不同服务器
有了 WSGI:
- 所有框架统一标准
- 所有服务器统一协议
即:
Django Flask FastAPI ↓ WSGI ↓ gunicorn/uWSGI
WSGI 常见实现
| 工具 | 作用 |
|---|---|
| wsgiref | Python内置WSGI服务器 |
| gunicorn | Linux生产WSGI服务器 |
| uWSGI | 高性能WSGI服务器 |
| Werkzeug | WSGI工具包 |
Werkzeug 功能
| 功能 | 说明 |
|---|---|
| Request | 请求对象 |
| Response | 响应对象 |
| Routing | 路由 |
| URL解析 | url处理 |
| HTTP工具 | HTTP处理 |
| Debugger | 调试器 |
所以:Flask 很多能力来自 Werkzeug。
Werkzeug 示例
from werkzeug.wrappers import Request, Response
@Request.application
def hello(request):
return Response('Hello World!')
if __name__ == '__main__':
from werkzeug.serving import run_simple
run_simple('localhost', 4000, hello)

Werkzeug 核心理解
本质:
Werkzeug
↓
封装WSGI
↓
提供Request/Response
↓
Flask继续封装
所以:
Flask 底层 ≈ Werkzeug
3.wsgiref(WSGI Web服务器)
wsgiref
- Python 标准库
- Python 官方 WSGI 实现
作用:快速启动一个 WSGI Web 服务。
wsgiref 本质
底层:
socket
HTTP解析
WSGI协议
它帮助开发者不用手写:
- TCP
- HTTP协议
- socket
即可写 Web 服务。
WSGI 应用标准格式
WSGI 应用必须满足:
def app(environ, start_response):
pass
参数:
| 参数 | 说明 |
|---|---|
| environ | 请求信息 |
| start_response | 设置响应 |
**environ **
本质:
dict
包含:
| key | 说明 |
|---|---|
| PATH_INFO | 请求路径 |
| REQUEST_METHOD | 请求方法 |
| QUERY_STRING | 查询参数 |
wsgiref 示例
import os
from wsgiref.simple_server import make_server
# 当前项目根目录
BASE_DIR = os.path.dirname(__file__)
# WSGI 应用函数
def mya(environ, start_response):
# 设置响应状态码和响应头
start_response(
'200 OK',
[('Content-Type', 'text/html;charset=utf-8')]
)
# 获取请求路径
# 例如:/index /login
path = environ.get('PATH_INFO')
# 首页
if path == '/index':
# 拼接 html 文件路径
file_path = os.path.join(
BASE_DIR,
'templates/old',
'index.html'
)
# 读取 html 文件
with open(file_path, 'rb') as f:
data = f.read()
# 登录页
elif path == '/login':
file_path = os.path.join(
BASE_DIR,
'templates/old',
'login.html'
)
with open(file_path, 'rb') as f:
data = f.read()
# 其他路径
else:
data = b'<h1>Hello, web!</h1>'
# WSGI 必须返回可迭代对象
return [data]
if __name__ == '__main__':
# 创建 web 服务
myserver = make_server('', 8011, mya)
print('监听8011')
# 启动服务
myserver.serve_forever()

Flask 和 wsgiref 关系
Flask 本质:
Flask
↓
Werkzeug
↓
WSGI
所以Flask 本质也是:
def app(environ,start_response)
的高级封装。
4.Jinja2(模板引擎)
Jinja2
Python 最流行模板引擎之一。
作用:
HTML + 数据
↓
渲染
↓
最终HTML
为什么需要模板引擎?
如果不用模板:
html = "<h1>" + name + "</h1>"会非常难维护。
模板引擎让 HTML、Python 数据分离。
JJinja2 语法
变量
{{ name }}
for循环
{% for item in users %}
{{ item }}
{% endfor %}
if判断
{% if age > 18 %}
成人
{% endif %}
示例
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template(
'old/index.html',
name='peng'
)
# <h1>{{ name }}</h1>

Jinja2 特点
| 特点 | 说明 |
|---|---|
| 快速 | 性能高 |
| 类Python语法 | 易学 |
| 自动转义 | 防XSS |
| 可扩展 | 支持过滤器 |
5.Click(CLI命令系统)
Click
Python 命令行工具库。
Flask 命令系统:
flask run
flask shell
flask routes
底层就是 Click。
Click 核心作用
- 命令行参数解析
- 自动帮助文档
- 命令嵌套
- CLI开发
Click 示例
# 导入 click 库
# click 是 Python 中用于快速构建命令行工具(CLI)的第三方库
import click
# 使用 @click.command() 把 hello 函数变成一个命令行程序
@click.command()
# 定义命令行参数 --count
@click.option(
'--count', # 命令行参数名
default=1, # 默认值为 1
help='Number of greetings.' # 参数说明
)
# 定义命令行参数 --name
@click.option(
'--name', # 命令行参数名
prompt='Your name', # 如果用户没有输入 --name,则自动提示输入
help='The person to greet.' # 参数说明
)
# 命令执行函数
# count:问候次数
# name:用户名
def hello(count, name):
# 循环 count 次
for x in range(count):
# 输出内容
# click.echo 类似于 print
# 推荐在 click 项目中使用 echo
click.echo(f"Hello {name}!")
# 当前文件直接运行时执行
if __name__ == '__main__':
# 启动命令行程序
hello()
需要输入命令
python .\04Click示例.py --count 3 --name peng
# 或者
.\.venv\Scripts\python.exe .\04Click示例.py --count 3 --name peng

Click 核心装饰器
| 装饰器 | 作用 |
|---|---|
| @click.command | 定义命令 |
| @click.option | 定义参数 |
| @click.argument | 定义位置参数 |
6.Flask创建和运行
6.1基本使用
安装 Flask
pip install flask

示例
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'hello flask'
if __name__ == '__main__':
app.run()

app.run() 本质
启动 Werkzeug 开发服务器
即:
from werkzeug.serving import run_simple
Flask 核心对象
作用:创建 Flask 应用对象。
app = Flask(__name__)
@app.route()
作用:路由映射
URL
↓
视图函数
视图函数
作用:处理请求。
def index():
return 'hello'
Flask 开发模式
开启:
- 自动重载
- 调试模式
- 错误页面
app.run(debug=True)
6.2.python-dotenv
python-dotenv用于读取 .env 配置文件并加载到环境变量
为什么需要 .env?
目的:配置分离,不能写死代码。
数据库密码 Redis地址 JWT密钥
安装 python-dotenv
pip install python-dotenv

示例
import os
from dotenv import load_dotenv
from dotenv import dotenv_values
## 1 加载配置文件
# 必须在根路径下新建一个 .env 的文件,并写入配置才能返回True,会把.env下的配置文件设置进环境变量
res=load_dotenv() # take environment variables from .env
print(res)
print(os.environ.get('DOMAIN'))
# You will probably want to add .env to your .gitignore, especially if it contains secrets like a password.
## 2 获取环境变量字典
config = dotenv_values(".env")
print(config)
print(config.get('DOMAIN'))
# .env
DOMAIN=www.baidu.com
SECRET_KEY=123456

6.3watchdog
watchdog 是一个 Python 库,用来监听操作系统文件系统变化事件(File System Events)。
它不是“扫描文件夹”,而是:基于操作系统底层通知机制的“事件驱动监听器”
watchdog 的核心能力
| 事件类型 | 说明 |
|---|---|
| created | 文件/目录创建 |
| modified | 文件内容修改 |
| deleted | 删除 |
| moved | 重命名 / 移动 |
watchdog 是一个基于操作系统文件事件通知机制的跨平台监听框架,用 Observer 管理线程,用 EventHandler 处理事件流,实现“文件变化驱动逻辑执行”。
安装
pip install watchdog

示例
# pip3 install watchdog
# 用于文件系统监听(文件新增/修改/删除/移动)
import sys # 获取命令行参数(监听路径)
import time # 用于主线程 sleep,保持程序运行
import logging # 用于打印事件日志
from watchdog.observers import Observer # 核心:文件系统监控调度器
from watchdog.events import LoggingEventHandler # 内置事件处理器:直接打印日志
if __name__ == "__main__":
# 配置日志输出格式
# level=logging.INFO:输出 INFO 及以上级别日志
# format:定义日志结构
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 获取要监听的目录路径
# sys.argv[1]:命令行第一个参数
# 如果没有传参,默认监听当前目录 "."
path = sys.argv[1] if len(sys.argv) > 1 else '.'
# 创建事件处理器
# LoggingEventHandler 是 watchdog 自带的处理器
# 功能:当文件发生变化时,自动打印日志(无需自己实现逻辑)
event_handler = LoggingEventHandler()
# 创建 Observer(核心调度器)
# Observer 负责:
# 1. 监听文件系统变化(底层依赖 OS API)
# 2. 将事件分发给 handler
observer = Observer()
# 注册监听任务
# path:监听目录
# recursive=True:递归监听子目录
observer.schedule(event_handler, path, recursive=True)
# 启动后台线程开始监听
observer.start()
try:
# 主线程保持运行,否则程序会直接退出
# watchdog 在后台线程工作
while True:
time.sleep(1)
except KeyboardInterrupt:
# Ctrl + C 时优雅退出
observer.stop()
# 等待 observer 线程完全结束
observer.join()
# ==========================
# mysqldump -u ...
# ==========================
# 这里是 MySQL 数据导出命令的开头(未写完整)
# mysqldump:数据库备份工具
# -u:指定用户名
手动创建a.txt,然后随便添加字符串,发现文件被监听

6.4虚拟环境
Python venv 是 Python 自带的一个模块,用来创建:“项目级隔离的 Python 运行环境”
它的本质不是“新装一个 Python”,而是: 在当前系统 Python 外面,复制一套“独立的依赖空间”
解决的核心问题
-
依赖冲突问题
-
生产环境不可控问题
-
Python版本冲突
虚拟环境的本质结构
创建 venv 后会生成:
project/
├── venv/
│ ├── Scripts/ (Windows)
│ ├── bin/ (Linux/mac)
│ ├── lib/
│ ├── pyvenv.cfg
├── app.py
核心机制
- 解释器隔离:venv 指向一个 Python 解释器副本
- pip 隔离:每个 venv 有自己的
site-packages/
创建虚拟环境
Mac/linux
# 创建虚拟环境
mkdir myproject
cd myproject
python3 -m venv .venv
# 激活虚拟环境
. .venv/bin/activate
Win
# 创建虚拟环境
mkdir myproject
cd myproject
py -3 -m venv .venv
# 激活虚拟环境
.venv\Scripts\activate
安装依赖
只装在当前 venv,不污染系统
pip install flask
导出依赖(生产必备)
pip freeze > requirements.txt
生产环境恢复
pip install -r requirements.txt
venv vs 其他方案
| 工具 | 说明 |
|---|---|
| venv | 官方标准(推荐) |
| virtualenv | 老工具(兼容旧Python) |
| conda | 数据科学生态 |
| poetry | 现代依赖管理(更高级) |
6.5Flask核心依赖
Flask 必装
│
├── Werkzeug → HTTP/WSGI核心
├── Jinja2 → 模板引擎
├── MarkupSafe → 安全转义
├── ItsDangerous → session/cookie签名
├── Click → CLI命令系统
└── Blinker → 事件信号系统(可选)
Flask选装
│
├── dotenv → 处理配置文件
└── watchdog → 监控文件变化
6.6示例
flask run 和 app.run 是两套启动方式,端口不共享配置
-
app.run(port=8888)→ 只对python app.py生效 -
flask run→ 默认 5000,必须用:flask run --port 8888
代码
from flask import Flask
app=Flask(__name__)
app.debug=True
# 注册路由
@app.route('/',methods=['GET'])
def index():
return 'hello world111'
#运行方式一:写下面代码,右键运行
if __name__ == '__main__':
app.run(port=8888)

7.Flask配置文件
7.1简介
Flask 配置文件
在 Flask 中,所有配置都保存在:
app.config
它本质是:
flask.config.Config
继承自 Python 的 dict,所以:
app.config['DEBUG'] = True
本质就是字典操作。
Flask 配置的本质
from flask import Flask
app = Flask(__name__)
print(type(app.config))
print(app.config)
# <class 'flask.config.Config'>

7.2配置项
| 配置项 | 作用 |
|---|---|
| DEBUG | 是否开启调试模式 |
| TESTING | 是否测试模式 |
| SECRET_KEY | Session/CSRF 加密密钥 |
| PERMANENT_SESSION_LIFETIME | session 有效期 |
| SERVER_NAME | 域名配置 |
| SESSION_COOKIE_NAME | cookie 名称 |
| SESSION_COOKIE_HTTPONLY | JS 是否可访问 cookie |
| SESSION_COOKIE_SECURE | 是否只允许 HTTPS |
| MAX_CONTENT_LENGTH | 上传文件大小限制 |
| JSON_AS_ASCII | JSON 是否转 ASCII |
| TEMPLATES_AUTO_RELOAD | 模板自动重载 |
| PREFERRED_URL_SCHEME | URL 协议 |
{
'DEBUG': False, # 是否开启 Debug 模式
'TESTING': False, # 是否开启测试模式
'PROPAGATE_EXCEPTIONS': None, # 是否向外抛出异常
'SECRET_KEY': None, # Flask Session 加密密钥
'SECRET_KEY_FALLBACKS': None, # 备用密钥列表
'PERMANENT_SESSION_LIFETIME': datetime.timedelta(days=31), # Session 有效期
'USE_X_SENDFILE': False, # 是否使用 X-Sendfile 优化文件发送
'TRUSTED_HOSTS': None, # 允许访问的 Host 列表
'SERVER_NAME': None, # 服务器域名和端口
'APPLICATION_ROOT': '/', # 应用根路径
'SESSION_COOKIE_NAME': 'session', # Session Cookie 名称
'SESSION_COOKIE_DOMAIN': None, # Session Cookie 域名
'SESSION_COOKIE_PATH': None, # Session Cookie 路径
'SESSION_COOKIE_HTTPONLY': True, # JS 是否禁止访问 Cookie
'SESSION_COOKIE_SECURE': False, # 是否仅 HTTPS 传输
'SESSION_COOKIE_PARTITIONED': False, # 是否启用分区 Cookie
'SESSION_COOKIE_SAMESITE': None, # SameSite 策略
'SESSION_REFRESH_EACH_REQUEST': True, # 每次请求是否刷新 Session
'MAX_CONTENT_LENGTH': None, # 请求体最大大小
'MAX_FORM_MEMORY_SIZE': 500000, # 表单内存最大值
'MAX_FORM_PARTS': 1000, # 表单字段最大数量
'SEND_FILE_MAX_AGE_DEFAULT': None, # 静态文件缓存时间
'TRAP_BAD_REQUEST_ERRORS': None, # 是否捕获 BadRequest 异常
'TRAP_HTTP_EXCEPTIONS': False, # 是否捕获 HTTP 异常
'EXPLAIN_TEMPLATE_LOADING': False, # 是否显示模板加载过程
'PREFERRED_URL_SCHEME': 'http', # URL 默认协议
'TEMPLATES_AUTO_RELOAD': None, # 模板是否自动重载
'MAX_COOKIE_SIZE': 4093, # Cookie 最大大小(字节)
'PROVIDE_AUTOMATIC_OPTIONS': True # 是否自动生成 OPTIONS 请求响应
}

7.3Flask配置方式
7.3.1直接修改app属性
方式1:直接修改 app 属性
from flask import Flask
app = Flask(__name__)
app.debug = True
app.secret_key = '123456'

7.3.2通过 app.config 修改
方式2:通过 app.config 修改(最常用)
from flask import Flask
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = '123456'
app.config['DB_HOST'] = '127.0.0.1'

7.3.3from_pyfile
方式3:from_pyfile(类似 Django)
第一步:创建 settings.py
DEBUG = True
SECRET_KEY = '123456'
DB_HOST = '127.0.0.1'
DB_PORT = 3306
DB_USER = 'root'
DB_PASSWORD = 'root'

第二步:加载配置
from flask import Flask
app = Flask(__name__)
app.config.from_pyfile('settings.py')

7.3.4from_object
方式4:from_object(大型项目最推荐)
第一步:配置类
class Config:
DEBUG = False
TESTING = False
DB_HOST = '127.0.0.1'
DB_PORT = 3306
class DevelopmentConfig(Config):
DEBUG = True
class ProductionConfig(Config):
DB_HOST = '192.168.1.100'
第二步:加载配置
app.config.from_object(DevelopmentConfig)
或者
app.config.from_object('settings.DevelopmentConfig')

7.3.5其它配置方式
方式5:其它配置方式
| 方式 | 示例 | 场景 |
|---|---|---|
| from_envvar | app.config.from_envvar() |
环境变量 |
| from_json | app.config.from_json() |
JSON 配置 |
| from_mapping | app.config.from_mapping() |
字典配置 |
| dotenv | .env 文件 |
现代项目 |
| 配置中心 | Nacos/Apollo | 微服务 |
7.3.6dotenv 配置
方式6:dotenv 配置
安装 python-dotenv
pip install python-dotenv

创建 .env 文件
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_DB=0
REDIS_PASSWORD=redis

加载
from dotenv import load_dotenv
load_dotenv()
print(os.environ.get('REDIS_HOST'))
print(os.environ.get('REDIS_PORT'))
print(os.environ.get('REDIS_DB'))
print(os.environ.get('REDIS_PASSWORD'))

8.路由
8.1Flask 路由本质
Flask 路由核心就是:
URL ---> 视图函数
例如:
@app.route('/index')
def index():
return 'ok'
表示:访问 /index 时,执行 index 函数
本质上 Flask 内部维护了一张:
{
'/index': index函数内存地址
}
的映射关系。
8.2Flask 路由注册方式
装饰器方式(最常用)
@app.route('/index')
def index():
return 'ok'
add_url_rule方式(本质)
def index():
return 'ok'
app.add_url_rule('/index', view_func=index)
实际上:@app.route('/index')最终内部也是调用:app.add_url_rule()
所以:route == add_url_rule 的语法糖
8.3app.route 常用方式
route
@app.route('/login', methods=['GET', 'POST'])
def login():
pass
get
Flask2 新增:
@app.get('/index')
def index():
pass
等价于:
@app.route('/index', methods=['GET'])
post
@app.post('/add')
def add():
pass
等价于:
@app.route('/add', methods=['POST'])
其它
app.put
app.delete
app.patch
8.4route 参数详解
函数
@app.route(
rule,
methods=None,
endpoint=None,
defaults=None,
strict_slashes=None,
redirect_to=None,
subdomain=None,
host=None
)
底层本质
app.add_url_rule()
route 参数
| 参数 | 类型 | 作用 |
|---|---|---|
| rule | str | 路由地址 |
| methods | list/tuple | 请求方式 |
| endpoint | str | 路由别名 |
| defaults | dict | 默认参数 |
| strict_slashes | bool | 是否严格匹配 / |
| redirect_to | str/function | 重定向 |
| subdomain | str | 子域名匹配 |
| host | str | 指定主机匹配 |
8.5路径转换器
基本语法
# app.add_url_rule('/index/<int:pk>')
@app.route('/路径/<转换器:参数名>')
例如:
@app.route('/user/<int:id>')
def user(id):
return str(id)
用于:
- 提取路径参数
- 限制参数类型
- 路由匹配校验
Flask 内置路径转换器
| 转换器 | 作用 | 示例 |
|---|---|---|
| string | 字符串(默认) | <name> |
| int | 整数 | <int:id> |
| float | 浮点数 | <float:price> |
| path | 包含 / 的字符串 |
<path:url> |
| uuid | UUID | <uuid:uid> |
| any | 枚举值限制 | <any(admin,user):role> |
Flask 默认转换器
DEFAULT_CONVERTERS = {
'default': UnicodeConverter,
'string': UnicodeConverter,
'any': AnyConverter,
'path': PathConverter,
'int': IntegerConverter,
'float': FloatConverter,
'uuid': UUIDConverter,
}
9.CBV(Class Based View)
9.1简介
Flask 默认主要是 FBV(Function Based View,函数视图):
@app.route('/index')
def index():
return 'index'
但 Flask 也支持 CBV(Class Based View,类视图)。
Flask 中类视图核心是:
from flask.views import View
from flask.views import MethodView
通常开发中:
View:基础类视图MethodView:最常用,支持 RESTful 风格
CBV:使用类处理请求。
优势:
- 代码复用
- 更适合 RESTful
- 不同请求方式分离
- 更容易继承
- 逻辑更清晰
9.2示例
from flask import Flask
from flask.views import MethodView
app = Flask(__name__)
class UserView(MethodView):
def get(self):
return '查询用户'
def post(self):
return '新增用户'
app.add_url_rule(
'/user',
view_func=UserView.as_view('user')
)
if __name__ == '__main__':
app.run()

9.3源码分析
UserView.as_view('user')
↓
返回view函数
↓
add_url_rule保存view函数
↓
请求来了
↓
执行view()
↓
self = UserView()
↓
self.dispatch_request()
↓
getattr(self,'get')
↓
self.get()
↓
返回响应

10.模版
Django DTL / Flask Jinja2 都是模板引擎。本质:字符串渲染器(template renderer)
流程都是:
后端数据 + HTML模板 → 模板引擎 → 渲染成HTML字符串
DTL 与 Jinja2 共性
-
都支持 if / for 控制结构
-
都支持 include / extends 模板继承
-
都支持过滤器(filter)
-
都支持 safe 关闭转义
-
都可以访问字典 / 对象 / 列表
Jinja2 比 DTL 多的能力
- 支持函数调用(可加括号传参)
- 支持更强表达式(类似 Python)
- 语法更灵活(接近编程语言)
示例
from flask import Flask, session, redirect, request, render_template, url_for
from functools import wraps
from flask.views import MethodView, View
app = Flask(__name__)
app.secret_key = 'adfafa'
app.debug = True
def add(a, b):
return a + b + 100
@app.route('/')
def index():
a = '<a href="http://www.baidu.com">百度一下</a>'
return render_template('moban.html', user={'name': 'peng', 'age': 19, 'hobby': '篮球'}, add=add, a=a)
if __name__ == '__main__':
app.run(port=8888)
前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div style="height: 300px;width: 100%;background-color: pink;display: flex;flex-direction:column;justify-content: center;align-items: center">
<p>姓名:{{user.name}}</p>
<p>年龄:{{user['age']}}</p>
<p>爱好:{{user.get('hobby')}}</p>
</div>
<div style="height: 300px;width: 100%;background-color:darkslategray;display: flex;flex-direction:column;justify-content: center;align-items: center">
直接使用add:{{add(3,4)}}
</div>
<hr>
{%include 'top.html'%}
<br>
{{a|safe}}
<br>
{{a}}
<hr>
{{add(1,2)}}
</body>
</html>

11.请求响应
11.1Request
request = 对当前线程/协程中 HTTP 请求的全量封装对象
它包含:
- URL
- 请求方法
- 请求头
- 请求参数
- 请求体
- Cookie
- 文件上传
- 客户端信息
request 生命周期
客户端发请求
↓
WSGI(server)
↓
Flask 创建 RequestContext
↓
push 到栈 (ctx.push)
↓
request 变成当前线程可访问对象
↓
视图函数中使用 request
↓
请求结束 ctx pop
核心属性
- request.method
- request.args
- request.form
- request.json / request.get_json()
- request.headers
- request.files
| 分类 | 属性 / 方法 | 作用 | 使用场景 | 示例 |
|---|---|---|---|---|
| 请求信息 | request.method |
请求方式 | 判断GET/POST | "GET" |
| 请求信息 | request.url |
完整URL | 调试/日志 | http://xx/user?id=1 |
| 请求信息 | request.path |
请求路径 | 路由分析 | /user |
| 请求信息 | request.endpoint |
路由别名 | url_for / 调试 | user_view |
| GET参数 | request.args |
URL参数 | GET请求 | ?id=1 |
| GET参数 | request.args.get() |
获取单个参数 | 安全取值 | id=1 |
| POST表单 | request.form |
表单数据 | form提交 | username=tom |
| JSON数据 | request.json |
JSON请求体 | API接口 | {name:"tom"} |
| JSON数据 | request.get_json() |
获取JSON(推荐) | 更安全 | request.get_json() |
| 原始数据 | request.data |
原始body(bytes) | 二进制/特殊数据 | b'xxx' |
| 请求头 | request.headers |
所有请求头 | token / UA | dict-like |
| 请求头 | request.headers.get() |
获取单个header | Authorization | Bearer xxx |
| Cookie | request.cookies |
cookie集合 | 登录态 | sessionid=xxx |
| 文件上传 | request.files |
上传文件 | 上传图片/文件 | avatar.png |
| 客户端 | request.remote_addr |
客户端IP | 安全/日志 | 192.168.x.x |
| 客户端 | request.user_agent |
浏览器信息 | 统计分析 | Chrome/Safari |
| JSON判断 | request.is_json |
是否JSON请求 | API校验 | True/False |
示例
from flask import Flask,session,redirect,request,render_template,url_for
from flask.sessions import SessionMixin
app=Flask(__name__)
app.secret_key='adfafa'
app.debug=True
from flask import Request
@app.route('/',methods=['GET',"POST"])
def index():
# 是一个代理对象
print(type(request)) # 看类型打印出:<class 'werkzeug.local.LocalProxy'>,但request本质是flask import Request
print(type(session)) # 看类型打印出:<class 'werkzeug.local.LocalProxy'>,但session本质是SessionMixin
# Request对象中得属性和方法--》类比Django的request对象
# request.method 提交的方法
print(request.method)
# request.args get请求提及的数据
print(request.args)
# request.form post请求提交的数据
print(request.form)
# request.values post和get提交的数据总和
print(request.values)
# request.cookies 客户端所带的cookie
print(request.cookies)
# request.headers 请求头
print(request.headers)
# request.path 不带域名,请求路径
print(request.path)
# request.full_path 不带域名,带参数的请求路径
print(request.full_path)
# request.url 带域名带参数的请求路径
print(request.url)
# request.base_url 带域名请求路径
print(request.base_url)
# request.url_root 域名
print(request.url_root)
# request.host 127.0.0.1:500
print(request.host)
# request.files
obj = request.files['myfile']
obj.save('./' + obj.filename)
return '请求对象'
if __name__ == '__main__':
app.run(port=8888)

11.2Response
Flask Response 类:
werkzeug.wrappers.response.Response
继承关系:
BaseResponse
↑
Response (Flask)
底层来自 Werkzeug(Flask 的 HTTP 工具库)
Response 的核心作用
| 功能 | 说明 |
|---|---|
| 响应内容 | body(HTML / JSON / 字符串 / 文件) |
| 状态码 | 200 / 404 / 500 等 |
| 响应头 | Content-Type / Set-Cookie 等 |
| cookie | session、登录信息 |
示例
from flask import Flask, jsonify, make_response
# 创建 Flask 应用实例
app = Flask(__name__)
# 注册路由:访问根路径 "/"
@app.route("/")
def index():
resp = "响应对象"
resp = make_response(jsonify(name="peng", age=18), 201)
resp.headers["X-Token"] = "abc123"
resp.set_cookie("login", "true")
return resp
# 程序入口:直接运行该文件时启动 Flask 服务
if __name__ == '__main__':
# 监听 8888 端口
app.run(port=8888)

12.偏函数
在 Python 中,偏函数(partial function)指的是:把一个函数的部分参数固定住,生成一个新的“参数更少”的函数
来自标准库:
from functools import partial
核心作用:提前绑定参数,简化函数调用
关键特点总结
| 特性 | 说明 |
|---|---|
| 来自模块 | functools |
| 本质 | 生成新函数 |
| 参数机制 | 预先绑定部分参数 |
| 调用方式 | 少传参数 |
| 是否改变原函数 | ❌ 不改变 |
示例
# partial 的本质:提前固定函数的部分参数,返回一个“参数更少的新函数”
from functools import partial
def add(a, b, c):
return a + b + c
# 场景1:固定前两个参数,第三个以后再传
res = partial(add, 2000, 2000)
print(res(6000)) # 10000
# 场景2:只固定一个参数
add_2000 = partial(add, 2000)
print(add_2000(3000, 6000)) # 11000

13.Flask-RESTful
Flask-RESTful 是 Flask 的一个扩展库,用于:在 Flask 基础上提供 RESTful API 开发的结构化封装
核心目标:
- 把 Flask 的“函数式路由” → 类视图资源(Resource)
- 提供统一的 请求解析、序列化、响应封装
- 提供基础的 REST 风格规范支持
安装
pip install flask-restful

示例
Flask CLI 依赖 Python import 机制,而模块名必须符合 Python 标识符规范(不能以数字开头,也不能包含 -)
from flask import Flask
from flask_restful import Resource, Api, reqparse
app = Flask(__name__)
api = Api(app)
# 模拟数据库
USERS = {
1: {"id": 1, "name": "Alice"},
2: {"id": 2, "name": "Bob"}
}
# 请求参数解析器
parser = reqparse.RequestParser()
parser.add_argument("name", type=str, required=True, help="name is required")
# Resource:用户列表 / 创建用户
class UserList(Resource):
def get(self):
"""获取所有用户"""
return {"users": list(USERS.values())}
def post(self):
"""创建用户"""
args = parser.parse_args()
new_id = max(USERS.keys()) + 1
user = {
"id": new_id,
"name": args["name"]
}
USERS[new_id] = user
return user, 201
# Resource:单个用户
class User(Resource):
def get(self, user_id):
"""获取单个用户"""
user = USERS.get(user_id)
if not user:
return {"msg": "User not found"}, 404
return user
def put(self, user_id):
"""更新用户"""
args = parser.parse_args()
if user_id not in USERS:
return {"msg": "User not found"}, 404
USERS[user_id]["name"] = args["name"]
return USERS[user_id]
def delete(self, user_id):
"""删除用户"""
if user_id not in USERS:
return {"msg": "User not found"}, 404
del USERS[user_id]
return {"msg": "deleted"}
# 注册路由
api.add_resource(UserList, "/users")
api.add_resource(User, "/users/<int:user_id>")
if __name__ == "__main__":
app.run(debug=True)

14.Session
Flask 默认 session = 签名 Cookie Session(客户端存储)
核心点:
- 数据不在服务器
- 存在浏览器 Cookie 中
- 但内容是 加密签名后的 JSON
- 防篡改,不防查看(默认)
Flask 中 session 的默认实现类:
SecureCookieSessionInterface
它负责两件事:
| 方法 | 生命周期 | 类比 Django |
|---|---|---|
| open_session | 请求进入 | process_request |
| save_session | 响应返回 | process_response |
示例
from flask import Flask, session, request
app = Flask(__name__)
# session加密密钥
app.secret_key = "123456"
@app.route("/login")
def login():
"""
模拟登录
"""
# 登录成功后,把用户信息放入session
session["user"] = "peng"
return "登录成功"
@app.route("/index")
def index():
"""
判断用户是否登录
"""
user = session.get("user")
if user:
return f"当前登录用户:{user}"
return "未登录"
@app.route("/logout")
def logout():
"""
退出登录
"""
session.clear()
return "退出成功"
if __name__ == '__main__':
app.run(debug=True)

Flask 默认 session 基于 SecureCookieSessionInterface 实现,通过 itsdangerous 对 session 数据进行序列化和 HMAC 签名,并存储到浏览器 Cookie 中
- 请求时通过 open_session 读取并验证 session
- 响应时通过 save_session 重新签名并写回 Cookie。

15.请求扩展
Flask 中的“请求扩展”本质上就是在请求处理的不同阶段,插入自定义逻辑。作用类似于 Django 的中间件。
Flask 请求扩展 vs 中间件
| 对比 | Flask 请求扩展 | Flask WSGI 中间件 |
|---|---|---|
| 使用频率 | 常用 | 较少 |
| 使用方式 | 装饰器 | 包装 app |
| 操作阶段 | 请求生命周期 | WSGI 层 |
| 开发难度 | 简单 | 偏复杂 |
| 常见用途 | 登录校验、日志、跨域、异常处理 | 请求转发、代理、底层处理 |
Flask 常用请求扩展
| 扩展 | 触发时机 | 是否必须返回值 | 常见用途 |
|---|---|---|---|
before_request |
请求进入视图前 | 不必须 | 登录校验、权限认证 |
after_request |
视图执行完成后 | 必须返回 response | 修改响应头、跨域 |
teardown_request |
请求结束时 | 不需要 | 释放资源、记录日志 |
errorhandler |
出现指定错误时 | 需要 | 全局异常处理 |
示例
None是teardown方法的exc输出
from flask import Flask, request, jsonify
app = Flask(__name__)
# 请求前执行
@app.before_request
def before():
print("before")
request.name = "peng"
# 请求后执行
@app.after_request
def after(response):
print("after")
response.headers["X-Token"] = "123"
return response
# 请求结束执行
@app.teardown_request
def teardown(exc):
print("teardown")
print(exc)
# 全局异常
@app.errorhandler(Exception)
def error(e):
return jsonify({
"code": 500,
"msg": str(e)
})
@app.route("/")
def index():
print(request.name)
return "ok"
if __name__ == '__main__':
app.run(debug=True)

16.CBV+装饰器
请求流程
请求来了
↓
路由匹配
↓
as_view生成的view函数
↓
装饰器
↓
dispatch_request
↓
get/post
↓
返回响应
示例
from flask import Flask
from flask.views import MethodView
from flask import session, redirect, request
app = Flask(__name__)
# session 加密秘钥
app.secret_key = "123456"
# 登录认证装饰器
def login_auth(func):
def inner(*args, **kwargs):
# 判断是否登录
if session.get("username"):
print("用户已登录")
res = func(*args, **kwargs)
return res
else:
print("用户未登录")
return redirect("/login")
return inner
# 登录页面
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "GET":
return """
<form method="post">
用户名:<input type="text" name="username">
<input type="submit">
</form>
"""
# 登录成功
username = request.form.get("username")
# 保存 session
session["username"] = username
return "登录成功"
# 用户管理 CBV
class UserView(MethodView):
# 给整个类加装饰器
decorators = [login_auth]
def get(self):
return "用户列表"
def post(self):
return "新增用户"
# 订单管理 CBV
class OrderView(MethodView):
# decorators = [login_auth]
@login_auth
def get(self):
return "订单列表"
# 注册路由
app.add_url_rule(
"/user",
view_func=UserView.as_view("user")
)
app.add_url_rule(
"/order",
view_func=OrderView.as_view("order")
)
if __name__ == '__main__':
app.run(debug=True)

17.闪现(Flask flash)
17.1Flask flash
Flask flash 的本质: 基于 session 的“单次消费队列”
也就是说:
- 数据存入 session
- 只在下一次请求有效
- 一旦
get_flashed_messages()读取 → 自动清空
生命周期流程
POST /login
↓
flash("用户名或密码错误")
↓
redirect("/login")
↓(新请求)
GET /login
↓
get_flashed_messages()
↓
数据被取出 + session清空
↓
页面展示错误信息
Flask flash 特点
| 特性 | 说明 |
|---|---|
| 依赖 | session |
| 生命周期 | 一次请求后消失 |
| 存储结构 | list[tuple(category, msg)] |
| 是否持久 | 不持久 |
| 使用场景 | 表单错误提示 / 登录提示 |
示例
02闪现.py
第一次发送登录验证,使用flash存储消息,服务器重定向到/login(这时已经算第二次请求了),前端显示错误信息
from flask import Flask, render_template, request, redirect, url_for, flash
app = Flask(__name__)
# flash 必须依赖 session,所以必须配置 secret_key
app.secret_key = "dev-secret-key"
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form.get("username")
password = request.form.get("password")
# 模拟校验逻辑
if username == "admin" and password == "123456":
return redirect(url_for("index"))
else:
# ❗关键:写入 flash
flash("用户名或密码错误", category="error")
return redirect(url_for("login"))
# GET 请求:只负责渲染页面
return render_template("login.html")
@app.route("/")
def index():
return "登录成功,进入首页"
if __name__ == "__main__":
app.run(debug=True)
login.html
<!DOCTYPE html>
<html>
<head>
<title>登录</title>
</head>
<body>
<h2>用户登录</h2>
<!-- ⭐ flash消息展示区 -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div style="color: red;">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="post">
<input type="text" name="username" placeholder="用户名" value="peng">
<br>
<input type="password" name="password" placeholder="密码" value="123456">
<br>
<button type="submit">登录</button>
</form>
</body>
</html>

17.2django消息框架
################ 1 基础配置############
INSTALLED_APPS = [
...
'django.contrib.messages',
...
]
# 在django setting.py 取消注释的message app
MIDDLEWARE = [
...
'django.contrib.messages.middleware.MessageMiddleware',
...
]
# 在django setting.py 取消注释的message 的中间件
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
...
'django.contrib.messages.context_processors.messages',
],
}
}
]
################ 2 设置存放位置############
MESSAGE_STORAGE = "django.contrib.messages.storage.session.SessionStorage"
################ 3 放入值############
#添加message
from django.contrib import messages
def concel_order(request):
messages.add_message(request, messages.SUCCESS, "删除成功1")
messages.add_message(request, messages.SUCCESS, "删除成功2")
return redirect("/order/control/")
################ 4 视图函数中取############
# 在视图函数中添加messages模块
# 再通过messages.add_message导入提示信息
# 在视图函数中import get_messages模块获取添加的提示信息
def control_order(request):
if request.method == "GET":
from django.contrib.messages.api import get_messages
m1 = get_messages(request)
print(m1)
################ 5 模板中取############
# 在html模板中添加for循环拿到message
<div>
{% for obj in messages %}
<ul>{{ obj.message }}</ul>
{% endfor %}
</div>
18.g对象
Flask 中的 g 是一个请求上下文全局变量(global namespace per request)。
它的全称不是 global,而是 Flask 内部定义的:flask.g = request-level global object
核心特点:
- 每个请求独立
- 请求结束自动销毁
- 线程/协程隔离(基于 context local)
g / request / session
| 对象 | 作用 | 是否可写 | 生命周期 | 推荐用途 |
|---|---|---|---|---|
| request | 请求数据封装 | 基本只读 | 当前请求 | 获取参数、headers |
| g | 请求共享存储 | 可写 | 当前请求 | 共享中间变量 |
| session | 会话级存储 | 可写 | 多请求 | 登录状态 |
g 的生命周期
请求进入
↓
创建 request context
↓
g 被创建(空对象)
↓
视图函数执行(可写 g)
↓
请求结束
↓
g 自动销毁
所以:g 只能用于“当前请求内的临时数据缓存”
g 和 Django 对比
| 框架 | 请求共享机制 |
|---|---|
| Flask | g + context local |
| Django | request 对象 + middleware |
Flask 的 g = 当前请求生命周期内的“临时共享变量容器”,用于解决函数链传参问题。
示例
from flask import Flask, g, request, jsonify
app = Flask(__name__)
# 模拟数据库
USERS = {
1: {"id": 1, "name": "peng"},
2: {"id": 2, "name": "tom"}
}
# 1. 模拟 token 解析
def decode_token(token):
# 实际项目中:JWT / OAuth
if token == "token_1":
return 1
elif token == "token_2":
return 2
return None
# 2. 请求前钩子:统一加载用户
@app.before_request
def load_user():
token = request.headers.get("token")
user_id = decode_token(token)
if user_id:
g.user = USERS.get(user_id)
else:
g.user = None
# 3. 业务函数 A
def check_permission():
if not g.user:
return False
return True
# 4. 业务函数 B
def get_user_info():
return g.user
# 5. 视图函数
@app.route("/profile")
def profile():
if not check_permission():
return jsonify({"msg": "未登录"}), 401
user = get_user_info()
return jsonify({
"msg": "success",
"data": user
})
if __name__ == "__main__":
app.run(debug=True)

在请求进入时解析 token,得到用户信息,存入 g,后续业务直接使用,实现“登录态贯穿整个请求”。
所以请求必须带上token

19.蓝图
Flask 的 Blueprint(蓝图) 本质是:用来“模块化拆分路由系统”的机制(类似 Django 的 app)
蓝图 = 把一个大 Flask 项目拆成多个“小路由模块”
比如:
- 用户模块(user)
- 订单模块(order)
- 商品模块(product)
可以把蓝图理解成:
Flask App
↓
项目越来越大
↓
按功能拆分
↓
home模块
product模块
user模块
↓
每个模块对应一个Blueprint
19.1不使用蓝图
目录

__init__.py
from flask import Flask
app = Flask(__name__)
from . import views

views.py
from . import app
from .models import User
@app.route('/')
def index(): # put application's code here
return 'index'

app.py
from src import app
if __name__ == '__main__':
app.run()

启动项目

19.2蓝图小项目
目录

__init__.py
from flask import Flask
app = Flask(__name__, template_folder="../templates", static_folder="../static")
app.config.from_pyfile('settings.py')
from .views import main_blue
app.register_blueprint(main_blue, url_prefix="/api/v1/main")
# app.register_blueprint(main_bl,url_prefix="/api/v1/user")

views.py
from flask import Blueprint, render_template
main_blue = Blueprint('main', __name__)
@main_blue.before_request
def before():
print("before...")
@main_blue.get('/')
def index():
return "首页"
@main_blue.get('/home')
def home():
return render_template('home.html', name="peng")

app.py

启动项目
http://127.0.0.1:5000/api/v1/main/home

19.3蓝图大项目
目录结构

每个项目的目录的基本结构都是一致的:
src/apps/home/static:静态文件目录src/apps/home/templates:模版文件目录src/apps/home/__init__.py:应用初始化src/apps/home/models.py:数据模型src/apps/home/serialzier.py:序列化与反序列化src/apps/home/views.py:视图函数
home模版
src/apps/home/__init__.py
from flask import Blueprint
home_blue = Blueprint('home', __name__, template_folder='templates', static_folder='static')
from . import views

src/apps/home/views.py
from flask import render_template
from . import home_blue
# 请求扩展
@home_blue.before_request
def before():
print('home before...')
@home_blue.get('/')
def index():
return render_template("home.html")

product模版
src/apps/product/__init__.py
from flask import Blueprint
product_blue = Blueprint('product', __name__, template_folder='templates', static_folder='static')
from . import views

src/apps/product/views.py
from flask import render_template
from . import product_blue
# 请求扩展
@product_blue.before_request
def before():
print('product before...')
@product_blue.get('/')
def index():
return render_template("product.html")

user模版
__init__.py
from flask import Blueprint
user_blue = Blueprint('user', __name__, template_folder='templates', static_folder='static')
from . import views
views.py
from flask import render_template
from . import user_blue
# 请求扩展
@user_blue.before_request
def before():
print('user before...')
@user_blue.get('/')
def index():
return render_template("user.html")
src/__init__.py
创建Flask应用,加载配置,注册各模块蓝图,并为所有请求添加全局前置处理逻辑。
from flask import Flask
app = Flask(__name__)
app.config.from_pyfile('settings.py')
from src.apps.home import home_blue
from src.apps.product import product_blue
from src.apps.user import user_blue
app.register_blueprint(home_blue, url_prefix='/api/v1/home')
app.register_blueprint(product_blue, url_prefix='/api/v1/product')
app.register_blueprint(user_blue, url_prefix='/api/v1/user')
@app.before_request
def before():
print("总路由...")

app.py
启动项目
from src import app
if __name__ == '__main__':
app.run()

测试
http://127.0.0.1:5000/api/v1/home/
http://127.0.0.1:5000/api/v1/user/
http://127.0.0.1:5000/api/v1/product/

19.4总结
创建蓝图对象
from flask import Blueprint
# 蓝图名,当前模块名
order_blue = Blueprint('order', __name__)
在蓝图中编写路由
@order_blue.route('/order')
def order():
return 'order-order-order'
给蓝图添加请求扩展
@order_blue.before_request
def before():
print('来了')
@order_blue.after_request
def after(response):
print('走了')
return response
@order_blue.errorhandler(404)
def error(e):
return '蓝图404'
在 app 中注册蓝图
from order import order_blue
app.register_blueprint(
order_blue,
url_prefix='/api/v1'
)
典型项目结构
project/
│
├── app.py
│
├── apps/
│ ├── user/
│ │ ├── __init__.py
│ │ └── views.py
│ │
│ ├── order/
│ │ ├── __init__.py
│ │ └── views.py
│ │
│ └── product/
│ ├── __init__.py
│ └── views.py
20.flask-session
Flask 默认的 Session 是客户端 Session。
数据保存在浏览器 Cookie 中,只不过经过了签名(不是加密)。
安装flask-session
pip install flask-session

如果没有redis需要安装
pip install redis

20.1方式一:手动(RedisSessionInterface)
from flask import Flask, session, request
from flask_session.redis import RedisSessionInterface
import redis
# 连接Redis
conn = redis.Redis(
host='192.168.188.180',
port=6379,
password='redis',
# decode_responses=True
)
# 替换Session实现
app.session_interface = RedisSessionInterface(
app,
client=conn,
key_prefix='session:',
use_signer=True,
permanent=True,
sid_length=32
)
@app.route('/login')
def login():
username = request.args.get('username')
# 登录成功
session['user'] = username
session['age'] = 18
return f'{username} 登录成功'
@app.route('/index')
def index():
user = session.get('user')
if not user:
return '请先登录'
return f'当前登录用户:{user}'
@app.route('/logout')
def logout():
session.clear()
return '退出成功'
if __name__ == '__main__':
app.run(port=5000)

20.2方式二:配置(app.config)
from flask_session import Session
from redis import Redis
app = Flask(__name__)
# 必须配置
app.secret_key = "peng"
# Session存Redis
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = Redis(
host='192.168.188.180',
port=6379,
password='redis'
)
Session(app)

20.3源码分析
open_session:
1. 从Cookie取session值
2. 验证签名
3. 反序列化
4. 创建Session对象
save_session:
1. Session转字典
2. 序列化
3. 使用secret_key签名
4. 写入Cookie
特点:
Session数据直接存浏览器Cookie
服务端不存储Session
Cookie:
session=Session数据+签名
默认过期时间:
PERMANENT_SESSION_LIFETIME=31天

21.WTForms
WTForms 是 Flask 中最常用的表单验证库,主要解决两个问题:
-
1.表单数据接收
-
2.表单数据校验
WTForms 是 Flask 中的表单验证框架,通过「Form + Field + Validator」的方式统一完成请求参数接收、数据校验和错误管理;Flask-WTF 则是在 WTForms 基础上增加了 Flask 集成和 CSRF 防护功能。
核心功能
| 功能 | 说明 |
|---|---|
| 字段(Field) | 接收数据 |
| 验证器(Validator) | 验证数据 |
| Form | 表单类 |
| FlaskForm | Flask集成版Form |
| validate() | 执行验证 |
| validate_on_submit() | POST并验证 |
| errors | 错误信息 |
| data | 验证后的数据 |
| CSRF | 防止跨站请求伪造 |
安装
pip install wtforms

示例
06wtforms.py
from flask import Flask, render_template, request
from wtforms import Form
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets
# 创建Flask应用
app = Flask(__name__, template_folder='templates')
# 开启调试模式
app.debug = True
# ==========================================
# 登录表单类
# 作用:
# 1. 定义表单字段
# 2. 定义字段验证规则
# 3. 定义页面渲染方式
# ==========================================
class LoginForm(Form):
# 用户名输入框
name = simple.StringField(
label='用户名', # 页面显示的标签
validators=[
# 不能为空
validators.DataRequired(
message='用户名不能为空'
),
# 长度限制
validators.Length(
min=6,
max=18,
message='用户名长度必须大于%(min)d且小于%(max)d'
)
],
# 指定生成<input type="text">
widget=widgets.TextInput(),
# 给HTML标签添加属性
render_kw={
'class': 'form-control'
},
default='peng'
)
# 密码输入框
pwd = simple.PasswordField(
label='密码',
validators=[
# 不能为空
validators.DataRequired(
message='密码不能为空'
),
# 最少8位
validators.Length(
min=8,
message='密码长度必须大于%(min)d'
),
# 正则验证密码强度
validators.Regexp(
regex=r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{8,}$",
message='密码至少8位,且包含大写字母、小写字母、数字和特殊字符'
)
],
# 指定生成<input type="password">
widget=widgets.PasswordInput(hide_value=False),
render_kw={
'class': 'form-control',
"value": "peng123456"
}
)
# ==========================================
# 登录页面
# GET:
# 显示页面
#
# POST:
# 接收用户提交的数据
# 执行WTForms验证
# ==========================================
@app.route('/login', methods=['GET', 'POST'])
def login():
# 第一次访问页面
if request.method == 'GET':
# 创建空表单对象
form = LoginForm(data={
"name": "peng",
"pwd": "peng123456"
})
return render_template(
'login_new.html',
form=form
)
# 用户提交表单
else:
# request.form中的数据交给WTForms处理
form = LoginForm(
formdata=request.form
)
# 执行所有验证器
if form.validate():
print(
'用户提交数据通过格式验证,提交的值为:',
form.data
)
"""
form.data格式:
{
'name':'peng123',
'pwd':'Abc123@#'
}
"""
else:
print('验证失败')
print(form.errors)
"""
form.errors格式:
{
'name': [
'用户名不能为空'
],
'pwd': [
'密码长度必须大于8'
]
}
"""
return render_template(
'login_new.html',
form=form
)
if __name__ == '__main__':
app.run()
login_new.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登录</h1>
<form method="post">
<p>{{form.name.label}} {{form.name}} {{form.name.errors[0] }}</p>
<p>{{form.pwd.label}} {{form.pwd(value="peng123456")}} <span style="color: red">{{form.pwd.errors[0] }}</span></p>
<input type="submit" value="提交">
</form>
</body>
</html>

22.mysql
数据库三大范式:
- 字段原子化
- 非主键完全依赖主键
- 非主键只依赖主键
安装pymysql
pip install pymysql

22.1全局连接(错误)
from flask import Flask, render_template, request, redirect
import pymysql
app = Flask(__name__, template_folder='templates')
app.debug = True
# 多个请求共用:conn cursor
# 请求A执行SQL
# 请求B执行SQL
# 请求A获取结果
# 结果被覆盖。
# =========================
# 创建 MySQL 连接对象
# =========================
conn = pymysql.connect(
host='192.168.188.180', # MySQL服务器地址
port=3306, # MySQL端口
user='root', # 用户名
password='root', # 密码
database='rbac_manager' # 数据库名称
)
# =========================
# 创建游标对象
# DictCursor:查询结果返回字典格式
# 默认返回元组格式
# =========================
cursor = conn.cursor(pymysql.cursors.DictCursor)
@app.route('/dept')
def dept():
"""
查询部门表前3条数据
访问:
http://127.0.0.1:5000/dept
"""
# 执行SQL语句
cursor.execute("select * from lqz_dept limit 3")
# 获取所有查询结果
data = cursor.fetchall()
# 转成字符串返回给浏览器
return str(data)
@app.route('/user')
def user():
"""
查询用户表前3条数据
访问:
http://127.0.0.1:5000/user
"""
# 执行SQL语句
cursor.execute("select * from lqz_user limit 3")
# 获取所有查询结果
data = cursor.fetchall()
# 转成字符串返回给浏览器
return str(data)
if __name__ == '__main__':
app.run(debug=True)

22.2每个请求独立连接
from flask import Flask, render_template, request, redirect
import pymysql
app = Flask(__name__, template_folder='templates')
app.debug = True
@app.route('/menu')
def menu():
conn = pymysql.connect(
host='192.168.188.180', # MySQL服务器地址
port=3306, # MySQL端口
user='root', # 用户名
password='root', # 密码
database='rbac_manager' # 数据库名称
)
cursor = conn.cursor(pymysql.cursors.DictCursor)
cursor.execute("select * from lqz_menu limit 3")
data = cursor.fetchall()
cursor.close()
conn.close()
return str(data)
@app.route('/role')
def role():
conn = pymysql.connect(
host='192.168.188.180', # MySQL服务器地址
port=3306, # MySQL端口
user='root', # 用户名
password='root', # 密码
database='rbac_manager' # 数据库名称
)
cursor = conn.cursor(pymysql.cursors.DictCursor)
cursor.execute("select * from lqz_role")
data = cursor.fetchall()
cursor.close()
conn.close()
return str(data)
if __name__ == '__main__':
app.run(debug=True)

22.3面试题
22.3.1脏读、不可重复读、幻读
脏读:读到其他事务未提交的数据。
不可重复读:同一事务中,多次读取同一条记录,读到了其他事务已提交的更新或删除数据,结果不一致。
幻读:同一事务中,多次查询同一范围数据,读到了其他事务已提交的新增或删除数据,结果集数量不一致。
22.3.2MySQL事务隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| Read Uncommitted(读未提交) | ❌ | ❌ | ❌ |
| Read Committed(读已提交) | ✅ | ❌ | ❌ |
| Repeatable Read(可重复读) | ✅ | ✅ | ❌* |
| Serializable(串行化) | ✅ | ✅ | ✅ |
MySQL 5.7 / 8.0 默认隔离级别:Repeatable Read(可重复读)
22.3.3qps、tps、并发量、pv、uv
| 指标 | 全称 | 含义 |
|---|---|---|
| QPS | Queries Per Second | 每秒查询请求数 |
| TPS | Transactions Per Second | 每秒事务处理数 |
| 并发量 | Concurrent Users/Requests | 同一时刻系统正在处理的请求数 |
| PV | Page View | 页面访问量 |
| UV | Unique Visitor | 独立访客数 |
22.3.4接口幂等性
幂等性(Idempotent):同一个请求执行一次和执行多次,对系统产生的结果是一样的,不会因为重复请求导致数据错误。
唯一索引
数据库增加唯一约束:
ALTER TABLE orders
ADD UNIQUE(order_no);
相同订单号只能插入一次。
Token机制
1. 请求前获取唯一Token
2. 提交时携带Token
3. 服务端校验并删除Token
4. 重复提交直接拒绝
Redis分布式锁
SETNX(key,value)
同一时间只允许一个请求执行。
适用于:抢购/秒杀/库存扣减
状态机控制
例如订单:
待支付 -> 已支付
如果已经是:
已支付
再次支付直接返回成功。
防重表(幂等记录表)
记录业务唯一号:
request_id
order_no
message_id
处理前先查询是否存在。
存在则不再执行。
22.3.5gil锁
GIL(Global Interpreter Lock,全局解释器锁) 是 Python 解释器(CPython)中的一种机制。
作用:同一时刻只允许一个线程执行 Python 字节码。
即使创建了多个线程,在执行 Python 代码时也需要先获取 GIL。
主要是为了保证:
- Python对象的线程安全
- 简化内存管理
- 提高解释器实现效率
它简化了内存管理并保证对象线程安全,但导致CPU密集型任务无法通过多线程充分利用多核CPU。
因此计算密集型任务通常使用多进程,IO密集型任务由于线程等待IO时会释放GIL,所以适合使用多线程。
22.3.6python垃圾回收机制
Python 主要采用 引用计数(Reference Counting),并结合 标记清除(Mark and Sweep) 和 分代回收(Generational GC) 来管理内存。
当对象引用计数为0时会立即释放内存。为了处理循环引用问题,Python又引入了标记清除算法。同时采用分代回收思想,将对象划分为0代、1代、2代,优先回收年轻对象,提高垃圾回收效率。
22.3.7多进程和多线程
计算密集型任务主要消耗CPU资源,由于Python存在GIL锁,多线程无法真正并行执行CPU计算,因此通常使用多进程。每个进程都有独立的GIL,可以充分利用多核CPU。
IO密集型任务大部分时间都在等待网络、磁盘或数据库响应,等待期间会释放GIL,因此多个线程可以并发执行。相比多进程,多线程创建和切换开销更小,所以IO密集型任务通常使用多线程。
22.4数据库连接池
数据库连接池是一种池化技术,用于复用数据库连接。程序启动时会预先创建一定数量的连接放入池中,请求访问数据库时直接从连接池获取连接,使用完后归还连接,而不是频繁创建和销毁连接。这样可以减少数据库连接开销,提高系统性能和并发能力。常见连接池有 HikariCP、Druid、C3P0,其中 Spring Boot 默认使用 HikariCP。对于 Python 项目,常用 DBUtils、SQLAlchemy Pool 等实现连接池。
安装dbutils
pip install dbutils

安装pymysql
pip install pymysql

22.4.1配置
pool.py
from dbutils.pooled_db import PooledDB
import pymysql
# 创建数据库连接池对象
POOL = PooledDB(
creator=pymysql, # 使用 pymysql 作为数据库驱动
maxconnections=6, # 连接池允许的最大连接数
# 0 或 None 表示不限制
mincached=2, # 初始化连接池时,创建的最小空闲连接数
maxcached=5, # 连接池中最多保留的空闲连接数
# 超过该数量的空闲连接会被关闭
maxshared=3, # 最大共享连接数
# 对 pymysql 基本无效,因为 pymysql 不支持共享连接
blocking=True, # 连接池耗尽时是否阻塞等待
# True:等待连接释放
# False:立即抛出异常
maxusage=None, # 单个连接最大复用次数
# None 表示无限次复用
setsession=[], # 获取连接后执行的 SQL
# 例如:
# ["SET NAMES utf8mb4"]
ping=0, # 检测数据库连接是否可用
# 0:从不检测
# 1:获取连接时检测
# 2:创建游标时检测
# 4:执行 SQL 时检测
# 7:每次都检测
# ========================
# MySQL 数据库连接参数
# ========================
host='192.168.188.180', # MySQL服务器IP
port=3306, # MySQL端口
user='root', # 用户名
password='root', # 密码
database='rbac_manager', # 数据库名称
charset='utf8mb4' # 字符编码,支持emoji
)

22.4.2使用
数据库连接都从连接池中获取
"""
数据库连接池本质上是提前创建并维护一批数据库连接,程序需要访问数据库时直接从池中获取连接,用完后归还,而不是每次都重新创建和销毁连接。
"""
import pymysql
from flask import Flask
from pool import POOL
app = Flask(__name__, template_folder="templates", static_folder="static")
app.debug = True
@app.route('/menu')
def menu():
# 从连接池获取连接
conn = POOL.connection()
# 创建游标
cursor = conn.cursor(pymysql.cursors.DictCursor)
cursor.execute("select * from lqz_menu limit 3")
data = cursor.fetchall()
cursor.close()
conn.close()
return str(data)
@app.route('/role')
def role():
# 从连接池获取连接
conn = POOL.connection()
# 创建游标
cursor = conn.cursor(pymysql.cursors.DictCursor)
cursor.execute("select * from lqz_role")
data = cursor.fetchall()
cursor.close()
conn.close()
return str(data)
if __name__ == '__main__':
app.run()

测试

22.5压力测试
22.5.1接口
一个接口使用数据库连接池连接,一个接口使用独立的数据库连接
"""
数据库连接池本质上是提前创建并维护一批数据库连接,程序需要访问数据库时直接从池中获取连接,用完后归还,而不是每次都重新创建和销毁连接。
"""
import pymysql
from flask import Flask,jsonify
import time
from pool import POOL
app = Flask(__name__, template_folder="templates", static_folder="static")
app.debug = True
@app.route('/menu')
def menu():
# 从连接池获取连接
conn = POOL.connection()
# 创建游标
cursor = conn.cursor(pymysql.cursors.DictCursor)
cursor.execute("select * from lqz_menu limit 3")
time.sleep(2)
data = cursor.fetchall()
print(data)
cursor.close()
conn.close()
return jsonify(data)
@app.route('/role')
def role():
conn = pymysql.connect(
host='192.168.188.180', # MySQL服务器地址
port=3306, # MySQL端口
user='root', # 用户名
password='root', # 密码
database='rbac_manager' # 数据库名称
)
cursor = conn.cursor(pymysql.cursors.DictCursor)
cursor.execute("select * from lqz_role")
time.sleep(1)
data = cursor.fetchall()
print(data)
cursor.close()
conn.close()
return jsonify(data)
if __name__ == '__main__':
app.run()

22.5.2requests和线程压测
安装requests
pip install requests

开启100个线程请求接口
import requests
from threading import Thread
def task():
# 有连接池接口(复用连接)
# url = "http://127.0.0.1:5000/menu"
# 无连接池接口(每次新建连接)
url = "http://127.0.0.1:5000/role"
res = requests.get(url)
print(res.json())
if __name__ == '__main__':
threads = []
# 模拟100并发请求
for _ in range(100):
t = Thread(target=task)
t.start()
threads.append(t)
for t in threads:
t.join()
"""
==================================================
核心观察
==================================================
1. 使用连接池:
- 复用数据库连接
- Threads_connected较稳定
- 避免频繁创建/销毁连接
2. 不使用连接池:
- 每次请求创建新连接
- Threads_connected暴涨
- MySQL压力大
==================================================
连接池本质
==================================================
提前创建连接 → 需要时取 → 用完归还
==================================================
核心结论(面试重点)
==================================================
连接池:
提升性能 + 控制连接数 + 提高稳定性
缺点:
连接池过小 → 请求排队 → 变慢

22.5.3测试结果
用于查看 MySQL 当前线程和连接状态
show status like '%Threads%';
我们这里留意Threads_connected即可
参数
| 参数 | 说明 |
|---|---|
| Delayed_insert_threads | 执行 INSERT DELAYED 的线程数(已废弃,基本为0) |
| Mysqlx_worker_threads | X Protocol(MySQL X Plugin)工作线程总数 |
| Mysqlx_worker_threads_active | X Protocol 当前活跃工作线程数 |
| Slow_launch_threads | 创建线程耗时超过 slow_launch_time 的线程数 |
| Threads_cached | 线程缓存中的空闲线程数 |
| Threads_connected | 当前客户端连接数(包括 Sleep) |
| Threads_created | MySQL 启动后累计创建的线程数 |
| Threads_running | 当前正在执行的线程数(不包括 Sleep) |

使用线程池连接每次都稳定在8个连接

独立连接请求数据库并发连接暴涨,但是程序执行的比较快
程序执行的比较快,命令需要快速执行,要不看不出效果

结论
连接池太小 → 并发请求排队 → 响应变慢
不用连接池 → 并发连接暴涨 → 数据库压力大
连接池的本质是:用“限制并发数”换“系统稳定性”
23.定制命令
23.1老版本(Flask-Script)
安装命令
pip install Flask-Script==2.0.3
pip install flask==1.1.4
pip install markupsafe==1.1.1
Flask-Script 状态(重点),已经废弃(Deprecated),原因:
- 不再维护
- 与 Flask 新版本不兼容
- 被官方 CLI 替代
要用低版本 python 3.8/3.9 环境,我这里使用 3.11 还是不行
本来就是淘汰的技术,这里就不再演示了
from flask import Flask
from flask_script import Manager
app = Flask(__name__)
manager = Manager(app)
# =========================
# 自定义命令
# =========================
@manager.command
def initdb(arg):
'''
# 自定义命令:
# python manage.py initdb 123
'''
print("参数:", arg)
# =========================
# 启动入口
# =========================
if __name__ == "__main__":
manager.run()

23.2flask定制命令
Flask 2.x/3.x 已经内置了命令系统,底层基于 click,不再需要老版本的 Flask-Script。
Flask 定制命令基于 Click 实现,
- 通过
@app.cli.command()定义命令 - 通过
@click.argument()接收位置参数 - 通过
@click.option()接收可选参数。
常用于数据库初始化、创建管理员、数据导入导出、缓存清理等运维场景。Flask 2.x 以后已经内置命令系统,不再需要 Flask-Script。
创建表
CREATE TABLE test_user (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
password VARCHAR(100) NOT NULL
);
自定义命令
# =========================
# Flask 自定义命令
# =========================
@app.cli.command("create-user")
@click.argument("name")
@click.argument("password")
def create_user(name, password):
"""
命令:
flask create-user peng 123456
"""
from pool import POOL
# 1. 获取连接
conn = POOL.connection()
cursor = conn.cursor()
# 2. 执行 SQL
sql = "INSERT INTO test_user(name, password) VALUES (%s, %s)"
cursor.execute(sql, (name, password))
# 3. 提交
conn.commit()
# 4. 关闭连接
cursor.close()
conn.close()
print(f"用户创建成功: {name}")
# =========================
# 普通接口
# =========================
@app.route("/")
def index():
return "index"
if __name__ == "__main__":
app.run(debug=True)
"""
命令:
flask --app <应用模块名> <命令名> [参数...]
flask --app "06flask定制命令" create-user peng 123456
"""

执行命令
# flask --app <应用模块名> <命令名> [参数...]
flask --app "06flask定制命令" create-user peng 123456

23.3django定制命令
创建一个django项目,创建user模块,创建目录user/management/commands/sayhello.py
startapp user

在该 app 下创建命令目录和命令 user/management/commands/sayhello.py
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = "测试命令"
def add_arguments(self, parser):
parser.add_argument('name', type=str)
parser.add_argument('--times', type=int, default=1)
def handle(self, *args, **options):
name = options['name']
times = options['times']
for i in range(times):
print(f"hello {name} - {i+1}")

执行命令,要保证自己django环境正常,不需要启动项目
python manage.py sayhello tom --times 3
python manage.py sayhello peng --times 3

步骤
"""
1.创建 django 项目 demo0333
2.随便创建一个 app ---> user 命令: startapp user
3.在该 app 下创建命令目录和命令 user/management/commands/sayhello.py
4.命令代码
5.执行命令
python manage.py sayhello peng --times 3
"""
24.缓存(Flask-Caching)
Flask-Caching 是 Flask 官方推荐的缓存扩展,用于将数据库查询结果、接口返回结果、函数执行结果缓存起来,从而提高系统性能、降低数据库压力。
Flask-Caching支持的缓存方式:
- SimpleCache(开发测试):缓存放在内存中
- RedisCache(生产最常用):缓存放在 Redis 中
- FileSystemCache:缓存到文件
- MemcachedCache:也是在内存中,使用较少
安装Flask-Caching
pip install Flask-Caching

安装redis
pip install redis

redis配置
DEBUG=True # some Flask specific configs
CACHE_TYPE="RedisCache" # Flask-Caching related configs ,可以缓存到 redis
CACHE_DEFAULT_TIMEOUT=300
CACHE_KEY_PREFIX='peng'
CACHE_REDIS_HOST='192.168.188.180'
CACHE_REDIS_PORT=6379
CACHE_REDIS_PASSWORD='redis'
CACHE_REDIS_DB=0

代码
from flask import Flask
from flask_caching import Cache,SimpleCache
app = Flask(__name__)
app.config.from_pyfile('./settings.py')
cache = Cache(app)
@app.route('/')
def index():
cache.set('name', 'peng')
return 'index'
@app.route('/get')
def get():
res=cache.get('name')
return res
if __name__ == '__main__':
app.run()

测试

25.信号
25.1简介
Flask 信号基于 Blinker 实现,本质上是一种观察者模式(Observer Pattern)。
简单理解:
- 某个事件发生(发送信号)
- 多个函数监听这个事件(接收信号)
- 事件触发时,所有监听函数自动执行
例如:
用户注册成功
↓
发送 create_user 信号
↓
日志记录
发送短信
发送邮件
刷新缓存
优点:
- 解耦
- 易扩展
- 符合开闭原则
AOP思想
信号其实体现了 AOP(面向切面编程)思想。
即:不修改原有业务代码,在特定位置插入额外逻辑。
-
装饰器
@login_required def index(): pass -
钩子函数
# Flask 请求扩展: @app.before_request def before(): pass -
信号
signals.before_render_template.connect(func)
信号
日志
记录用户访问
记录异常
记录操作日志
缓存
轮播图更新
↓
删除Redis缓存
消息通知
用户注册
↓
发送短信
数据同步
用户创建成功
↓
同步ES
同步MQ
同步其他系统
25.2Flask内置信号
Flask 基于 Blinker 提供了一系列内置信号,用于监听请求生命周期中的关键事件。
导入方式:
from flask import (
request_started,
request_finished,
request_tearing_down,
got_request_exception,
template_rendered,
before_render_template,
appcontext_pushed,
appcontext_popped,
appcontext_tearing_down
)
Flask内置信号
| 信号名称 | 触发时机 | 常见用途 |
|---|---|---|
request_started |
请求开始,视图函数执行前 | 请求日志、访问统计 |
request_finished |
响应生成后,请求结束时 | 响应时间统计、状态码记录 |
request_tearing_down |
请求上下文销毁时(无论是否异常) | 资源释放、关闭连接 |
got_request_exception |
请求发生异常时 | 异常日志、告警通知 |
before_render_template |
模板渲染前 | 修改模板上下文、调试 |
template_rendered |
模板渲染后 | 模板统计、性能分析 |
appcontext_pushed |
应用上下文创建时 | 初始化资源 |
appcontext_tearing_down |
应用上下文销毁前 | 清理资源、提交数据 |
appcontext_popped |
应用上下文销毁后 | 收尾处理 |
请求生命周期中的信号顺序
| 顺序 | 阶段 |
|---|---|
| 1 | appcontext_pushed |
| 2 | request_started |
| 3 | 视图函数执行 |
| 4 | before_render_template |
| 5 | template_rendered |
| 6 | request_finished |
| 7 | request_tearing_down |
| 8 | appcontext_tearing_down |
| 9 | appcontext_popped |
示例
from flask import (
Flask,
render_template,
request,
g,
request_started,
request_finished,
request_tearing_down,
got_request_exception,
before_render_template,
template_rendered,
appcontext_pushed,
appcontext_tearing_down,
appcontext_popped
)
import time
app = Flask(__name__)
# ==========================
# 应用上下文创建
# ==========================
@appcontext_pushed.connect_via(app)
def app_ctx_push(sender, **kwargs):
print("① appcontext_pushed -> 应用上下文创建")
# ==========================
# 请求开始
# ==========================
@request_started.connect_via(app)
def req_start(sender, **kwargs):
print("② request_started -> 请求开始")
g.start_time = time.time()
print(" 请求路径:", request.path)
print(" 请求方式:", request.method)
# ==========================
# 模板渲染前
# ==========================
@before_render_template.connect_via(app)
def before_render(sender, template, context, **kwargs):
print("③ before_render_template -> 模板即将渲染")
print(" 模板名称:", template.name)
# ==========================
# 模板渲染后
# ==========================
@template_rendered.connect_via(app)
def after_render(sender, template, context, **kwargs):
print("④ template_rendered -> 模板渲染完成")
print(" 模板名称:", template.name)
# ==========================
# 请求结束
# ==========================
@request_finished.connect_via(app)
def req_finished(sender, response, **kwargs):
cost = time.time() - g.start_time
print("⑤ request_finished -> 请求结束")
print(" 状态码:", response.status_code)
print(f" 耗时:{cost:.3f}s")
# ==========================
# 请求销毁
# ==========================
@request_tearing_down.connect_via(app)
def req_teardown(sender, **kwargs):
print("⑥ request_tearing_down -> 请求上下文销毁")
# ==========================
# 应用上下文销毁前
# ==========================
@appcontext_tearing_down.connect_via(app)
def app_teardown(sender, **kwargs):
print("⑦ appcontext_tearing_down -> 应用上下文销毁前")
# ==========================
# 应用上下文销毁后
# ==========================
@appcontext_popped.connect_via(app)
def app_popped(sender, **kwargs):
print("⑧ appcontext_popped -> 应用上下文销毁后")
# ==========================
# 异常监听
# ==========================
@got_request_exception.connect_via(app)
def handle_exception(sender, exception, **kwargs):
print("★ got_request_exception -> 捕获异常")
print(" 异常信息:", exception)
# ==========================
# 正常请求
# ==========================
@app.route("/")
def index():
time.sleep(1)
return render_template("index.html")
# ==========================
# 异常请求
# ==========================
@app.route("/error")
def error():
a = 1 / 0
return "ok"
if __name__ == "__main__":
app.run(debug=True)
index.html
<!DOCTYPE html>
<html>
<head>
<title>首页</title>
</head>
<body>
<h1>首页</h1>
</body>
</html>

25.3自定义信号
安装mysql相关包
pip install pymysql
pip install dbutils

创建信号,然后给信号绑定函数,当信号触发(send)时,当前函数自动执行

注册用户的时候发送信号

完整代码
import datetime
from flask import Flask
from flask.signals import Namespace
import pymysql
from pool import POOL
app = Flask(__name__)
# ==================================================
# 1. 创建信号空间(Namespace)
# ==================================================
# 一个 Namespace 可以管理多个自定义信号
# 类似于一个信号容器
my_signal = Namespace()
# 创建一个名为 create_user 的信号
# 当用户注册成功后触发
create_user_signal = my_signal.signal("create_user")
# ==================================================
# 2. 监听器1:记录日志
# ==================================================
# connect装饰器表示:
# 将当前函数绑定到 create_user_signal 信号
# 当信号触发(send)时,当前函数自动执行
@create_user_signal.connect
def write_log(sender, **kwargs):
"""
记录日志
"""
# 获取发送信号时携带的数据
user = kwargs.get("user")
print("==========日志==========")
print("新增用户成功")
print(user)
# ==================================================
# 3. 监听器2:发送欢迎邮件
# ==================================================
@create_user_signal.connect
def send_email(sender, **kwargs):
"""
发送邮件
"""
user = kwargs.get("user")
print("==========邮件==========")
print(f"给 {user['name']} 发送欢迎邮件")
# ==================================================
# 4. 监听器3:发送欢迎短信
# ==================================================
@create_user_signal.connect
def send_sms(sender, **kwargs):
"""
发送短信
"""
user = kwargs.get("user")
print("==========短信==========")
print(f"给 {user['mobile']} 发送欢迎短信")
# ==================================================
# 公共数据库操作方法
# ==================================================
def execute(sql, *args):
"""
执行增删改SQL
"""
conn = None
cursor = None
try:
# 从连接池获取连接
conn = POOL.connection()
# 返回字典格式结果
cursor = conn.cursor(
pymysql.cursors.DictCursor
)
# 执行SQL
row = cursor.execute(sql, args)
# 提交事务
conn.commit()
return row
except Exception as e:
# 回滚事务
conn.rollback()
raise e
finally:
# 关闭游标
if cursor:
cursor.close()
# 归还连接到连接池
if conn:
conn.close()
# ==================================================
# 用户注册接口
# ==================================================
@app.route("/register")
def register():
"""
模拟用户注册
"""
# 注册用户信息
user = {
"name": "peng",
"mobile": "18888888888",
"score": 100,
"create_time": datetime.datetime.now()
}
# 插入数据库
sql = """
INSERT INTO test_user
(
name,
mobile,
score,
create_time
)
VALUES
(
%s,
%s,
%s,
%s
)
"""
execute(
sql,
user["name"],
user["mobile"],
user["score"],
user["create_time"]
)
# ==================================================
# 发送信号
# ==================================================
# sender:信号发送者,一般写app或当前对象
# user=user: 额外传递的数据
# 触发后:
# write_log()
# send_email()
# send_sms()
# 都会自动执行
create_user_signal.send(
app,
user=user
)
return "注册成功"
if __name__ == '__main__':
app.run(debug=True)
"""
DROP TABLE IF EXISTS test_user;
CREATE TABLE test_user (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(32) NOT NULL COMMENT '用户名',
mobile VARCHAR(20) NOT NULL COMMENT '手机号',
score INT DEFAULT 0 COMMENT '积分',
create_time DATETIME COMMENT '创建时间'
);
"""
数据库连接池
from dbutils.pooled_db import PooledDB
import pymysql
# 创建数据库连接池对象
POOL = PooledDB(
creator=pymysql, # 使用 pymysql 作为数据库驱动
maxconnections=6, # 连接池允许的最大连接数
# 0 或 None 表示不限制
mincached=2, # 初始化连接池时,创建的最小空闲连接数
maxcached=5, # 连接池中最多保留的空闲连接数
# 超过该数量的空闲连接会被关闭
maxshared=3, # 最大共享连接数
# 对 pymysql 基本无效,因为 pymysql 不支持共享连接
blocking=True, # 连接池耗尽时是否阻塞等待
# True:等待连接释放
# False:立即抛出异常
maxusage=None, # 单个连接最大复用次数
# None 表示无限次复用
setsession=[], # 获取连接后执行的 SQL
# 例如:
# ["SET NAMES utf8mb4"]
ping=0, # 检测数据库连接是否可用
# 0:从不检测
# 1:获取连接时检测
# 2:创建游标时检测
# 4:执行 SQL 时检测
# 7:每次都检测
# ========================
# MySQL 数据库连接参数
# ========================
host='192.168.188.180', # MySQL服务器IP
port=3306, # MySQL端口
user='root', # 用户名
password='root', # 密码
database='rbac_manager', # 数据库名称
charset='utf8mb4' # 字符编码,支持emoji
)
测试

26.SQLAlchemy
26.1简介
SQLAlchemy 是 Python 中最流行的 ORM(Object Relational Mapping,对象关系映射)框架,同时也是功能最强大的数据库工具包之一。
它的作用是:
Python对象 <------> 数据库表
让开发者用 Python 代码操作数据库,而不是直接写 SQL。
SQLAlchemy 分为两层:
SQLAlchemy
│
├── Core(核心层)
│ 类似 JDBC
│ 操作SQL
│
└── ORM(对象映射层)
类似 MyBatis、Hibernate
操作对象
核心组件
SQLAlchemy 主要由 5 个核心对象组成:
-
Engine(引擎层):SQLAlchemy 与数据库之间的总入口,管理连接池(pool)、DBAPI 驱动,负责生成 Connection
-
Connection(连接层):表示一次真实数据库连接,负责执行 SQL(execute)
-
Session(会话层 / ORM核心):ORM 的核心入口,管理对象生命周期(Unit of Work),管理事务(commit/rollback)
-
Model(模型层):数据库表的Python对象映射,定义字段、约束、关系
-
Query(查询层):描述“我要查什么数据”,生成 SQL(但不立即执行)
Python ORM 生态全景
| ORM | 定位 | 特点 | 适用场景 |
|---|---|---|---|
| Django ORM | Django内置 | 强绑定Django | Django项目 |
| Peewee | 轻量ORM | 简单、小巧 | 小型项目 |
| SQLAlchemy | 企业级ORM | 灵活、强大、解耦 | 中大型系统(主流) |
| Tortoise ORM | 异步ORM | 类似Django ORM风格 | FastAPI生态 |
四大架构组件(底层架构):
- Engine(引擎)
- Connection Pooling(连接池)
- Dialect(方言)
- SQL Expression Language(SQL表达语言)
支持不同数据库
PostgreSQL
# 默认(不推荐)
create_engine("postgresql://scott:tiger@localhost/mydatabase")
# 推荐驱动(psycopg2)
create_engine("postgresql+psycopg2://scott:tiger@localhost/mydatabase")
# 轻量驱动(pg8000)
create_engine("postgresql+pg8000://scott:tiger@localhost/mydatabase")
MySQL
# 默认(旧写法)
mysql://scott:tiger@localhost/foo
# pymysql(纯 Python)
create_engine("mysql+pymysql://scott:tiger@localhost/foo")
# mysqlclient(性能更好)
create_engine("mysql+mysqldb://scott:tiger@localhost/foo")
Oracle
create_engine("oracle://scott:tiger@127.0.0.1:1521/sidname")
create_engine("oracle+cx_oracle://scott:tiger@tnsname")
SQL Server
# pyodbc(推荐)
create_engine("mssql+pyodbc://scott:tiger@mydsn")
# pymssql(旧方案)
create_engine("mssql+pymssql://scott:tiger@hostname:port/dbname")
SQLite
# Linux/Mac
sqlite:////absolute/path/to/foo.db
# Windows
sqlite:///C:\path\to\foo.db
# 推荐写法(raw string)
create_engine(r"sqlite:///C:\path\to\foo.db")
安装
pip install sqlalchemy
pip install pymysql

26.2原生操作
import threading
import time
from sqlalchemy import create_engine
# mysql mysql 数据库类型
# pymysql pymysql 驱动程序
# root 用户名
# root 密码
# 192.168.188.180 数据库IP
# 3308 数据库端口
# rbac_manager 数据库名
# charset=utf8mb4 字符集
# 创建 Engine(自带连接池)
engine = create_engine(
"mysql+pymysql://root:root@192.168.188.180:3306/rbac_manager?charset=utf8mb4",
# 连接池参数
pool_size=5, # 连接池大小
max_overflow=0, # 不允许创建额外连接
pool_timeout=30, # 获取连接最长等待时间
pool_recycle=3600, # 1小时回收一次连接
echo=False # 是否打印SQL
)
def task():
"""
模拟一个查询业务
"""
conn = None
cursor = None
try:
thread_name = threading.current_thread().name
print(f"{thread_name} 开始获取连接")
# 从连接池获取连接
conn = engine.raw_connection()
print(f"{thread_name} 获取连接成功")
cursor = conn.cursor()
sql = "select * from lqz_user"
cursor.execute(sql)
result = cursor.fetchall()
print(f"{thread_name} 查询到 {len(result)} 条数据")
# 模拟业务耗时
time.sleep(3)
except Exception as e:
print(f"{thread_name} 执行异常:{e}")
finally:
# 关闭游标
if cursor:
cursor.close()
# 归还连接到连接池
if conn:
conn.close()
print(f"{thread_name} 归还连接")
if __name__ == '__main__':
thread_list = []
# 启动20个线程
for i in range(20):
t = threading.Thread(
target=task,
name=f"Thread-{i + 1}"
)
thread_list.append(t)
t.start()
# 等待所有线程执行结束
for t in thread_list:
t.join()
print("所有线程执行完成")

26.3CRUD
这部分比较简单,自己对照代码敲一遍基本都懂了
26.3.1数据库连接池(common.py)
注意自己的数据库连接
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
# =========================
# 1 Engine:数据库连接入口 + 连接池
# =========================
engine = create_engine(
"mysql+pymysql://root:root@192.168.188.180:3306/flask_demo?charset=utf8mb4",
pool_size=5, # 连接池常驻连接数(复用连接)
max_overflow=0, # 不允许额外连接(严格控制并发)
pool_timeout=30, # 获取连接超时时间
pool_recycle=3600, # 1小时重建连接(防断开)
echo=False # 是否打印SQL(调试用)
)
# =========================
# 2 Session工厂:创建ORM会话
# =========================
Session = sessionmaker(bind=engine)
# =========================
# 3 scoped_session:线程/请求隔离(Web必备)
# =========================
session = scoped_session(Session)
# 核心作用:
# - 每个线程/请求一个独立Session
# - 防止数据串用
# - 常用于 Flask / Web项目
# =========================
# 4 关键点
# =========================
# session不是普通对象,是“线程代理对象”
# 用完必须:
# session.remove()

26.3.2数据库模型(models.py)
记得运行生成数据库表
import datetime
from sqlalchemy import String, Text, ForeignKey, DateTime
from sqlalchemy.orm import (
DeclarativeBase,
Mapped,
mapped_column,
relationship
)
"""
-- 创建数据库
CREATE DATABASE IF NOT EXISTS flask_demo
CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
-- 查看数据库
SHOW DATABASES;
-- 使用数据库
USE flask_demo;
"""
# =========================================================
# 1 Base类(所有ORM模型的根)
# =========================================================
# ✔ 所有表模型必须继承 Base
# ✔ Base 负责收集所有模型 -> 用于 create_all / drop_all
class Base(DeclarativeBase):
pass
# =========================================================
# 2 用户表(基础单表模型)
# =========================================================
class User(Base):
__tablename__ = "user" # ✔ 映射数据库表名
# === 主键 ===
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
# === 普通字段 ===
name: Mapped[str] = mapped_column(
String(32),
nullable=False, # 不允许为空
index=True # 创建索引(提升查询速度)
)
email: Mapped[str] = mapped_column(
String(32),
unique=True # 唯一约束(不能重复)
)
# === 时间字段 ===
# 注意:不能写 datetime.now()
# default = 函数引用(每次插入自动执行)
ctime: Mapped[datetime.datetime] = mapped_column(
DateTime,
default=datetime.datetime.now
)
# === 可选字段 ===
extra: Mapped[str | None] = mapped_column(Text, nullable=True)
def __repr__(self):
return self.name
# =========================================================
# 3 Address表(简单映射示例)
# =========================================================
class Address(Base):
__tablename__ = "address"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
email_address: Mapped[str] = mapped_column(
String(30),
index=True # 索引:加速查询
)
# =========================================================
# 4 Hobby(一对多:主表)
# =========================================================
class Hobby(Base):
__tablename__ = "hobby"
id: Mapped[int] = mapped_column(primary_key=True)
caption: Mapped[str] = mapped_column(
String(50),
default="篮球"
)
# =====================================================
# relationship(ORM层关系,不是数据库字段)
# =====================================================
# ✔ 作用:
# - 不生成数据库字段
# - 用于Python对象之间的关联查询
#
# ✔ 逻辑:
# Hobby 1 ---> N Person
#
# ✔ back_populates:
# 双向绑定(必须和 Person.hobby 对应)
persons = relationship(
"Person",
back_populates="hobby"
)
def __repr__(self):
return self.caption
# =========================================================
# 5 Person(一对多:从表)
# =========================================================
class Person(Base):
__tablename__ = "person"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str | None] = mapped_column(
String(32),
index=True
)
# =====================================================
# 外键(数据库层真正的关系)
# =====================================================
# ✔ 作用:
# - 约束数据合法性
# - 建立表关系
hobby_id: Mapped[int] = mapped_column(
ForeignKey("hobby.id") # 指向 Hobby 表主键
)
# =====================================================
# relationship(ORM层快捷访问)
# =====================================================
# ✔ hobby_id 是数据库字段(存ID)
# ✔ hobby 是 Python对象(直接拿 Hobby 对象)
hobby = relationship(
"Hobby",
back_populates="persons"
)
def __repr__(self):
return self.name
# =========================================================
# 6 Boy2Girl(多对多:中间表)
# =========================================================
class Boy2Girl(Base):
__tablename__ = "boy2girl"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
# =====================================================
# 两个外键:连接 Boy 和 Girl
# =====================================================
boy_id: Mapped[int] = mapped_column(ForeignKey("boy.id"))
girl_id: Mapped[int] = mapped_column(ForeignKey("girl.id"))
ctime: Mapped[datetime.datetime] = mapped_column(
DateTime,
default=datetime.datetime.now
)
# =========================================================
# 7 Girl(多对多的一方)
# =========================================================
class Girl(Base):
__tablename__ = "girl"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(
String(64),
unique=True,
nullable=False
)
# =====================================================
# 多对多 relationship
# =====================================================
# secondary = 中间表
# back_populates = 双向映射 Boy.girls
#
# ✔ 作用:
# Girl -> Boy(通过 boy2girl 自动桥接)
boys = relationship(
"Boy",
secondary="boy2girl",
back_populates="girls"
)
def __repr__(self):
return self.name
# =========================================================
# 8 Boy(多对多另一方)
# =========================================================
class Boy(Base):
__tablename__ = "boy"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(
String(64),
unique=True,
nullable=False
)
# =====================================================
# 多对多 ORM 映射
# =====================================================
girls = relationship(
"Girl",
secondary="boy2girl",
back_populates="boys"
)
def __repr__(self):
return self.name
# =========================================================
# 9 建表 / 删表入口
# =========================================================
if __name__ == "__main__":
from common import engine
# =====================================================
# drop_all:
# - 删除所有表(慎用)
# =====================================================
Base.metadata.drop_all(engine)
# =====================================================
# create_all:
# - 根据 Base 注册的所有模型生成表
# - 只会“新增表”,不会修改字段
# =====================================================
Base.metadata.create_all(engine)

26.3.3增删改查
import random
from sqlalchemy import select, update, delete
from sqlalchemy.orm import Session
from common import engine
from models import User
# =========================
# 1 Session 上下文管理
# =========================
def get_session():
return Session(engine)
# =========================
# 2 查询(新版本)
# =========================
def query_user_by_name(name: str):
with get_session() as session:
stmt = select(User).where(User.name == name)
result = session.execute(stmt)
return result.scalars().all()
# =========================
# 3 新增(单个)
# =========================
def add_user(name: str, email: str, extra: str | None = None):
with get_session() as session:
try:
user = User(
name=name,
email=email,
extra=extra # None 就写入 NULL
)
session.add(user)
session.commit()
except Exception:
session.rollback()
raise
# =========================
# 4 新增(多个)
# =========================
def add_users(users: list[User]):
with get_session() as session:
try:
session.add_all(users)
session.commit()
except Exception:
session.rollback()
raise
# =========================
# 5 更新(推荐写法)
# =========================
def update_user(old_name: str, new_name: str, new_email: str):
with get_session() as session:
try:
stmt = (
update(User)
.where(User.name == old_name)
.values(name=new_name, email=new_email)
)
session.execute(stmt)
session.commit()
except Exception:
session.rollback()
raise
# =========================
# 6 删除(推荐写法)
# =========================
def delete_user(name: str):
with get_session() as session:
try:
stmt = delete(User).where(User.name == name)
session.execute(stmt)
session.commit()
except Exception:
session.rollback()
raise
def seed_users():
with Session(engine) as session:
try:
users = []
for i in range(1, 101):
user = User(
name=f"user_{i}",
email=f"user_{i}@test.com",
extra=f"test_data_{random.randint(1000, 9999)}"
)
users.append(user)
session.add_all(users)
session.commit()
print("✅ 成功插入100条测试数据")
except Exception as e:
session.rollback()
print("❌ 插入失败:", e)
raise
def query_user_by_name_like(keyword: str):
with Session(engine) as session:
stmt = select(User).where(User.name.like(f"%{keyword}%"))
result = session.scalars(stmt).all()
return result
def to_dict(user):
return {
"id": user.id,
"name": user.name,
"email": user.email,
"ctime": user.ctime,
"extra": user.extra,
}
if __name__ == "__main__":
# # 1.seed_users()
res = query_user_by_name("user_1")
print([to_dict(u) for u in res])
# # 2.add
# add_user("tom","tom@123,com")
# res = query_user_by_name("tom")
# print([to_dict(u) for u in res])
# # 3.add
# add_user("jack", "jack@123,com","普通用户")
# res = query_user_by_name("jack")
# print([to_dict(u) for u in res])
# # 4.add_users
# users = []
# for i in range(1, 10):
# user = User(
# name=f"peng_{i}",
# email=f"peng_{i}@test.com",
# extra=f"test_data_{random.randint(1000, 9999)}"
# )
# users.append(user)
# add_users(users)
# res = query_user_by_name_like("peng")
# print([to_dict(u) for u in res])
# # 5.update_user
# update_user(
# old_name="peng",
# new_name="pengshuai",
# new_email="peng@888.comm"
# )
# res = query_user_by_name("pengshuai")
# print([to_dict(u) for u in res])
# 6.delete_user
delete_user("peng_9")
res = query_user_by_name_like("peng")
print([to_dict(u) for u in res])

26.4一对多
26.4.1一对多配置
Hobby(1的一方)
-
一个 Hobby 对应多个 Person
-
ORM自动帮你做“列表关系”
persons = relationship(
"Person",
back_populates="hobby"
)

Person(N的一方)
-
每个 Person 只属于一个 Hobby
-
ORM直接返回对象,而不是 id
hobby = relationship(
"Hobby",
back_populates="persons"
)

back_populates 的作用
双向绑定
Hobby.persons <----> Person.hobby
26.4.2一对多操作
import random
from sqlalchemy import select, update, delete
from sqlalchemy.orm import Session
from common import engine
from models import User, Hobby, Person
# =========================
# 1 Session 上下文管理
# =========================
def get_session():
return Session(engine)
def add_hobby(caption: str):
with get_session() as session:
try:
h = Hobby(
caption=caption,
)
session.add(h)
session.commit()
except Exception:
session.rollback()
raise
def get_hobbys():
with get_session() as session:
hs = select(Hobby)
result = session.execute(hs)
return result.scalars().all()
def to_dict(obj):
return {
c.name: getattr(obj, c.name)
for c in obj.__table__.columns
}
def add_person(name: str, hobby_id: int):
with get_session() as session:
try:
person = Person(
name=name,
hobby_id=hobby_id,
)
session.add(person)
session.commit()
except Exception:
session.rollback()
raise
def get_person():
with get_session() as session:
hs = select(Person)
result = session.execute(hs)
return result.scalars().all()
if __name__ == "__main__":
# 1.先 hobby 再添加 person,笨办法
# add_hobby("唱")
# add_hobby("跳")
# add_hobby("rap")
# hobbys = get_hobbys()
# h_ids = [h.id for h in hobbys ]
# add_person(name="peng",hobby_id= random.choice(h_ids))
res = get_person()
print([to_dict(u) for u in res])
# 2.一对多添加:通过表关系
# with get_session() as session:
# try:
# person = Person(name='tom', hobby=Hobby(caption="跳"))
# session.add(person)
# session.commit()
# except Exception:
# session.rollback()
# raise
# res = get_person()
# print([to_dict(u) for u in res])
# 3.一对多查询
with get_session() as session:
person = session.query(Person).filter_by(id=2).first()
print(person.id)
print(person.name)
print(person.hobby_id)
print(person.hobby)
# 4.夸表查询
with get_session() as session:
try:
# hobby = session.execute(
# select(Hobby).where(Hobby.name == "唱")
# ).scalar_one()
h = session.query(Hobby).filter_by(caption="唱").first()
person1 = Person(name='qwe', hobby=h)
person2 = Person(name='asd', hobby=h)
person3 = Person(name='zxc', hobby=h)
session.add_all([person1,person2,person3])
session.commit()
except Exception:
session.rollback()
raise
res = get_person()
print([to_dict(u) for u in res])
with get_session() as session:
hobby = session.query(Hobby).filter_by(id=1).first()
print(hobby.id)
print(hobby.caption)
print(hobby.persons)
print(hobby.persons[0].id)

26.5threading.local()
local() 的意义是:每个线程拥有“独立的属性空间”,互不干扰
也就是说:
-
线程 A:
l.session = "aaa" -
线程 B:
l.session = "bbb" -
两者互相看不见
示例
import uuid
from threading import local, Thread
import time
# threading.local() 创建一个“线程隔离的对象”
# 它的特点是:不同线程访问同一个变量名,但实际存储是独立的
# 类似:每个线程都有自己的“字典空间”
l = local()
def worker(name, value):
"""
线程执行函数
name: 线程名称(用于打印)
value: 当前线程要设置的 session 值
"""
# 给当前线程绑定一个属性 session
# 注意:这个 session 只属于当前线程,不会影响其他线程
# 这里的 session 和 tokne 可以随机命名
l.session = value
l.token = uuid.uuid4()
# 模拟线程执行耗时操作
time.sleep(1)
# 读取当前线程自己的 session
# 不同线程这里取到的值是互不干扰的
print(f"{name}: {l.session}")
print(f"{l.token }")
# 创建线程 t1,传入参数 ("thread-1", "aaa")
t1 = Thread(target=worker, args=("thread-1", "aaa"))
# 创建线程 t2,传入参数 ("thread-2", "bbb")
t2 = Thread(target=worker, args=("thread-2", "bbb"))
# 启动线程1
t1.start()
# 启动线程2
t2.start()
# 等待线程1执行完成
t1.join()
# 等待线程2执行完成
t2.join()

26.6scoped线程安全
这里提供2中线程安全写法
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
from models import User
engine = create_engine("mysql+pymysql://root:root@192.168.188.180:3306/flask_demo?charset=utf8mb4")
# 1️.创建 session 工厂(注意:不是 Session 实例)
SessionFactory = sessionmaker(bind=engine)
# 2️.创建线程安全 session
session = scoped_session(SessionFactory)
# 方式一:scoped_session(经典线程安全方案)
db = session()
result = db.query(User).all()
print(result)
# 方式二:Session(engine) + 手动管理(2.x 推荐)
with SessionFactory() as session:
result= session.query(User).all()
print(result)
# 之前:不是线程安全的---》多个线程公用这一个
# from sqlalchemy.orm import Session
# session=Session(engine)

26.7flask使用scoped_session
26.7.1配置(common.py)
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
# =========================
# 1 Engine:数据库连接入口 + 连接池
# =========================
engine = create_engine(
"mysql+pymysql://root:root@192.168.188.180:3306/flask_demo?charset=utf8mb4",
pool_size=5, # 连接池常驻连接数(复用连接)
max_overflow=0, # 不允许额外连接(严格控制并发)
pool_timeout=30, # 获取连接超时时间
pool_recycle=3600, # 1小时重建连接(防断开)
echo=False # 是否打印SQL(调试用)
)
# =========================
# 2 Session工厂:创建ORM会话
# =========================
SessionFactory = sessionmaker(bind=engine)
# =========================
# 3 scoped_session:线程/请求隔离(Web必备)
# =========================
Session = scoped_session(SessionFactory )
# 核心作用:
# - 每个线程/请求一个独立Session
# - 防止数据串用
# - 常用于 Flask / Web项目
# =========================
# 4 关键点
# =========================
# session不是普通对象,是“线程代理对象”
# 用完必须:
# session.remove()
# # 提供获取 session 方法(可选)
# def get_session():
# return Session()
# # 请求结束后必须清理
# def remove_session(exception=None):
# Session.remove()

26.7.2flask使用
from flask import Flask, jsonify
from common import Session
from models import User
app = Flask(__name__)
def to_dict(obj):
return {
c.name: getattr(obj, c.name)
for c in obj.__table__.columns
}
@app.get("/user/<int:user_id>")
def index(user_id):
try:
session = Session()
user = session.query(User).filter(User.id == user_id).first()
if not user:
return jsonify({
"code": 404,
"message": "user not found"
}), 404
return jsonify({
"code": 200,
"data": to_dict(user)
})
finally:
Session.close()
if __name__ == "__main__":
app.run(threaded=True)

26.8多对多
26.8.1配置
中间表
| 作用 | 说明 |
|---|---|
| 关系拆解 | 把 N↔N 拆成 1↔N + N↔1 |
| 存关系 | 只存 ID,不存对象 |
| 无业务字段 | 通常只做“连接桥梁” |
# =========================================================
# 6 Boy2Girl(多对多:中间表)
# =========================================================
class Boy2Girl(Base):
__tablename__ = "boy2girl"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
# =====================================================
# 两个外键:连接 Boy 和 Girl
# =====================================================
boy_id: Mapped[int] = mapped_column(ForeignKey("boy.id"))
girl_id: Mapped[int] = mapped_column(ForeignKey("girl.id"))
ctime: Mapped[datetime.datetime] = mapped_column(
DateTime,
default=datetime.datetime.now
)

Girl
-
Girl 不直接连 Boy
-
通过
boy2girl中间表自动关联
# =========================================================
# 7 Girl(多对多的一方)
# =========================================================
class Girl(Base):
__tablename__ = "girl"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(
String(64),
unique=True,
nullable=False
)
# =====================================================
# 多对多 relationship
# =====================================================
# secondary = 中间表
# back_populates = 双向映射 Boy.girls
#
# ✔ 作用:
# Girl -> Boy(通过 boy2girl 自动桥接)
boys = relationship(
"Boy",
secondary="boy2girl",
back_populates="girls"
)
def __repr__(self):
return self.name

Boy
# =========================================================
# 8 Boy(多对多另一方)
# =========================================================
class Boy(Base):
__tablename__ = "boy"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(
String(64),
unique=True,
nullable=False
)
# =====================================================
# 多对多 ORM 映射
# =====================================================
girls = relationship(
"Girl",
secondary="boy2girl",
back_populates="boys"
)
def __repr__(self):
return self.name

26.8.2操作
方式一不通过关系表,手动创建
方式二通过关系表自动创建
from common import Session
from models import Boy, Girl, Boy2Girl
session = Session()
# =========================
# 方式一:手动中间表
# =========================、
# 1.添加 Boy Girl
# boy = Boy(name="胡歌")
# girl1 = Girl(name="赵露思")
# girl2 = Girl(name="田曦薇")
# session.add_all([boy, girl1, girl2])
# session.commit()
# 2.添加映射关系
# rel1 = Boy2Girl(boy_id=boy.id, girl_id=girl1.id)
# rel2 = Boy2Girl(boy_id=boy.id, girl_id=girl2.id)
# session.add(rel1)
# session.add(rel2)
# session.commit()
# 3.测试查询
# boy = session.query(Boy).filter_by(name="古天乐").first()
# print(boy)
# print(boy.girls)
# =========================
# 方式二:ORM关系写法(推荐)
# =========================
boy = Boy(name="peng")
girl1 = Girl(name="赵今麦")
girl2 = Girl(name="张子枫")
girl3 = Girl(name="宋祖儿")
boy.girls = [girl1, girl2, girl3]
session.add(boy)
session.commit()
boy = session.query(Boy).filter_by(name="peng").first()
print(boy)
print(boy.girls)
girl = session.query(Girl).filter_by(name="赵今麦").first()
print(girl)
print(girl.boys)

26.9更多查询操作
from sqlalchemy import and_, or_, func
from common import Session
from models import User, Person, Hobby, Girl, Boy, Boy2Girl
session = Session()
def print_res(title, res):
print(f"\n===== {title} =====")
if res is None:
print(None)
return
if not isinstance(res, list):
res = [res]
for item in res:
if hasattr(item, "__table__"):
data = {c.name: getattr(item, c.name) for c in item.__table__.columns}
print(data)
else:
print(item)
# =====================================================
# filter_by(简单等值查询)
# =====================================================
res = session.query(User).filter_by(name="张三").first()
print_res("filter_by", res)
# =====================================================
# filter / where(推荐)
# =====================================================
res = session.query(User).filter(User.id >= 2).all()
print_res("filter", res)
res = session.query(User).where(User.id >= 2).all()
print_res("where", res)
# =====================================================
# between
# =====================================================
res = session.query(User).filter(User.id.between(1, 10)).all()
print_res("between", res)
# =====================================================
# in / not in
# =====================================================
res = session.query(User).filter(User.id.in_([1, 3, 5])).all()
print_res("in", res)
res = session.query(User).filter(~User.id.in_([1, 3, 5])).all()
print_res("not in", res)
# =====================================================
# 子查询 in
# =====================================================
# 先查出 name=刘备 的所有 id,再用这些 id 去过滤 User 表
sub = session.query(User.id).filter_by(name="刘备")
res = session.query(User).filter(User.id.in_(sub)).all()
print_res("subquery:", res)
# =====================================================
# and / or
# =====================================================
res = session.query(User).filter(
and_(User.id > 1, User.name == "诸葛亮")
).all()
print_res("and:", res)
res = session.query(User).filter(
or_(User.id <= 2, User.name == "李世民")
).all()
print_res("or:", res)
# =====================================================
# like 模糊查询
# =====================================================
res = session.query(User).filter(User.name.like("%李%")).all()
print_res("like:", res)
# =====================================================
# 分页
# =====================================================
page = 2
size = 4
res = session.query(User)[(page - 1) * size: page * size]
print_res("page:", res)
# =====================================================
# 排序
# =====================================================
res = session.query(User).order_by(User.name.asc()).all()
print_res("asc:", res)
res = session.query(User).order_by(User.name.desc(), User.id.asc()).all()
print_res("desc multi:", res)
# =====================================================
# 🔟 分组 + 聚合
# =====================================================
# 这个示例不好,没有分组条件
res = session.query(
User.name,
func.max(User.id),
func.sum(User.id),
func.min(User.id),
func.count(User.id)
).group_by(User.name).all()
print_res("group by:", res)
# =====================================================
# having
# =====================================================
res = session.query(
User.name,
func.sum(User.id).label("total")
).group_by(User.name).having(func.sum(User.id) > 10).all()
print_res("having:", res)
# =====================================================
# 连表查询(笛卡尔积方式)
# =====================================================
res = session.query(Person, Hobby).filter(
Person.hobby_id == Hobby.id
).all()
print_res("cartesian join:", res)
# =====================================================
# join(推荐)
# =====================================================
res = session.query(Person).join(Hobby).all()
print_res("inner join:", res)
res = session.query(Person).join(Hobby, isouter=True).all()
print_res("left join:", res)
res = session.query(Hobby).join(Person, isouter=True).all()
print_res("right join:", res)
# =====================================================
# 多对多查询(Boy ↔ Girl)
# =====================================================
g = session.query(Girl).first()
if g:
print_res("girl -> boys:", g.boys)
b = session.query(Boy).first()
print(b)
if b:
print_res("boy -> girls:", b.girls)
# =====================================================
# UNION
# =====================================================
# 并集
q1 = session.query(Person.name).filter(Person.id > 10)
q2 = session.query(Person.name).filter(Person.id < 5)
res = q1.union(q2).all()
print("union:", res)
res = q1.union_all(q2).all()
print("union_all:", res)
# =====================================================
# 提交(如果有增删改)
# =====================================================
session.commit()
session.close()

26.10集成flask
安装相关包
pip install flask sqlalchemy pymysql
pip install redis
pip install Flask-Caching
pip install flask-session
26.10.1数据库(common.py)
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
# =========================
# 1 Engine:数据库连接入口 + 连接池
# =========================
engine = create_engine(
"mysql+pymysql://root:root@192.168.188.180:3306/flask_demo?charset=utf8mb4",
pool_size=5, # 连接池常驻连接数(复用连接)
max_overflow=0, # 不允许额外连接(严格控制并发)
pool_timeout=30, # 获取连接超时时间
pool_recycle=3600, # 1小时重建连接(防断开)
echo=False # 是否打印SQL(调试用)
)
# =========================
# 2 Session工厂:创建ORM会话
# =========================
SessionFactory = sessionmaker(bind=engine)
# =========================
# 3 scoped_session:线程/请求隔离(Web必备)
# =========================
session = scoped_session(SessionFactory )

26.10.2数据库模型(models.py)
import datetime
from sqlalchemy import String, Text, ForeignKey, DateTime
from sqlalchemy.orm import (
DeclarativeBase,
Mapped,
mapped_column,
relationship
)
"""
-- 创建数据库
CREATE DATABASE IF NOT EXISTS flask_demo
CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
-- 查看数据库
SHOW DATABASES;
-- 使用数据库
USE flask_demo;
"""
# =========================================================
# 1 Base类(所有ORM模型的根)
# =========================================================
# ✔ 所有表模型必须继承 Base
# ✔ Base 负责收集所有模型 -> 用于 create_all / drop_all
class Base(DeclarativeBase):
pass
# =========================================================
# 2 用户表(基础单表模型)
# =========================================================
class User(Base):
__tablename__ = "user" # ✔ 映射数据库表名
# === 主键 ===
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
# === 普通字段 ===
name: Mapped[str] = mapped_column(
String(32),
nullable=False, # 不允许为空
index=True # 创建索引(提升查询速度)
)
email: Mapped[str] = mapped_column(
String(32),
unique=True # 唯一约束(不能重复)
)
# === 时间字段 ===
# 注意:不能写 datetime.now()
# default = 函数引用(每次插入自动执行)
ctime: Mapped[datetime.datetime] = mapped_column(
DateTime,
default=datetime.datetime.now
)
# === 可选字段 ===
extra: Mapped[str | None] = mapped_column(Text, nullable=True)
def __repr__(self):
return self.name
# =========================================================
# 3 Address表(简单映射示例)
# =========================================================
class Address(Base):
__tablename__ = "address"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
email_address: Mapped[str] = mapped_column(
String(30),
index=True # 索引:加速查询
)

26.10.3设置(settings.py)
from redis import Redis
DEBUG = True
DEBUG = True # some Flask specific configs
CACHE_TYPE = "RedisCache" # Flask-Caching related configs ,可以缓存到redis
CACHE_DEFAULT_TIMEOUT = 300
CACHE_KEY_PREFIX = 'peng'
CACHE_REDIS_HOST = '192.168.188.180'
CACHE_REDIS_PORT = 6379
CACHE_REDIS_PASSWORD = 'redis'
CACHE_REDIS_DB = 0
SESSION_TYPE = 'redis'
SESSION_REDIS = Redis(host='192.168.188.180', port='6379', password="redis")
SESSION_KEY_PREFIX = 'peng'

26.10.4视图(views.py)
from flask import Blueprint, render_template,request
from src.common import session
from src.models import User
main_bl = Blueprint('main', __name__)
@main_bl.route("/")
def index():
return render_template('index.html')
@main_bl.route("/users")
def users():
try:
users = session.query(User).all()
return {
"data": [
{
"id": u.id,
"name": u.name
}
for u in users
]
}
finally:
session.close()

26.10.5初始化(__init__.py)
from flask import Flask
app=Flask(__name__,template_folder='../templates',static_folder='../statics')
# 1 配置文件
app.config.from_pyfile('./settings.py')
# 2 使用缓存
from flask_caching import Cache,SimpleCache
cache = Cache(app)
# 3 使用flask-session
from flask_session import Session
Session(app)
# # 4 自定制命令
# import click
# @app.cli.command("create-user")
# @click.argument("name")
# @click.argument("password")
# def create_user(name,password):
# print(name)
# 5 蓝图
from . import views
app.register_blueprint(views.main_bl)
# 6 使用sqlalchemy
# common.py ---> session = Session()

26.10.6测试

26.11数据库迁移(flask-migrate)
26.11.1简介
Flask-Migrate 是 Flask 项目中最常用的数据库版本管理工具,本质上是对 SQLAlchemy 官方迁移工具 Alembic 的封装。
作用:当你的 ORM 模型发生变化时,不需要手工写 SQL 去修改表结构,而是自动生成数据库变更脚本。
安装
# 迁移核心包
pip install flask-sqlalchemy
pip install flask-migrate
# 其他需要的包
pip install pymysql
pip install flask_caching
pip install flask_session

迁移命令
| 命令 | 作用 |
|---|---|
flask db init |
初始化迁移系统 |
flask db migrate |
根据模型变化生成“迁移脚本” |
flask db upgrade |
执行迁移脚本,真正改数据库 |
26.11.2数据库配置(databse.py)
提前创建好数据库
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from . import app
db = SQLAlchemy(app)
migrate = Migrate(app, db) # flask 就会多出好几个命令---》
"""
-- 创建数据库
CREATE DATABASE IF NOT EXISTS flask_demo
CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
-- 查看数据库
SHOW DATABASES;
-- 使用数据库
USE flask_demo;
"""

26.11.3数据库实体(models.py)
这里留2个字段一会再迁移
import datetime
from .databse import db
class TestUser(db.Model):
__tablename__ = 'user' # 表名
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(32), index=True, nullable=False) # name列varchar32,索引,不可为空
email = db.Column(db.String(32), unique=True) # email 列,varchar32,唯一
# 新加字段
# password = db.Column(db.String(32), nullable=False)
# extra = db.Column(db.Text, nullable=True)
# datetime.datetime.now不能加括号,加了括号,以后永远是当前时间
ctime = db.Column(db.DateTime, default=datetime.datetime.now)
# def __str__(self):
# return self.name
def __repr__(self):
return self.name

26.11.4配置(settings.py)
from redis import Redis
DEBUG = True
DEBUG = True # some Flask specific configs
CACHE_TYPE = "RedisCache" # Flask-Caching related configs ,可以缓存到redis
CACHE_DEFAULT_TIMEOUT = 300
CACHE_KEY_PREFIX = 'peng'
CACHE_REDIS_HOST = '192.168.188.180'
CACHE_REDIS_PORT = 6379
CACHE_REDIS_PASSWORD = 'redis'
CACHE_REDIS_DB = 0
SESSION_TYPE = 'redis'
SESSION_REDIS = Redis(host='192.168.188.180', port='6379', password="redis")
SESSION_KEY_PREFIX = 'peng'
SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:root@192.168.188.180:3306/flask_demo?charset=utf8mb4"
SQLALCHEMY_POOL_SIZE = 5
SQLALCHEMY_POOL_TIMEOUT = 30
SQLALCHEMY_POOL_RECYCLE = -1
# 追踪对象的修改并且发送信号
SQLALCHEMY_TRACK_MODIFICATIONS = False

25.11.5初始化(__init__.py)
from flask import Flask
app=Flask(__name__,template_folder='../templates',static_folder='../statics')
# 1 配置文件
app.config.from_pyfile('./settings.py')
# 2 使用缓存
from flask_caching import Cache,SimpleCache
cache = Cache(app)
# 3 使用flask-session
from flask_session import Session
Session(app)
# # 4 自定制命令
# import click
# @app.cli.command("create-user")
# @click.argument("name")
# @click.argument("password")
# def create_user(name,password):
# print(name)
# 5 蓝图
from . import views
app.register_blueprint(views.main_bl)

25.11.6视图层(views.py)
主要实现几个接口测试

25.11.7迁移
先创建好数据库,然后执行迁移命令
flask db init
flask db migrate
flask db upgrade

数据库表也成功创建

25.11.8再次迁移
这次增加2个字段,再次迁移

表新增字段成功

25.11.9测试
测试add和users接口可以正常新增和查询

25.12开源项目
项目地址
https://gitee.com/lunarlanding/pear-admin-flask

创建数据库
-- 创建数据库
CREATE DATABASE IF NOT EXISTS pear_admin_flask
CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
-- 查看数据库
SHOW DATABASES;
-- 使用数据库
USE pear_admin_flask

虚拟环境配置
# 1. 查看当前系统已安装的 Python 版本及路径
py -0p
# 2. 使用 Python 3.11 创建虚拟环境(推荐用于 Flask 老项目)
py -3.11 -m venv venv
# 3. 激活虚拟环境(进入项目专属 Python 环境)
venv\Scripts\activate
# 4. 安装项目依赖(只安装到当前 venv,不影响系统 Python)
pip install -r requirements.txt
# 5.退出命令 deactivate

迁移命令
flask db init
flask db migrate
flask db upgrade
flask admin init

命令启动
flask --app app.py run -h 0.0.0.0 -p 8000 --debug
# 或者
python app.py

解释器启动

📌 创作不易,感谢支持!
每一篇内容都凝聚了心血与热情,如果我的内容对您有帮助,欢迎请我喝杯咖啡☕,您的支持是我持续分享的最大动力!
💬 加入交流群(QQ群):576434538

浙公网安备 33010602011771号