Flask 教程

Flask 是 Python 的轻量级 Web 框架,由 Armin Ronacher 于 2010 年发布。它以"简单、灵活、可扩展"著称,核心只有路由和模板,其余功能通过扩展实现——适合快速开发和小型到中型应用。

Flask 的特点

特点说明
轻量核心极小,一个文件就能跑起来
灵活不强制项目结构,自由组织
扩展丰富Flask-SQLAlchemy、Flask-Login 等
内置模板Jinja2 模板引擎
开发调试自带开发服务器与调试器

最小应用

from flask import Flask

app = Flask(__name__)

@app.route("/")
def home():
    return "Hello, CodeNest!"

if __name__ == "__main__":
    app.run(debug=True)
# 运行
python app.py
# 访问 http://127.0.0.1:5000

Flask vs FastAPI vs Django

框架定位适用
Flask轻量灵活小中型、快速原型
FastAPI高性能 API前后端分离、高并发
Django大而全大型全栈应用

Flask 的生态

  • Flask-SQLAlchemy:ORM 数据库
  • Flask-Login:用户认证
  • Flask-Migrate:数据库迁移
  • Flask-WTF:表单
  • Flask-RESTful:API 扩展
💡 提示

Flask 学习曲线平缓,适合作为 Python Web 入门框架;理解它的路由/模板/请求响应模型后,迁移到其他框架很容易。

Flask 简介

Flask 简介:Flask 的历史、设计理念与核心概念。

Flask 的诞生

  • 2010 年由 Armin Ronacher 创建(Werkzeug 团队)
  • 源于对微型框架的需求("micro-framework")
  • 建立在 Werkzeug(WSGI)和 Jinja2(模板)之上

"微型"的含义

Flask 的"micro"不是说功能少,而是核心精简:默认只提供路由、模板、请求响应;数据库、认证、表单等通过扩展按需添加。

Flask 的核心组成

组件作用
WerkzeugWSGI 工具库(路由、请求响应)
Jinja2模板引擎
click命令行接口(flask run)
itsdangerous签名(Session 安全)

Flask 的优势

  • 上手快:半小时写个 Web 应用
  • 灵活:项目结构自由
  • 文档优秀:官方文档清晰完整
  • 社区大:问题容易找到答案

典型使用场景

  • 个人网站、博客
  • 内部管理系统
  • API 服务(配合前端)
  • 机器学习模型部署
  • 快速原型验证
💡 提示

Flask 的理念:"微核心 + 扩展生态"。你掌控一切,框架不替你做决定。

Flask 安装

安装 Flask 并搭建开发环境。

环境要求

  • Python 3.8+
  • pip
  • 推荐虚拟环境

创建虚拟环境

python3 -m venv venv
source venv/bin/activate        # macOS/Linux
venv\Scripts\activate          # Windows

安装 Flask

pip install flask

# 验证
python -c "import flask; print(flask.__version__)"
# 3.x

常用扩展安装

pip install flask-sqlalchemy     # ORM
pip install flask-migrate        # 迁移
pip install flask-login          # 认证
pip install flask-wtf            # 表单
pip install flask-cors           # 跨域

第一个应用

# app.py
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello Flask!"

if __name__ == "__main__":
    app.run(debug=True)

运行方式

# 方式一:直接运行
python app.py

# 方式二:flask 命令
export FLASK_APP=app.py
export FLASK_DEBUG=1
flask run

常见问题

问题解决
端口被占用app.run(port=5001)
局域网访问app.run(host="0.0.0.0")
修改后不生效开启 debug=True 自动重载
💡 提示

始终在虚拟环境中开发;保存依赖:pip freeze > requirements.txt。

Flask 第一个应用

创建你的第一个 Flask 应用,走通完整流程。

编写代码

# app.py
from flask import Flask, request

app = Flask(__name__)

# 路由 + 视图函数
@app.route("/")
def home():
    return "<h1>欢迎来到 CodeNest</h1>"

@app.route("/hello/<name>")
def hello(name):
    return f"你好,{name}!"

@app.route("/api/data")
def data():
    return {"message": "这是 JSON 数据", "count": 42}

if __name__ == "__main__":
    app.run(debug=True)

启动与访问

python app.py
# * Running on http://127.0.0.1:5000

# 访问:
# http://127.0.0.1:5000/          → 欢迎页
# http://127.0.0.1:5000/hello/张三 → 你好,张三!
# http://127.0.0.1:5000/api/data  → JSON

代码解析

代码作用
Flask(__name__)创建应用实例
@app.route("/")注册路由
def home()视图函数,返回响应
debug=True调试模式(自动重载 + 报错页)

返回 JSON

# 直接返回字典 → Flask 自动转 JSON
@app.route("/api/user")
def user():
    return {
        "id": 1,
        "name": "张三",
        "skills": ["Python", "Flask"]
    }
# 响应头 Content-Type: application/json

debug 模式注意

# 生产环境必须关闭 debug!
if __name__ == "__main__":
    app.run(debug=False)   # 生产
# 或通过环境变量控制
💡 提示

第一个应用跑通后,理解三要素:路由(URL)→ 视图函数(逻辑)→ 响应(返回内容)。

Flask 基本概念

掌握 Flask 基本概念:请求、响应、路由、应用上下文。

Flask 的工作流程

浏览器请求 → WSGI → Flask 路由匹配 → 视图函数
                                 ↓
浏览器响应 ← WSGI ← 响应对象 ← 返回值处理

核心概念

概念说明
应用(app)Flask 实例,全局配置与路由
路由(route)URL 到视图函数的映射
视图函数处理请求并返回响应
请求(request)封装客户端请求数据
响应(response)返回给客户端的数据
上下文(context)请求/应用上下文,供全局访问

request 对象

from flask import request

@app.route("/info")
def info():
    return {
        "method": request.method,      # GET/POST
        "path": request.path,          # /info
        "args": request.args.to_dict(),# 查询参数
        "headers": dict(request.headers),  # 请求头
        "ip": request.remote_addr,     # 客户端 IP
    }

响应返回方式

from flask import Response, jsonify

# 1. 字符串
return "Hello"

# 2. 字典(自动 JSON)
return {"ok": True}

# 3. 元组(内容, 状态码)
return "Not Found", 404

# 4. Response 对象
resp = Response("内容", status=200)
resp.headers["X-Custom"] = "value"
return resp

应用上下文

# current_app / g 在请求中全局可用
from flask import current_app, g

@app.before_request
def before():
    g.start_time = time.time()   # 存到 g

@app.route("/slow")
def slow():
    # 视图内可访问 g
    elapsed = time.time() - g.start_time
    return f"处理耗时 {elapsed:.3f}s"
💡 提示

理解"请求 → 上下文 → 视图 → 响应"这条链路,Flask 的一切都围绕它运转。

Flask 项目结构

掌握 Flask 项目结构:从小单文件到工程化组织。

小项目:单文件

app.py          # 全部代码

中项目:模块化

myapp/
├── app.py              # 入口
├── config.py           # 配置
├── models.py           # 数据模型
├── views/              # 视图
│   ├── __init__.py
│   ├── main.py
│   └── api.py
├── templates/          # Jinja2 模板
├── static/             # 静态文件(css/js/img)
└── requirements.txt

大型项目:工厂模式

myapp/
├── run.py              # 启动入口
├── config.py           # 配置(开发/生产)
├── app/
│   ├── __init__.py     # create_app 工厂
│   ├── models/         # 模型
│   ├── views/          # 蓝图
│   ├── templates/
│   ├── static/
│   └── extensions.py   # 扩展初始化
├── migrations/         # 数据库迁移
└── requirements.txt

工厂函数模式(create_app)

# app/__init__.py
from flask import Flask

def create_app(config_name="default"):
    app = Flask(__name__)
    app.config.from_object(config[config_name])

    # 初始化扩展
    db.init_app(app)

    # 注册蓝图
    from .views import main
    app.register_blueprint(main.bp)

    return app
# run.py
from app import create_app

app = create_app()

if __name__ == "__main__":
    app.run(debug=True)

结构选择建议

  • 原型/小项目:单文件
  • 中型:模块化目录
  • 可扩展/团队:工厂 + 蓝图 + 扩展分离
💡 提示

项目变大后第一时间拆蓝图(Blueprint):按功能模块组织路由,避免单文件无限膨胀。

Flask 路由

掌握 Flask 路由:URL 映射、动态参数、HTTP 方法。

基本路由

from flask import Flask
app = Flask(__name__)

@app.route("/")
def index():
    return "首页"

@app.route("/about")
def about():
    return "关于页"

动态路由(URL 参数)

# <参数名> 捕获 URL 片段
@app.route("/user/<username>")
def user(username):
    return f"用户:{username}"

# 带类型转换器
@app.route("/post/<int:post_id>")
def post(post_id):
    return f"文章 ID:{post_id}"   # 自动转 int

@app.route("/files/<path:filepath>")
def file(filepath):
    return f"文件路径:{filepath}"  # path 可含斜杠

类型转换器

转换器匹配示例
string字符串(默认)<string:name>
int整数<int:id>
float浮点数<float:price>
path含斜杠路径<path:path>
uuidUUID<uuid:uid>

HTTP 方法

@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        return "提交登录"
    return "显示登录表单"

# 或
@app.get("/data")      # 只 GET
def get_data(): ...
@app.post("/data")     # 只 POST
def post_data(): ...

url_for(反向生成 URL)

from flask import url_for

@app.route("/user/<int:uid>")
def user(uid):
    return f"用户 {uid}"

# 视图中生成 URL
url = url_for("user", uid=42)    # "/user/42"

# 模板中使用
# <a href="{{ url_for('user', uid=42) }}">查看</a>
💡 提示

路由命名(函数名)要清晰,配合 url_for() 使用——改 URL 时只需改一处。

Flask 请求与响应

掌握 Flask 请求与响应:完整的数据交互。

请求数据(request)

from flask import request

# 查询参数(GET)
request.args.get("page", default=1, type=int)   # ?page=2
request.args.get("q")

# 表单数据(POST)
request.form.get("username")        # application/x-www-form-urlencoded
request.form.getlist("tags")        # 多值

# JSON 数据(POST)
data = request.get_json()           # 返回 dict/None
data.get("name")

# 请求头
request.headers.get("Authorization")
request.headers.get("User-Agent")

# 文件上传
file = request.files.get("file")
file.filename
file.save("/tmp/uploaded.jpg")

# 其他
request.method      # 请求方法
request.path        # 路径
request.remote_addr # IP
request.cookies.get("key")  # Cookie

响应数据

from flask import jsonify, redirect, url_for, make_response

# JSON
return jsonify({"status": "ok", "data": [1, 2, 3]})

# 重定向
return redirect(url_for("index"))
return redirect("https://example.com")

# 状态码 + 响应头
resp = make_response("内容")
resp.status_code = 404
resp.headers["X-Powered-By"] = "CodeNest"
return resp

# 设置 Cookie
resp = make_response("ok")
resp.set_cookie("theme", "dark", max_age=3600)
return resp

# 文件下载
from flask import send_file
return send_file("report.pdf", as_attachment=True)

完整示例

@app.post("/api/users")
def create_user():
    data = request.get_json()
    if not data or not data.get("name"):
        return jsonify({"error": "缺少 name"}), 400

    # 业务逻辑...
    return jsonify({"id": 1, "name": data["name"]}), 201
💡 提示

request 取数据、返回值定义响应——理解这两个对象,就能处理 90% 的接口开发。

Flask 视图函数

掌握 Flask 视图函数:组织业务逻辑的入口。

视图函数基础

from flask import Flask, request, abort

app = Flask(__name__)

@app.route("/")
def index():
    return "首页"

# 带逻辑的视图
@app.route("/search")
def search():
    keyword = request.args.get("q", "")
    if not keyword:
        abort(400, "缺少搜索词")
    # 业务逻辑...
    return f"搜索:{keyword}"

视图返回类型

from flask import jsonify, render_template, redirect

# 返回模板
@app.route("/page")
def page():
    return render_template("page.html", title="页面")

# 返回 JSON
@app.route("/api/data")
def api():
    return jsonify({"ok": True})

# 返回重定向
@app.route("/old")
def old():
    return redirect("/new")

错误处理

from flask import abort

@app.route("/user/<int:uid>")
def user(uid):
    if uid <= 0:
        abort(404)              # 404 页面
    if uid > 10000:
        abort(403, "无权访问")   # 403 + 描述
    return f"用户 {uid}"

# 自定义错误页面
@app.errorhandler(404)
def not_found(e):
    return jsonify({"error": "页面不存在"}), 404

@app.errorhandler(500)
def server_error(e):
    return jsonify({"error": "服务器错误"}), 500

请求钩子(before/after)

@app.before_request
def before_request():
    # 每个请求前执行(鉴权、日志)
    pass

@app.after_request
def after_request(response):
    # 每个响应后执行(加头、统计)
    response.headers["X-Server"] = "Flask"
    return response
💡 提示

视图函数保持"薄":只做参数解析 + 调用业务 + 返回响应;复杂逻辑抽到 service 层。

Flask 模板渲染

掌握 Flask 模板渲染:Jinja2 模板引擎。

渲染模板

from flask import render_template

@app.route("/")
def index():
    return render_template("index.html", title="首页", users=users)

模板文件结构

templates/
├── index.html
├── user/
│   └── profile.html
└── base.html

Jinja2 语法

<!-- 输出变量 -->
<h1>{{ title }}</h1>

<!-- if 判断 -->
{% if user %}
  <p>你好,{{ user.name }}</p>
{% else %}
  <p>请登录</p>
{% endif %}

<!-- for 循环 -->
<ul>
  {% for u in users %}
    <li>{{ u.name }} - {{ u.age }}岁</li>
  {% endfor %}
</ul>

<!-- 过滤器 -->
{{ name | upper }}
{{ content | truncate(50) }}
{{ price | round(2) }}

<!-- 注释 -->
{# 这是模板注释 #}

模板继承

<!-- base.html -->
<!DOCTYPE html>
<html>
<head>
  <title>{% block title %}默认标题{% endblock %}</title>
</head>
<body>
  <nav>网站导航</nav>
  {% block content %}{% endblock %}
  <footer>版权信息</footer>
</body>
</html>
<!-- index.html -->
{% extends "base.html" %}

{% block title %}首页{% endblock %}

{% block content %}
  <h1>欢迎</h1>
  {% include "partials/list.html" %}
{% endblock %}

模板中的 url_for

<a href="{{ url_for('index') }}">首页</a>
<a href="{{ url_for('user', uid=42) }}">用户</a>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
💡 提示

模板自动转义变量(防 XSS);确需输出 HTML 用 |safe(仅限可信内容)。模板继承避免重复 HTML。

Flask 静态文件

掌握 Flask 静态文件:CSS、JS、图片的托管。

静态文件目录

static/
├── css/
│   └── style.css
├── js/
│   └── app.js
└── images/
    └── logo.png

模板中引用

<link rel="stylesheet"
      href="{{ url_for('static', filename='css/style.css') }}">

<script src="{{ url_for('static', filename='js/app.js') }}"></script>

<img src="{{ url_for('static', filename='images/logo.png') }}">

自定义静态目录

app = Flask(__name__,
            static_folder="assets",      # 目录名
            static_url_path="/assets")   # URL 前缀

# 访问 /assets/css/style.css

文件下载

from flask import send_file, send_from_directory

@app.route("/download/<filename>")
def download(filename):
    # 从目录发送文件(安全)
    return send_from_directory("uploads", filename, as_attachment=True)

@app.route("/download2")
def download2():
    return send_file("static/files/report.pdf", as_attachment=True)

上传文件

import os
from flask import request

UPLOAD_FOLDER = "uploads"
app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

@app.post("/upload")
def upload():
    file = request.files.get("file")
    if not file:
        return {"error": "未上传文件"}, 400

    # 安全处理文件名
    filename = secure_filename(file.filename)
    path = os.path.join(app.config["UPLOAD_FOLDER"], filename)
    file.save(path)
    return {"saved": filename, "url": f"/uploads/{filename}"}
💡 提示

上传文件名务必用 secure_filename() 清洗,防路径穿越;生产环境静态文件用 Nginx 托管更高效。

Flask Session 与 Cookie

掌握 Flask Session 与 Cookie:状态保持。

Cookie 操作

from flask import make_response, request

@app.route("/set-cookie")
def set_cookie():
    resp = make_response("设置成功")
    resp.set_cookie("theme", "dark",
                    max_age=3600,        # 1 小时
                    httponly=True,       # JS 不可读
                    path="/")
    return resp

@app.route("/get-cookie")
def get_cookie():
    theme = request.cookies.get("theme", "light")
    return f"主题:{theme}"

@app.route("/delete-cookie")
def delete_cookie():
    resp = make_response("已删除")
    resp.delete_cookie("theme")
    return resp

Session 操作

from flask import session

# 需要密钥(签名保护)
app.secret_key = "your-secret-key-here"
# 生产环境用环境变量
# app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY")

@app.route("/login")
def login():
    session["user_id"] = 1
    session["username"] = "张三"
    session.permanent = True          # 持久会话
    return "已登录"

@app.route("/me")
def me():
    username = session.get("username")
    if not username:
        return "未登录", 401
    return f"当前用户:{username}"

@app.route("/logout")
def logout():
    session.clear()
    return "已退出"

Session 原理

Session 数据默认存客户端 Cookie(签名防篡改)
- 服务器不存 Session(轻量)
- 内容经过 itsdangerous 签名
- 敏感数据不要放 Session(可被读取,只是防篡改)

生产可换 Redis 等存储(Flask-Session 扩展)

会话配置

app.config.update(
    SECRET_KEY="complex-secret",
    SESSION_COOKIE_HTTPONLY=True,   # 防 XSS 读取
    SESSION_COOKIE_SAMESITE="Lax",  # CSRF 防护
    PERMANENT_SESSION_LIFETIME=86400,  # 1 天
)
⚠️ 注意

SECRET_KEY 必须保密且足够复杂(用环境变量注入);它泄露=可以伪造任意 Session。

Flask 表单处理

掌握 Flask 表单处理:接收、校验、Flask-WTF。

接收表单数据

from flask import request

@app.route("/submit", methods=["GET", "POST"])
def submit():
    if request.method == "POST":
        # 读取表单字段
        name = request.form.get("name")
        email = request.form.get("email")
        agree = request.form.get("agree") == "on"

        # 手动校验
        if not name or len(name) < 2:
            return {"error": "姓名至少 2 个字符"}, 400

        return {"ok": True, "name": name}
    return "GET 请求"

HTML 表单模板

<form method="post">
  <input type="text" name="name" placeholder="姓名">
  <input type="email" name="email" placeholder="邮箱">
  <label><input type="checkbox" name="agree"> 同意协议</label>
  <button type="submit">提交</button>
</form>

Flask-WTF(推荐)

pip install flask-wtf
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField
from wtforms.validators import DataRequired, Email, Length

class RegisterForm(FlaskForm):
    username = StringField("用户名", validators=[
        DataRequired(), Length(min=3, max=20)])
    email = StringField("邮箱", validators=[
        DataRequired(), Email()])
    password = PasswordField("密码", validators=[
        DataRequired(), Length(min=6)])
    agree = BooleanField("同意协议", validators=[
        DataRequired()])

@app.route("/register", methods=["GET", "POST"])
def register():
    form = RegisterForm()
    if form.validate_on_submit():       # 校验通过
        username = form.username.data
        return f"注册成功:{username}"
    return render_template("register.html", form=form)
<form method="post">
  {{ form.hidden_tag() }}      <!-- CSRF 令牌 -->
  {{ form.username.label }} {{ form.username() }}
  {% for err in form.username.errors %}<p class="error">{{ err }}</p>{% endfor %}

  {{ form.password.label }} {{ form.password() }}
  {{ form.agree() }} 同意协议
  <button type="submit">注册</button>
</form>

CSRF 保护

# Flask-WTF 默认开启 CSRF 保护
# 模板中必须加 {{ form.hidden_tag() }}
# 或配置 SECRET_KEY 后使用

app.config["SECRET_KEY"] = "xxx"
app.config["WTF_CSRF_ENABLED"] = True
💡 提示

简单表单手动解析即可;复杂表单用 Flask-WTF(自动校验 + CSRF 保护),生产必开 CSRF。

Flask 数据库操作

掌握 Flask 数据库操作:SQLite/MySQL 基础与 ORM。

原生 SQL(sqlite3)

import sqlite3

# 连接
conn = sqlite3.connect("app.db")
cur = conn.cursor()

# 建表
cur.execute('''CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT UNIQUE
)''')

# 插入(参数化防注入)
cur.execute("INSERT INTO users (name, email) VALUES (?, ?)",
            ("张三", "zhang@example.com"))
conn.commit()

# 查询
cur.execute("SELECT * FROM users WHERE name = ?", ("张三",))
rows = cur.fetchall()

conn.close()

Flask-SQLAlchemy(ORM,推荐)

pip install flask-sqlalchemy
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
# SQLite
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///app.db"
# MySQL
# app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+pymysql://user:pass@localhost/db"

db = SQLAlchemy(app)

# 模型
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50), nullable=False)
    email = db.Column(db.String(100), unique=True)

    def to_dict(self):
        return {"id": self.id, "name": self.name, "email": self.email}

# 建表(首次)
with app.app_context():
    db.create_all()

CRUD 操作

@app.post("/users")
def create_user():
    data = request.get_json()
    user = User(name=data["name"], email=data["email"])
    db.session.add(user)
    db.session.commit()
    return user.to_dict(), 201

@app.get("/users")
def list_users():
    users = User.query.all()
    return [u.to_dict() for u in users]

@app.get("/users/<int:uid>")
def get_user(uid):
    user = db.session.get(User, uid)
    if not user:
        return {"error": "用户不存在"}, 404
    return user.to_dict()

@app.put("/users/<int:uid>")
def update_user(uid):
    user = db.session.get(User, uid)
    if not user:
        return {"error": "用户不存在"}, 404
    data = request.get_json()
    user.name = data.get("name", user.name)
    db.session.commit()
    return user.to_dict()

@app.delete("/users/<int:uid>", methods=["DELETE"])
def delete_user(uid):
    user = db.session.get(User, uid)
    if not user:
        return {"error": "用户不存在"}, 404
    db.session.delete(user)
    db.session.commit()
    return {"ok": True}

常用查询

User.query.all()                    # 全部
User.query.filter_by(name="张三").first()
User.query.filter(User.age > 18).all()
User.query.order_by(User.id.desc()).limit(10).all()
User.query.count()                  # 数量
User.query.get_or_404(1)            # 404 处理
💡 提示

ORM 自动参数化防注入;本站 CodeNest 数据库表结构(categories/tutorials/sections)就是 ORM 模型的典型示例。

Flask 蓝图

掌握 Flask 蓝图(Blueprint):模块化组织路由。

为什么用蓝图

  • 按功能模块拆分路由
  • 避免单文件膨胀
  • 可复用模块(插件式开发)
  • 团队并行开发

创建蓝图

# views/main.py
from flask import Blueprint, render_template

# 创建蓝图:名称 + 前缀
main_bp = Blueprint("main", __name__)

@main_bp.route("/")
def index():
    return render_template("index.html")

@main_bp.route("/about")
def about():
    return "关于页面"
# views/api.py
from flask import Blueprint, jsonify

api_bp = Blueprint("api", __name__, url_prefix="/api")

@api_bp.route("/users")
def users():
    return jsonify([{"id": 1, "name": "张三"}])

@api_bp.route("/posts/<int:pid>")
def post(pid):
    return jsonify({"id": pid})

注册蓝图

# app.py
from flask import Flask
from views.main import main_bp
from views.api import api_bp

app = Flask(__name__)

app.register_blueprint(main_bp)
app.register_blueprint(api_bp)   # 所有路由前缀 /api

# 自定义前缀
# app.register_blueprint(api_bp, url_prefix="/v1")

蓝图内使用 url_for

# 蓝图名 + 端点名
url_for("main.index")     # /
url_for("api.users")      # /api/users
url_for("api.post", pid=42)  # /api/posts/42

# 模板中
# <a href="{{ url_for('main.about') }}">关于</a>

蓝图模板与静态文件

# 蓝图可带自己的模板/静态目录
admin_bp = Blueprint("admin", __name__,
                     template_folder="templates",
                     static_folder="static")
💡 提示

蓝图命名规范:用 蓝图名_端点 区分同名函数;url_prefix 统一接口前缀(/api、/admin)。

Flask 错误处理

掌握 Flask 错误处理:统一异常与错误响应。

错误处理装饰器

from flask import jsonify

# 404
@app.errorhandler(404)
def not_found(e):
    return jsonify({"code": 404, "error": "页面不存在"}), 404

# 405 方法不允许
@app.errorhandler(405)
def method_not_allowed(e):
    return jsonify({"code": 405, "error": "方法不允许"}), 405

# 500 服务器错误
@app.errorhandler(500)
def server_error(e):
    # 记录日志
    app.logger.error(f"服务器错误: {e}")
    return jsonify({"code": 500, "error": "服务器内部错误"}), 500

自定义业务异常

class BizError(Exception):
    def __init__(self, code, message, status_code=400):
        self.code = code
        self.message = message
        self.status_code = status_code

@app.errorhandler(BizError)
def handle_biz_error(e):
    return jsonify({
        "code": e.code,
        "message": e.message
    }), e.status_code

# 使用
@app.route("/api/pay")
def pay():
    balance = 10
    amount = 100
    if balance < amount:
        raise BizError(4001, "余额不足", 400)
    return {"ok": True}

统一响应格式

# 封装统一响应
def ok(data=None, message="ok"):
    return jsonify({"code": 0, "message": message, "data": data})

def fail(code, message, status_code=400):
    return jsonify({"code": code, "message": message, "data": None}), status_code

@app.get("/api/users")
def users():
    return ok([{"id": 1}])

@app.get("/api/nothing")
def nothing():
    return fail(4040, "资源不存在", 404)

全局异常兜底

import traceback

@app.errorhandler(Exception)
def handle_exception(e):
    # 完整堆栈进日志(不暴露给客户端)
    traceback.print_exc()
    return jsonify({"code": 500, "message": "服务器错误"}), 500
💡 提示

错误处理三原则:不向客户端泄露堆栈、统一错误格式、日志记录完整信息。

Flask 中间件和扩展

掌握 Flask 中间件和扩展:请求拦截与扩展使用。

before_request / after_request

from flask import request, g
import time

# 每个请求前
@app.before_request
def before_request():
    g.start = time.time()
    # 简单鉴权(排除公开路径)
    public = {"/", "/login", "/static"}
    if request.path not in public:
        token = request.headers.get("Authorization", "")
        if not token.startswith("Bearer "):
            return {"error": "未认证"}, 401

# 每个请求后
@app.after_request
def after_request(response):
    # 计算耗时
    if hasattr(g, "start"):
        elapsed = time.time() - g.start
        response.headers["X-Process-Time"] = f"{elapsed:.3f}s"
    return response

# 异常时(替代 after)
@app.teardown_request
def teardown(exception):
    # 清理资源(数据库关闭等)
    pass

常用扩展

扩展用途
Flask-SQLAlchemyORM
Flask-Migrate数据库迁移
Flask-Login用户认证
Flask-WTF表单 + CSRF
Flask-CORS跨域
Flask-Caching缓存
Flask-Limiter限流

Flask-CORS 示例

from flask_cors import CORS

app = Flask(__name__)
CORS(app)   # 全开放(开发)

# 生产限制来源
CORS(app, resources={
    r"/api/*": {"origins": ["https://www.example.com"]}
})

Flask-Limiter 限流

from flask_limiter import Limiter

limiter = Limiter(app, default_limits=["200 per day"])

@app.route("/api/login")
@limiter.limit("5 per minute")   # 登录接口限流
def login():
    return {"ok": True}
💡 提示

before_request 做全局鉴权/日志;扩展按需安装(Flask 哲学:核心精简,扩展即插即用)。

Flask 部署

掌握 Flask 部署:生产环境上线。

生产部署架构

浏览器 → Nginx(静态+反代)→ Gunicorn(WSGI)→ Flask 应用
                                    ↓
                               MySQL/Redis

安装 Gunicorn

pip install gunicorn

# 启动(4 进程)
gunicorn -w 4 -b 127.0.0.1:8000 app:app

# 后台运行 + 日志
gunicorn -w 4 -b 127.0.0.1:8000 app:app   --daemon --log-file gunicorn.log

生产配置要点

# 关闭调试!
app.config.update(
    DEBUG=False,
    SECRET_KEY=os.environ.get("SECRET_KEY"),
    # 生产数据库
    SQLALCHEMY_DATABASE_URI=os.environ.get("DB_URL"),
)

Nginx 配置

server {
    listen 80;
    server_name www.laowang.click;

    # 静态文件
    location /static/ {
        alias /www/wwwroot/app/static/;
        expires 7d;
    }

    # 动态请求
    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

systemd 服务

# /etc/systemd/system/flaskapp.service
[Unit]
Description=Flask App
After=network.target

[Service]
WorkingDirectory=/www/wwwroot/app
ExecStart=/www/wwwroot/app/venv/bin/gunicorn -w 4 -b 127.0.0.1:8000 app:app
Restart=always
EnvironmentFile=/www/wwwroot/app/.env

[Install]
WantedBy=multi-user.target

宝塔面板部署

  1. 创建 Python 项目,上传代码
  2. 安装依赖(pip install -r requirements.txt)
  3. 启动命令设为 gunicorn
  4. 外网映射(自动反代)
  5. 申请 SSL 证书

上线检查

curl http://127.0.0.1:8000/health    # 健康检查
curl -I https://www.laowang.click/    # 状态码 200
💡 提示

生产三件套:Gunicorn(多进程)+ Nginx(反代静态)+ 环境变量配置。DEBUG 必须 False。

Flask 配置管理

掌握 Flask 配置管理:配置文件、环境变量、加载顺序。

配置方式

# 方式一:直接赋值
app.config["DEBUG"] = True
app.config["SECRET_KEY"] = "xxx"

# 方式二:update 批量
app.config.update(
    DEBUG=True,
    SECRET_KEY="xxx",
)

# 方式三:对象加载(推荐)
class Config:
    SECRET_KEY = "xxx"
    SQLALCHEMY_DATABASE_URI = "sqlite:///app.db"

app.config.from_object(Config)

# 方式四:文件加载
app.config.from_pyfile("config.py")
app.config.from_json("config.json")

多环境配置

# config.py
import os

class BaseConfig:
    SECRET_KEY = os.environ.get("SECRET_KEY", "dev-secret")
    SQLALCHEMY_TRACK_MODIFICATIONS = False

class DevConfig(BaseConfig):
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = "sqlite:///dev.db"

class ProdConfig(BaseConfig):
    DEBUG = False
    SQLALCHEMY_DATABASE_URI = os.environ.get("DB_URL", "")

config = {
    "dev": DevConfig,
    "prod": ProdConfig,
    "default": DevConfig,
}
# app.py 工厂中
def create_app(env="default"):
    app = Flask(__name__)
    app.config.from_object(config[env])
    return app

# 运行
app = create_app(os.environ.get("FLASK_ENV", "default"))

.env 文件(python-dotenv)

pip install python-dotenv
# .env(不入版本库!)
SECRET_KEY=complex-secret-value
DB_URL=mysql+pymysql://user:pass@localhost/app
DEBUG=False
from dotenv import load_dotenv
load_dotenv()   # 加载 .env

import os
secret = os.environ.get("SECRET_KEY")

读取配置

# 视图中读取
from flask import current_app

current_app.config["SECRET_KEY"]
current_app.config.get("DEBUG")

# 模板中
# {{ config['DEBUG'] }}
💡 提示

配置管理铁律:密钥等敏感信息放环境变量/.env,绝不写死在代码;开发/生产配置分离。

Flask 数据库集成

掌握 Flask 数据库集成:SQLAlchemy 完整实践。

安装与初始化

pip install flask-sqlalchemy
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///app.db"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

db = SQLAlchemy(app)

定义模型

class Category(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50), unique=True, nullable=False)
    sort_order = db.Column(db.Integer, default=0)

    # 一对多关系
    tutorials = db.relationship("Tutorial", backref="category",
                                lazy="dynamic")

class Tutorial(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    cat_id = db.Column(db.Integer,
                       db.ForeignKey("category.id"), nullable=False)
    title = db.Column(db.String(100), nullable=False)
    views = db.Column(db.Integer, default=0)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    def to_dict(self):
        return {
            "id": self.id,
            "title": self.title,
            "views": self.views,
            "created_at": self.created_at.isoformat() if self.created_at else None,
        }

# 建表
with app.app_context():
    db.create_all()

关系查询

# 一对一/多对一
tutorial = Tutorial.query.first()
tutorial.category.name      # 正向

category = Category.query.first()
category.tutorials.all()    # 反向(backref)

# 跨表过滤
Tutorial.query.filter(Tutorial.category.has(name="前端")).all()

# 多对多
# tags = db.Table(...) 关联表
# Tag.tutorials = db.relationship(secondary=tags)

查询进阶

from sqlalchemy import func, or_, desc

# 聚合
db.session.query(Category.id,
                 func.count(Tutorial.id)).outerjoin(Tutorial)    .group_by(Category.id).all()

# 条件组合
Tutorial.query.filter(
    or_(Tutorial.title.contains("Python"),
        Tutorial.title.contains("Flask"))
).order_by(desc(Tutorial.views)).all()

# 分页
page = Tutorial.query.paginate(page=1, per_page=10)
page.items; page.total; page.pages; page.has_next

事务管理

try:
    db.session.add(new_tutorial)
    db.session.add(new_category)
    db.session.commit()
except Exception:
    db.session.rollback()
    raise
💡 提示

本站 CodeNest 的数据库(categories/tutorials/sections)与本示例结构几乎一致,可以对照学习。

Flask 测试

掌握 Flask 测试:单元测试与接口测试。

安装 pytest

pip install pytest

测试客户端

# test_app.py
import pytest
from app import create_app

@pytest.fixture
def client():
    app = create_app("test")
    app.config["TESTING"] = True
    with app.test_client() as client:
        yield client

def test_index(client):
    resp = client.get("/")
    assert resp.status_code == 200
    assert b"CodeNest" in resp.data

def test_user_api(client):
    resp = client.get("/api/users")
    assert resp.status_code == 200
    data = resp.get_json()
    assert isinstance(data, list)

def test_create_user(client):
    resp = client.post("/api/users", json={"name": "张三"})
    assert resp.status_code == 201
    assert resp.get_json()["name"] == "张三"

def test_not_found(client):
    resp = client.get("/nonexistent")
    assert resp.status_code == 404

测试数据库

@pytest.fixture
def client():
    app = create_app("test")
    app.config.update(
        TESTING=True,
        SQLALCHEMY_DATABASE_URI="sqlite:///:memory:",
    )
    with app.app_context():
        db.create_all()
        # 插入测试数据
        db.session.add(User(name="张三"))
        db.session.commit()

    with app.test_client() as client:
        yield client

    with app.app_context():
        db.drop_all()

测试登录认证

def test_require_auth(client):
    resp = client.get("/api/me")
    assert resp.status_code == 401

def test_login(client):
    resp = client.post("/api/login", json={
        "username": "admin", "password": "secret"})
    assert resp.status_code == 200
    token = resp.get_json()["token"]

    resp = client.get("/api/me",
        headers={"Authorization": f"Bearer {token}"})
    assert resp.status_code == 200

运行测试

pytest -v              # 详细输出
pytest -k "user"       # 只跑含 user 的
pytest --cov           # 覆盖率(需 pytest-cov)
💡 提示

测试三覆盖:正常流程、错误分支、边界条件。接入 CI(GitHub Actions)每次提交自动跑。

Flask 请求生命周期

掌握 Flask 请求生命周期:请求从进入到返回的完整流程。

生命周期全景

1. 请求到达 WSGI 服务器(Gunicorn)
2. Flask 创建 Request 对象
3. 推送请求上下文(request/session 可访问)
4. before_request 钩子(可提前返回)
5. 路由匹配 → 视图函数
6. 视图处理 → 返回值
7. after_request 钩子(处理响应)
8. 序列化响应返回客户端
9. teardown_request(清理资源)
10. 弹出请求上下文

各阶段示例

from flask import Flask, request, g
import time

app = Flask(__name__)

# 1-2. 请求进入
@app.before_request
def before_request():
    g.start_time = time.time()
    print(f"开始处理: {request.method} {request.path}")

# 3. 视图
@app.route("/api/data")
def data():
    return {"message": "hello"}

# 4. 响应生成
@app.after_request
def after_request(response):
    elapsed = time.time() - g.start_time
    response.headers["X-Time"] = f"{elapsed:.3f}s"
    print(f"完成处理: {response.status_code} ({elapsed:.3f}s)")
    return response

# 5. 清理
@app.teardown_request
def teardown(exception):
    # 关闭数据库连接等
    print("请求结束,清理资源")

上下文对象

# 请求上下文(每个请求独立)
request       # 请求数据
session       # 会话

# 应用上下文(整个应用共享)
current_app   # 当前应用
g             # 请求内全局(跨函数共享)

# g 的使用:在 before_request 存,视图取
@app.before_request
def load_user():
    g.user = User.query.first()

@app.route("/me")
def me():
    return {"user": g.user.name}

手动推送上下文

# 在请求外访问 current_app/db
from app import create_app, db

app = create_app()
with app.app_context():
    # 此处可访问 db 等
    users = User.query.all()
💡 提示

理解生命周期就能掌控全局逻辑:before 做鉴权/日志,after 做统计/加头,teardown 做清理。

Flask 应用对象 API

掌握 Flask 应用对象 API:app 的配置与常用方法。

创建应用

from flask import Flask

# 基本创建
app = Flask(__name__)

# 自定义模板/静态目录
app = Flask(__name__,
            template_folder="templates",
            static_folder="static",
            static_url_path="/static")

# 配置
app.config["DEBUG"] = True
app.secret_key = "secret"
app.name              # 应用名
app.root_path         # 应用根目录
app.url_map           # 路由映射表

路由方法

# 注册路由
app.route("/")(view_func)
app.add_url_rule("/path", "endpoint_name", view_func,
                 methods=["GET", "POST"])

# 查看所有路由
for rule in app.url_map.iter_rules():
    print(rule.rule, rule.endpoint, rule.methods)

请求钩子

app.before_request(fn)      # 每个请求前
app.before_first_request(fn)  # 首次请求前(Flask 2.2 移除)
app.after_request(fn)       # 每个响应后
app.teardown_request(fn)    # 请求结束清理
app.teardown_appcontext(fn) # 应用上下文结束

# 作为装饰器
@app.before_request
def hook():
    pass

错误处理

@app.errorhandler(404)
def handle_404(e):
    return "自定义 404", 404

@app.errorhandler(ValueError)
def handle_value(e):
    return "参数错误", 400

上下文管理

with app.app_context():
    # 访问 current_app、db
    pass

with app.test_request_context("/api/data?page=2"):
    # 模拟请求上下文
    print(request.args.get("page"))
💡 提示

app 对象是 Flask 的中心:路由、配置、钩子、错误处理都挂在它上面。工厂函数(create_app)是大型项目的标准组织方式。

Flask 蓝图对象 API

掌握 Flask 蓝图对象 API:Blueprint 的完整用法。

创建蓝图

from flask import Blueprint

# 基本
bp = Blueprint("name", __name__)

# 常用参数
bp = Blueprint(
    "api",
    __name__,
    url_prefix="/api",          # URL 前缀
    template_folder="templates",# 蓝图模板目录
    static_folder="static",     # 蓝图静态目录
)

蓝图常用属性/方法

bp.name                # 蓝图名
bp.url_prefix          # 前缀
bp.route("/path")      # 添加路由
bp.errorhandler(404)   # 蓝图内错误处理
bp.before_request      # 蓝图内请求钩子
bp.app_context_processor  # 模板全局变量

蓝图内钩子

api_bp = Blueprint("api", __name__, url_prefix="/api")

@api_bp.before_request
def before_api():
    # 只对 /api 下的请求生效
    token = request.headers.get("Authorization")
    if not token:
        return {"error": "未认证"}, 401

@api_bp.route("/data")
def data():
    return {"ok": True}

蓝图错误处理

@api_bp.errorhandler(404)
def api_404(e):
    return {"error": "API 不存在"}, 404

# 优先级:蓝图内 > 应用级

嵌套蓝图

# Flask 3.0+ 支持嵌套蓝图
admin_bp = Blueprint("admin", __name__, url_prefix="/admin")
users_bp = Blueprint("users", __name__, url_prefix="/users")
admin_bp.register_blueprint(users_bp)

app.register_blueprint(admin_bp)
# 完整路径:/admin/users/...

蓝图复用(可插拔)

# 同一个蓝图注册两次(不同前缀)
app.register_blueprint(api_bp, url_prefix="/v1")
app.register_blueprint(api_bp, url_prefix="/v2")   # 需改名

# 蓝图带子域名
# bp = Blueprint("shop", __name__, subdomain="shop")
💡 提示

蓝图让模块"即插即用":写一次,可在多个应用/多个前缀下复用。大型 Flask 项目的组织基石。

Flask Request API

掌握 Flask Request API:request 对象的全部用法。

请求数据访问

from flask import request

# 基础信息
request.method           # GET/POST/PUT/DELETE
request.path             # /api/users
request.full_path        # /api/users?page=2
request.url              # 完整 URL
request.base_url         # 不含查询串
request.host             # localhost:5000
request.remote_addr      # 客户端 IP
request.user_agent       # User-Agent
request.is_json          # 是否 JSON 请求

# 查询参数(GET)
request.args.get("page", default=1, type=int)
request.args.getlist("tag")     # 多值 ?tag=a&tag=b
request.args.to_dict()          # 全部转字典

# 表单(POST)
request.form.get("username")
request.form.getlist("tags")

# JSON
data = request.get_json(silent=True)   # 解析失败返回 None
data = request.json                    # 简写

# 请求头
request.headers.get("Authorization")
request.headers.get("User-Agent")

# Cookie
request.cookies.get("theme")

# 文件
request.files.get("file")

# 原始数据
request.data              # 字节
request.get_data()        # 同上

完整示例

@app.route("/api/users", methods=["POST"])
def create_user():
    # 支持 JSON 或表单
    if request.is_json:
        data = request.get_json()
    else:
        data = request.form

    name = data.get("name")
    if not name:
        return {"error": "缺少 name"}, 400

    page = request.args.get("page", 1, type=int)
    user_agent = request.headers.get("User-Agent")
    return {
        "created": name,
        "page": page,
        "ua": user_agent[:50],
    }, 201
💡 提示

request 是只读的全局对象(请求上下文内可用);参数解析给默认值和类型(type=int)可避免类型错误。

Flask Response API

掌握 Flask Response API:响应对象与返回方式。

返回方式汇总

from flask import Response, jsonify, redirect, make_response

# 1. 字符串
return "hello"

# 2. 字典(自动 JSON)
return {"ok": True, "data": [1, 2]}

# 3. 元组(内容, 状态码)
return "Not Found", 404
return {"error": "x"}, 400

# 4. 元组(内容, 状态码, 响应头)
return "内容", 200, {"X-Custom": "v"}

# 5. jsonify
return jsonify(ok=True, data=[1, 2])

# 6. 重定向
return redirect("/new-page")

# 7. Response 对象(最灵活)
resp = Response("内容", status=200, mimetype="text/plain")
return resp

make_response

# 任何返回值 → Response 对象
resp = make_response("内容")
resp.status_code = 404
resp.headers["X-Server"] = "Flask"
resp.set_cookie("k", "v", max_age=3600)
return resp

# 模板转 Response
resp = make_response(render_template("index.html"))
resp.headers["X-Page"] = "index"
return resp

Response 属性与方法

resp.status          # "200 OK"
resp.status_code     # 200
resp.headers         # Headers 对象
resp.data            # 字节内容
resp.mimetype        # "text/html"
resp.content_type    # "text/html; charset=utf-8"
resp.cookies         # Cookie 操作

resp.set_cookie("name", "value", max_age=3600,
                httponly=True, secure=True, samesite="Lax")
resp.delete_cookie("name")

自定义响应类

from flask import Response

class ApiResponse(Response):
    default_mimetype = "application/json"

app.response_class = ApiResponse

# 或统一包装
def ok(data=None):
    return jsonify({"code": 0, "data": data})
💡 提示

返回值三要素:内容、状态码、响应头。需要操作 Cookie/头时用 make_response 包装。

Flask Session API

掌握 Flask Session API:会话管理的核心用法。

Session 基础

from flask import session

# 需要 SECRET_KEY
app.secret_key = "your-secret"

# 写入
session["user_id"] = 1
session["username"] = "张三"

# 读取
user_id = session.get("user_id")
username = session.get("username", "游客")

# 判断存在
if "user_id" in session:
    pass

# 删除
session.pop("username", None)
del session["user_id"]    # 不存在会 KeyError

# 清空
session.clear()

会话配置

app.config.update(
    SECRET_KEY="complex-secret",
    SESSION_COOKIE_NAME="session",       # Cookie 名
    SESSION_COOKIE_HTTPONLY=True,        # 防 XSS
    SESSION_COOKIE_SECURE=False,         # HTTPS 时开 True
    SESSION_COOKIE_SAMESITE="Lax",       # CSRF 防护
    PERMANENT_SESSION_LIFETIME=86400,    # 1 天
)

# 持久会话(关闭浏览器不丢)
session.permanent = True

完整登录示例

@app.route("/login", methods=["POST"])
def login():
    data = request.get_json()
    user = User.query.filter_by(
        username=data.get("username")).first()

    if not user or not check_password(user, data.get("password")):
        return {"error": "用户名或密码错误"}, 401

    session["user_id"] = user.id
    session["username"] = user.username
    session.permanent = True
    return {"ok": True}

@app.route("/logout")
def logout():
    session.clear()
    return {"ok": True}

@app.route("/me")
def me():
    uid = session.get("user_id")
    if not uid:
        return {"error": "未登录"}, 401
    return {"user_id": uid, "username": session.get("username")}

装饰器保护

from functools import wraps

def login_required(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        if not session.get("user_id"):
            return {"error": "请先登录"}, 401
        return f(*args, **kwargs)
    return wrapper

@app.get("/profile")
@login_required
def profile():
    return {"ok": True}
⚠️ 注意

默认 Session 存 Cookie(签名防篡改但不加密),别存敏感数据;生产建议 Flask-Session 换 Redis 存储。

Flask Session Interface API

掌握 Flask Session Interface API:自定义会话存储。

默认 Session 接口

# Flask 默认使用 SecureCookieSessionInterface
# Session 数据签名后存客户端 Cookie

from flask.sessions import SecureCookieSessionInterface

app.session_interface = SecureCookieSessionInterface()

自定义存储(Redis)

# 使用 Flask-Session 扩展(推荐)
from flask_session import Session

app.config.update(
    SECRET_KEY="secret",
    SESSION_TYPE="redis",          # redis 存储
    SESSION_REDIS=redis_client,    # redis 连接
    SESSION_PERMANENT=False,
    SESSION_USE_SIGNER=True,       # 签名
)
Session(app)

自定义 Session Interface

from flask.sessions import SessionInterface, SessionMixin
from flask import Flask, session

class MemorySession(dict, SessionMixin):
    pass

class MemorySessionInterface(SessionInterface):
    # 内存版 Session(演示)
    sessions = {}

    def open_session(self, app, request):
        sid = request.cookies.get("sid")
        if sid and sid in self.sessions:
            return MemorySession(self.sessions[sid])
        return MemorySession()

    def save_session(self, app, session, response):
        sid = request.cookies.get("sid", "default")
        self.sessions[sid] = dict(session)
        response.set_cookie("sid", sid)

app.session_interface = MemorySessionInterface()

接口方法

# SessionInterface 核心方法
class MyInterface(SessionInterface):
    def open_session(self, app, request):
        # 从请求恢复会话
        pass

    def save_session(self, app, session, response):
        # 把会话保存到响应
        pass

    # 可选
    def is_null_session(self, obj):
        return False

    def get_cookie_name(self, app):
        return app.config["SESSION_COOKIE_NAME"]
💡 提示

日常开发用 Flask-Session(Redis/文件存储)即可;自定义 Interface 适合特殊存储需求(如对接已有系统)。

Flask Test Client API

掌握 Flask Test Client API:接口测试客户端。

创建测试客户端

import pytest
from app import create_app

@pytest.fixture
def client():
    app = create_app("test")
    app.config["TESTING"] = True
    return app.test_client()

常用方法

# GET
resp = client.get("/api/users")
resp = client.get("/api/users?page=2&size=10")

# POST(JSON)
resp = client.post("/api/users",
                   json={"name": "张三"})
# POST(表单)
resp = client.post("/login",
                   data={"username": "a", "password": "b"})

# PUT / DELETE
resp = client.put("/api/users/1", json={"name": "李四"})
resp = client.delete("/api/users/1")

# 带请求头
resp = client.get("/api/me",
                  headers={"Authorization": "Bearer token"})

# 带 Cookie
client.set_cookie("session", "value")
resp = client.get("/me")

响应断言

def test_api(client):
    resp = client.get("/api/users")

    resp.status_code            # 200
    resp.status                # "200 OK"
    resp.headers["Content-Type"]
    resp.data                  # 字节
    resp.get_json()            # 解析 JSON
    resp.get_json()["data"]

    # 文本内容
    assert b"hello" in resp.data
    assert resp.get_data(as_text=True).startswith("

会话保持

# 登录后测试受保护接口
def test_profile(client):
    # 先登录(Session 自动保持)
    client.post("/login", json={"username": "a", "password": "b"})

    resp = client.get("/profile")
    assert resp.status_code == 200

完整测试示例

def test_user_flow(client):
    # 创建
    resp = client.post("/api/users", json={"name": "张三"})
    assert resp.status_code == 201
    uid = resp.get_json()["id"]

    # 查询
    resp = client.get(f"/api/users/{uid}")
    assert resp.get_json()["name"] == "张三"

    # 更新
    resp = client.put(f"/api/users/{uid}", json={"name": "李四"})
    assert resp.get_json()["name"] == "李四"

    # 删除
    resp = client.delete(f"/api/users/{uid}")
    assert resp.status_code == 200

    # 404
    resp = client.get(f"/api/users/{uid}")
    assert resp.status_code == 404
💡 提示

Test Client 无需启动服务器即可模拟请求,是接口测试的标准工具;配合 pytest 断言状态码/数据。

Flask Test CLI Runner API

掌握 Flask Test CLI Runner API:命令行测试。

flask 命令入口

# 设置应用
export FLASK_APP=app.py
flask --help           # 查看所有命令

# 常用命令
flask run              # 启动开发服务器
flask routes           # 列出所有路由
flask shell            # 交互式 shell(带上下文)

自定义 CLI 命令

import click
from flask import Flask

app = Flask(__name__)

@app.cli.command("init-db")
def init_db_command():
    # 初始化数据库
    db.create_all()
    click.echo("数据库已初始化")

@app.cli.command("create-admin")
@click.argument("username")
@click.option("--password", prompt=True, hide_input=True)
def create_admin(username, password):
    # 创建管理员
    admin = User(username=username, password=password, is_admin=True)
    db.session.add(admin)
    db.session.commit()
    click.echo(f"管理员 {username} 已创建")
flask init-db
flask create-admin admin_user
flask create-admin --password 123456 bob

测试 CLI 命令

from click.testing import CliRunner

def test_cli():
    runner = CliRunner()
    result = runner.invoke(app.cli, ["init-db"])
    assert result.exit_code == 0
    assert "初始化" in result.output

cli 注册方式

# 方式一:装饰器(上面示例)
# 方式二:单独函数
import click

@click.command("init-db")
def init_db():
    pass

app.cli.add_command(init_db)

# 方式三:分组
@app.cli.group()
def db():
    # 数据库命令组
    pass

@db.command("init")
def db_init():
    click.echo("db init")

# flask db init
💡 提示

把"初始化、建管理员、数据清理"等运维操作做成 CLI 命令,比手动执行代码可靠得多。

Flask Application Globals

掌握 Flask Application Globals:g 与 current_app。

g 对象(请求级全局)

from flask import g

# g 在同一个请求内跨函数共享
@app.before_request
def load_user():
    # 每次请求前加载用户
    g.user = User.query.first()

@app.route("/me")
def me():
    # 视图内直接使用
    return {"user": g.user.name if g.user else None}

# 多函数共享
def get_current_user():
    # 缓存到 g,避免重复查询
    if "user" not in g:
        g.user = User.query.first()
    return g.user

g 的应用场景

# 1. 数据库连接
@app.before_request
def open_db():
    g.db = connect_db()

@app.teardown_request
def close_db(exc):
    g.db.close()

# 2. 请求日志/耗时
@app.before_request
def start_timer():
    g.start = time.time()

# 3. 配置缓存
def get_settings():
    if "settings" not in g:
        g.settings = load_settings()
    return g.settings

current_app

from flask import current_app

# 在请求/应用上下文中访问应用配置
current_app.config["SECRET_KEY"]
current_app.name          # 应用名
current_app.logger        # 应用日志

# 在扩展初始化中常用
def init_db(app):
    with app.app_context():
        db.init_app(app)

g 的注意事项

  • g 只在一个请求内有效,请求结束清空
  • 不能跨请求/跨线程使用
  • 适合缓存请求内临时数据
  • 不要在 g 存大对象
💡 提示

g = "请求内的全局变量";current_app = "应用全局"。两者配合 before/teardown 钩子实现连接管理等横切逻辑。

Flask 实用函数与类

掌握 Flask 实用函数与类:常用内置工具。

URL 工具

from flask import url_for, request

# 反向生成 URL
url_for("index")
url_for("user", uid=42)
url_for("static", filename="css/style.css")
url_for("api.users", page=2)    # /api/users?page=2

# 当前 URL
request.url
request.base_url
request.host_url

安全工具

from werkzeug.security import generate_password_hash, check_password_hash

# 密码哈希(绝不存明文)
hashed = generate_password_hash("mypassword")
check_password_hash(hashed, "mypassword")   # True

from werkzeug.utils import secure_filename

# 安全文件名(防路径穿越)
secure_filename("../../etc/passwd")   # "etc_passwd"

JSON 工具

from flask import jsonify
import json

# Flask 的 jsonify(带 MIME)
jsonify({"ok": True})

# 标准 json
json.dumps({"a": 1}, ensure_ascii=False)
json.loads('{"a": 1}')

# 序列化对象
json.dumps(obj, default=str)   # 处理 datetime 等

请求/响应工具

from flask import abort, redirect, make_response

abort(404)                        # 终止并返回错误
abort(403, "无权访问")
redirect("/")                     # 重定向
make_response("内容")             # 构造响应

from flask import send_file, send_from_directory
send_file("file.pdf", as_attachment=True)
send_from_directory("uploads", "a.pdf")

模板全局工具

# 模板可用的内置对象
config        # 应用配置
request       # 请求
session       # 会话
g             # 全局
url_for()     # URL 生成
get_flashed_messages()  # 消息
💡 提示

密码哈希用 Werkzeug 内置(无需装 bcrypt);secure_filename 是上传文件的安全必备。

Flask Message Flashing API

掌握 Flask Message Flashing API:闪现消息。

基本用法

from flask import flash, redirect, render_template

# 视图里发送消息
@app.route("/submit", methods=["POST"])
def submit():
    # 业务处理...
    flash("保存成功!", "success")    # 消息 + 类别
    flash("记得备份", "warning")
    return redirect("/")

# 一次性:下次请求读取后消失

模板中显示

<!-- base.html -->
{% with messages = get_flashed_messages(with_categories=true) %}
  {% if messages %}
    <div class="messages">
      {% for category, message in messages %}
        <div class="alert alert-{{ category }}">
          {{ message }}
        </div>
      {% endfor %}
    </div>
  {% endif %}
{% endwith %}

按类别过滤

# 只取某类别
get_flashed_messages(category_filter=["error"])

# 模板中
{% for msg in get_flashed_messages(category_filter=["error"]) %}
  <p class="error">{{ msg }}</p>
{% endfor %}

API 场景替代

# 前后端分离项目,flash 不太适用
# 直接返回结构化信息
return jsonify({
    "success": True,
    "message": "保存成功",
    "code": 0,
}), 200

# flash 适合服务端渲染(传统 Web)场景

完整示例

@app.route("/register", methods=["POST"])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        user = User(username=form.username.data)
        db.session.add(user)
        db.session.commit()
        flash(f"欢迎加入,{user.username}!", "success")
        return redirect(url_for("index"))

    flash("表单校验失败,请检查", "error")
    return render_template("register.html", form=form)
💡 提示

flash 适合"PRG 模式"(Post-Redirect-Get):提交后重定向,消息在目标页显示一次。API 项目用 JSON 返回消息。

Flask JSON

掌握 Flask JSON:JSON 的序列化与解析。

返回 JSON

from flask import jsonify

# 字典自动转 JSON
@app.route("/api/data")
def data():
    return jsonify({
        "name": "CodeNest",
        "version": 1.0,
        "tags": ["教程", "编程"],
        "active": True
    })

# 数组
return jsonify([1, 2, 3])

# 指定状态码
return jsonify({"error": "未找到"}), 404

序列化对象

# 模型转字典
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))
    created_at = db.Column(db.DateTime)

    def to_dict(self):
        return {
            "id": self.id,
            "name": self.name,
            "created_at": self.created_at.isoformat() if self.created_at else None,
        }

# 列表
users = User.query.all()
return jsonify([u.to_dict() for u in users])

解析 JSON 请求

from flask import request

@app.post("/api/users")
def create():
    data = request.get_json()          # 解析失败返回 None
    if data is None:
        return {"error": "无效的 JSON"}, 400

    # silent=True:解析失败不抛异常
    data = request.get_json(silent=True)

    # force=True:非 JSON Content-Type 也尝试解析
    data = request.get_json(force=True)

    name = data.get("name")
    return {"created": name}, 201

JSON 编码细节

# 中文不转义
import json
json.dumps({"name": "张三"}, ensure_ascii=False)
# '{"name": "张三"}'

# 处理不可序列化对象
json.dumps({"date": datetime.now()}, default=str)

# Flask 配置
app.config["JSON_AS_ASCII"] = False    # 中文原样(Flask 2.2 前)
# Flask 3.x 默认 ensure_ascii=False

统一响应包装

def api_response(data=None, message="ok", code=0, status=200):
    return jsonify({
        "code": code,
        "message": message,
        "data": data,
    }), status

@app.get("/api/users")
def users():
    return api_response([{"id": 1}])

@app.post("/api/users")
def create_user():
    data = request.get_json()
    if not data or not data.get("name"):
        return api_response(message="参数错误", code=400, status=400)
    return api_response({"id": 1}, status=201)
💡 提示

前后端分离项目统一 JSON 响应格式(code/message/data),前端只需处理一套结构。

Flask 模板渲染 API

掌握 Flask 模板渲染 API:Jinja2 高级用法。

render_template 参数

from flask import render_template

# 基本
render_template("index.html", title="首页", users=users)

# 模板字符串(不常用)
from flask import render_template_string
render_template_string("<h1>{{ title }}</h1>", title="Hello")

模板上下文处理器

# 给所有模板注入变量
@app.context_processor
def inject_globals():
    return {
        "site_name": "CodeNest",
        "current_year": datetime.now().year,
    }

# 所有模板可直接使用 {{ site_name }}
# <p>{{ site_name }} © {{ current_year }}</p>

模板过滤器

# 自定义过滤器
@app.template_filter("money")
def money_filter(value):
    return f"¥{value:,.2f}"

# 模板中
# {{ price | money }}  → ¥1,234.56

# 注册函数到模板
@app.template_global()
def format_time(dt):
    return dt.strftime("%Y-%m-%d %H:%M")

Jinja2 高级语法

<!-- 宏(可复用片段)-->
{% macro input(name, type="text", placeholder="") %}
  <input type="{{ type }}" name="{{ name }}" placeholder="{{ placeholder }}">
{% endmacro %}

{{ input("username") }}
{{ input("password", type="password") }}

<!-- 循环高级 -->
{% for item in items %}
  {% if loop.first %}首个{% endif %}
  {{ loop.index }}. {{ item.name }}   <!-- 索引从 1 开始 -->
  {% if loop.last %}末个{% endif %}
{% endfor %}

<!-- set 定义变量 -->
{% set base_price = 100 %}
<p>{{ base_price * 0.9 }}</p>

转义与安全

<!-- 自动转义(防 XSS)-->
{{ user_input }}        <!-- <script> 会被转义 -->

<!-- 确需输出 HTML(仅可信内容)-->
{{ content | safe }}

<!-- 禁用全局自动转义(不推荐)-->
{% autoescape false %}
  {{ content }}
{% endautoescape %}
💡 提示

模板保持"薄":复杂逻辑放视图/过滤器;| safe 只用于可信 HTML;上下文处理器注入公共变量。

Flask Config API

掌握 Flask Config API:配置加载与访问。

配置属性访问

from flask import Flask, current_app

app = Flask(__name__)

# 设置
app.config["DEBUG"] = True
app.config["SECRET_KEY"] = "secret"

# 读取(在上下文中)
current_app.config["SECRET_KEY"]
current_app.config.get("NOT_EXIST", "default")

# 视图外(无上下文)
with app.app_context():
    val = current_app.config["DEBUG"]

常用配置项

app.config.update(
    DEBUG=False,                     # 调试
    SECRET_KEY="complex",            # 密钥
    JSON_AS_ASCII=False,             # JSON 中文
    SESSION_COOKIE_HTTPONLY=True,    # Session 安全
    SQLALCHEMY_DATABASE_URI="sqlite:///app.db",
    MAX_CONTENT_LENGTH=16 * 1024 * 1024,  # 上传限制 16MB
    PERMANENT_SESSION_LIFETIME=86400,
)

配置加载方式

# from_object
app.config.from_object("config.ProdConfig")

# from_pyfile
app.config.from_pyfile("config.py")

# from_envvar(环境变量指向配置文件)
app.config.from_envvar("APP_CONFIG")

# from_mapping
app.config.from_mapping(
    DEBUG=True,
    SECRET_KEY="x",
)

环境变量配置

import os

app.config.update(
    SECRET_KEY=os.environ.get("SECRET_KEY", "dev-secret"),
    DB_URL=os.environ.get("DB_URL", "sqlite:///dev.db"),
)

读取配置最佳实践

# 在扩展初始化中使用
db.init_app(app)   # 内部读取 SQLALCHEMY_DATABASE_URI

# 自定义配置封装
def get_config(key, default=None):
    return current_app.config.get(key, default)
💡 提示

配置加载顺序建议:默认值 → 配置文件 → 环境变量覆盖。密钥绝不硬编码。

Flask Stream Helpers API

掌握 Flask Stream Helpers API:流式响应。

流式响应基础

from flask import Response

@app.route("/stream")
def stream():
    def generate():
        for i in range(10):
            yield f"数据块 {i}
"

    return Response(generate(), mimetype="text/plain")

# 客户端逐步接收,而非一次性

实时日志流

import time

@app.route("/logs")
def stream_logs():
    def generate():
        while True:
            yield f"日志时间: {time.time()}
"
            time.sleep(1)
    return Response(generate(), mimetype="text/plain")

大文件下载

# 大文件分块发送(省内存)
@app.route("/download")
def download():
    def generate():
        with open("big_file.zip", "rb") as f:
            while chunk := f.read(8192):
                yield chunk
    return Response(
        generate(),
        headers={"Content-Disposition": "attachment; filename=big.zip"},
        mimetype="application/zip",
    )

SSE(Server-Sent Events)

import json

@app.route("/events")
def sse():
    def generate():
        yield "retry: 3000

"   # 3 秒重连
        for i in range(5):
            data = json.dumps({"count": i})
            yield f"data: {data}

"   # SSE 格式
            time.sleep(1)
    return Response(generate(), mimetype="text/event-stream")
// 前端接收 SSE
const es = new EventSource("/events");
es.onmessage = (e) => console.log(JSON.parse(e.data));

注意

  • 流式响应用生成器(yield)
  • 长时间流需考虑代理超时设置
  • SSE 是单向推送(服务器→客户端);双向用 WebSocket
💡 提示

流式响应适用:大文件传输、实时日志、SSE 推送。yield 生成器实现,简单高效。

Flask 内部对象 API

掌握 Flask 内部对象 API:请求/应用上下文内部机制。

本地代理(Local Proxy)

# request/session/current_app/g 都是"本地代理"
# 它们根据当前上下文自动指向正确的对象

from flask import request, current_app, g

# 使用(无需关心上下文切换)
request.method
current_app.name
g.user

上下文对象

from flask import Flask, request, session

app = Flask(__name__)

# 请求上下文:request + session
# 应用上下文:current_app + g

# 手动管理(一般用 with)
with app.test_request_context("/?page=2"):
    print(request.args.get("page"))   # 2

# 内部实现
from flask.ctx import RequestContext, AppContext

# 上下文栈(ctx stack)
# flask._app_ctx_stack
# flask._request_ctx_stack

current_app 工作原理

# current_app 是 LocalProxy
# 它从应用上下文栈取栈顶的 app

# 为什么要代理?
# - 一个进程可能有多个 Flask 应用(测试时常见)
# - 代理保证总是取"当前"的应用

# 直接使用 app 对象也可以(简单场景)
app.config["DEBUG"]

g 的生命周期

# g 绑定在应用上下文
# 应用上下文 push → g 可用
# 应用上下文 pop → g 清空

with app.app_context():
    g.x = 1
# 离开 with 后 g 不可用

# 每个请求:请求上下文 push → 视图 → pop
# 所以 g 是"请求级"的

何时需要了解内部机制

  • 调试"working outside of application context"错误
  • 编写扩展(需要访问上下文)
  • 理解测试中的 context
  • 多应用/多线程场景
💡 提示

日常开发只需知道:请求内用 request/session/g,上下文外用 current_app/db;报 "outside context" 时用 with app.app_context()。

Flask 信号 API

掌握 Flask 信号 API:事件通知机制(Blinker)。

信号基础

# Flask 内置信号(需要 blinker)
from flask.signals import (
    request_started, request_finished,
    template_rendered, got_request_exception
)

# 订阅信号
def log_request(sender, **extra):
    print("请求开始")

request_started.connect(log_request)

# 可取消订阅
request_started.disconnect(log_request)

内置信号

信号触发时机
request_started请求开始时
request_finished响应发送前
got_request_exception请求处理抛异常时
template_rendered模板渲染后
before_render_template模板渲染前
message_flashedflash 消息时
appcontext_pushed/popped应用上下文压栈/出栈

自定义信号

from blinker import Namespace

# 创建命名空间
my_signals = Namespace()

# 定义信号
user_registered = my_signals.signal("user-registered")
user_logged_in = my_signals.signal("user-logged-in")

# 发送信号
@app.route("/register", methods=["POST"])
def register():
    user = create_user(...)
    user_registered.send(app, user=user)   # 发送(携带数据)
    return {"ok": True}

订阅自定义信号

def send_welcome_email(sender, user, **extra):
    print(f"给 {user.username} 发欢迎邮件")

user_registered.connect(send_welcome_email)

# 装饰器方式
@user_registered.connect
def log_register(sender, user, **extra):
    app.logger.info(f"新用户注册: {user.username}")

信号 vs 钩子

对比项信号钩子(before_request)
解耦发送方不关心订阅者注册在应用上
灵活任意模块订阅固定生命周期
适用业务事件(注册/登录)请求前后处理
💡 提示

信号用于业务事件解耦(注册后发邮件/记日志,无需侵入注册逻辑);需安装 blinker。

Flask 类视图 API

掌握 Flask 类视图 API:用类组织视图(Flask 3.0+)。

method_view(Flask 3.0+)

from flask import request, jsonify
from flask.views import MethodView

# 类视图:按 HTTP 方法组织
class UserView(MethodView):
    def get(self):
        users = User.query.all()
        return jsonify([u.to_dict() for u in users])

    def post(self):
        data = request.get_json()
        user = User(name=data["name"])
        db.session.add(user)
        db.session.commit()
        return jsonify(user.to_dict()), 201

class UserDetailView(MethodView):
    def get(self, uid):
        user = db.session.get(User, uid)
        if not user:
            return {"error": "不存在"}, 404
        return jsonify(user.to_dict())

    def put(self, uid):
        user = db.session.get(User, uid)
        if not user:
            return {"error": "不存在"}, 404
        data = request.get_json()
        user.name = data.get("name", user.name)
        db.session.commit()
        return jsonify(user.to_dict())

    def delete(self, uid):
        user = db.session.get(User, uid)
        if not user:
            return {"error": "不存在"}, 404
        db.session.delete(user)
        db.session.commit()
        return {"ok": True}

# 注册
app.add_url_rule("/api/users", view_func=UserView.as_view("users"))
app.add_url_rule("/api/users/",
                 view_func=UserDetailView.as_view("user_detail"))

类视图装饰器

class AdminView(MethodView):
    decorators = [login_required]   # 所有方法加装饰器

    def get(self):
        return {"admin": True}

    def post(self):
        return {"created": True}

类视图 vs 函数视图

对比项类视图函数视图
方法组织get/post/put 分离if method 判断
复用继承、装饰器列表装饰器
清晰度CRUD 更清晰简单场景更直接
适用REST API、复杂视图简单页面/接口
💡 提示

RESTful CRUD 接口用 MethodView 组织更清晰;简单接口用函数视图更轻量。蓝图 + MethodView 是 API 项目标配。

Flask 定义路由 API

掌握 Flask 定义路由 API:路由注册的多种方式。

方式一:装饰器(最常用)

@app.route("/")
def index():
    return "首页"

# 指定方法
@app.route("/data", methods=["GET", "POST"])
def data():
    return {"ok": True}

# 简化写法(Flask 2.0+)
@app.get("/data")
def get_data(): ...
@app.post("/data")
def post_data(): ...
@app.put("/data")
def put_data(): ...
@app.delete("/data")
def delete_data(): ...

方式二:add_url_rule

def index():
    return "首页"

app.add_url_rule("/", "index", index)

# 带方法
app.add_url_rule("/api/users", "users", users_view,
                 methods=["GET", "POST"])

# 等价于 @app.route

方式三:蓝图注册

bp = Blueprint("api", __name__, url_prefix="/api")

@bp.route("/users")
def users():
    return {"ok": True}

app.register_blueprint(bp)

方式四:类视图

from flask.views import MethodView

class UsersView(MethodView):
    def get(self): return {"method": "get"}
    def post(self): return {"method": "post"}

app.add_url_rule("/api/users", view_func=UsersView.as_view("users"))

路由命名与 url_for

# 默认端点名 = 函数名
@app.route("/user/")
def user_detail(uid): ...

url_for("user_detail", uid=42)   # /user/42

# 自定义端点名
@app.route("/x", endpoint="my_x")
def x(): ...
url_for("my_x")

查看所有路由

flask routes
# 或
# python -c "from app import app; [print(r) for r in app.url_map]"
# Endpoint  Methods  Rule
# index    GET      /
# users    GET,POST /api/users
💡 提示

四种方式本质相同:装饰器最简单、add_url_rule 最底层、蓝图做模块化、MethodView 做 REST。

Flask 视图函数 API

掌握 Flask 视图函数 API:返回值与请求处理。

视图返回类型

from flask import jsonify, render_template, redirect, Response

# 1. 字符串(默认 text/html)
return "hello"

# 2. 字典/列表(自动 JSON)
return {"ok": True}
return [1, 2, 3]

# 3. 元组
return "内容", 201
return "内容", 200, {"X-Custom": "v"}

# 4. jsonify
return jsonify(ok=True)

# 5. 模板
return render_template("index.html")

# 6. 重定向
return redirect("/other")

# 7. Response
return Response("内容", status=200)

获取请求数据

from flask import request

@app.post("/api/users")
def create():
    # 参数优先级:args(URL) > form > json > files
    data = request.get_json() or request.form

    name = data.get("name")
    page = request.args.get("page", 1, type=int)

    # 校验
    if not name:
        return {"error": "缺少 name"}, 400

    return {"created": name, "page": page}, 201

错误返回

from flask import abort

# 快速错误
abort(404)
abort(400, "参数错误")   # 自定义描述

# 自定义错误处理
@app.errorhandler(400)
def bad_request(e):
    return jsonify({
        "error": "Bad Request",
        "detail": getattr(e, "description", "")
    }), 400

请求钩子与视图

@app.before_request
def before():
    # 视图前的统一处理
    pass

@app.after_request
def after(response):
    # 视图后的统一处理
    return response

视图最佳实践

  • 参数解析(request)与校验分离
  • 业务逻辑抽到 service 层
  • 统一返回格式(code/message/data)
  • 视图保持"薄"(薄视图,厚服务)
💡 提示

薄视图原则:视图只做"取参数 → 调服务 → 返响应",逻辑放 service/models,测试与维护都轻松。

Flask 命令行 API

掌握 Flask 命令行 API:CLI 命令与交互式 shell。

flask 命令基础

export FLASK_APP=app.py
export FLASK_ENV=development    # 开发模式(2.3 前)
# Flask 2.3+ 用 --debug
flask --app app.py run --debug

# 常用命令
flask run          # 启动开发服务器
flask routes       # 列出路由
flask shell        # 交互式 shell
flask --help       # 帮助

自定义命令

import click
from flask import Flask

app = Flask(__name__)

@app.cli.command("init-db")
def init_db():
    # 初始化数据库
    db.create_all()
    click.echo("数据库初始化完成")

@app.cli.command("seed")
@click.option("--count", default=10, help="数量")
def seed(count):
    # 填充测试数据
    for i in range(count):
        db.session.add(User(name=f"用户{i}"))
    db.session.commit()
    click.echo(f"已创建 {count} 条数据")
flask init-db
flask seed --count 20

命令分组

@app.cli.group("db")
def db_group():
    # 数据库命令组

@db_group.command("init")
def db_init():
    click.echo("db init")

@db_group.command("drop")
def db_drop():
    click.echo("db drop")

# flask db init / flask db drop

flask shell 增强

# 自动导入常用对象到 shell
@app.shell_context_processor
def make_shell_context():
    return {
        "db": db,
        "User": User,
        "app": app,
    }

# flask shell 后可直接用 db、User

CLI 测试

from click.testing import CliRunner

def test_cli():
    runner = CliRunner()
    result = runner.invoke(app.cli, ["init-db"])
    assert result.exit_code == 0
💡 提示

把运维操作(初始化、种子数据、清理)做成 CLI 命令,比手动跑脚本可靠;flask shell 适合调试。

Flask Web 应用

综合运用 Flask 知识,构建完整 Web 应用。

项目规划

功能:
1. 文章列表(分页 + 搜索)
2. 文章详情
3. 分类筛选
4. 用户注册/登录
5. 后台管理(增删改)
6. 部署上线

项目结构

blog/
├── run.py              # 入口
├── config.py           # 配置
├── app/
│   ├── __init__.py     # 工厂
│   ├── models.py       # 模型
│   ├── forms.py        # 表单
│   ├── views/
│   │   ├── __init__.py
│   │   ├── main.py     # 前台
│   │   ├── auth.py     # 认证
│   │   └── admin.py    # 后台
│   ├── templates/
│   └── static/
├── requirements.txt
└── .env

工厂函数

# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

def create_app():
    app = Flask(__name__)
    app.config.from_object("config.Config")

    db.init_app(app)

    # 注册蓝图
    from .views.main import main_bp
    from .views.auth import auth_bp
    from .views.admin import admin_bp

    app.register_blueprint(main_bp)
    app.register_blueprint(auth_bp, url_prefix="/auth")
    app.register_blueprint(admin_bp, url_prefix="/admin")

    return app

模型

# app/models.py
from . import db
from datetime import datetime

class Category(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50), unique=True)
    articles = db.relationship("Article", backref="category")

class Article(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.Text)
    category_id = db.Column(db.Integer,
                            db.ForeignKey("category.id"))
    views = db.Column(db.Integer, default=0)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

前台视图

# app/views/main.py
from flask import Blueprint, render_template
from ..models import Article

main_bp = Blueprint("main", __name__)

@main_bp.route("/")
def index():
    page = request.args.get("page", 1, type=int)
    articles = Article.query.order_by(
        Article.created_at.desc()).paginate(
            page=page, per_page=10)
    return render_template("index.html", articles=articles)

后台管理

# app/views/admin.py
from flask import Blueprint, request, redirect, url_for
from ..models import Article, db

admin_bp = Blueprint("admin", __name__)

@admin_bp.route("/articles/create", methods=["GET", "POST"])
def create():
    if request.method == "POST":
        article = Article(
            title=request.form["title"],
            content=request.form["content"],
        )
        db.session.add(article)
        db.session.commit()
        return redirect(url_for("main.index"))
    return render_template("admin/form.html")
💡 提示

完整项目 = 工厂模式 + 蓝图 + 模型 + 模板 + 表单。对照本站 CodeNest(FastAPI)的结构,思路相通。

Jinja2 模板

掌握 Jinja2 模板:Flask 内置模板引擎详解。

模板基础

<!-- 变量与过滤器 -->
<p>{{ user.name | upper }}</p>
<p>{{ content | truncate(30) }}</p>
<p>{{ price | round(2) }}</p>

<!-- 控制结构 -->
{% if user.is_admin %}
  <a href="/admin">后台</a>
{% endif %}

{% for item in items %}
  <li>{{ loop.index }}. {{ item }}</li>
{% endfor %}

继承与块

<!-- base.html -->
<html>
<head>
  <title>{% block title %}CodeNest{% endblock %}</title>
  {% block head %}{% endblock %}
</head>
<body>
  {% include "nav.html" %}
  {% block content %}{% endblock %}
  {% block footer %}{% endblock %}
</body>
</html>

自定义过滤器

@app.template_filter("timeago")
def timeago(dt):
    seconds = (datetime.now() - dt).total_seconds()
    if seconds < 60: return "刚刚"
    if seconds < 3600: return f"{int(seconds//60)} 分钟前"
    if seconds < 86400: return f"{int(seconds//3600)} 小时前"
    return f"{int(seconds//86400)} 天前"

# 模板:{{ article.created_at | timeago }}

模板测试

{% if user is defined %}已定义{% endif %}
{% if items is iterable %}可迭代{% endif %}
{% if 'a' in items %}包含{% endif %}

性能与安全

  • 默认自动转义(防 XSS)
  • 复杂逻辑放视图/过滤器,模板保持简单
  • 频繁渲染可开模板缓存(app.config["TEMPLATES_AUTO_RELOAD"])
💡 提示

Jinja2 三要素:变量 {{ }}、语句 {% %}、注释 {# #}。继承 + 宏(macro)实现模板复用。

Flask-SQLAlchemy

掌握 Flask-SQLAlchemy:Flask 与 SQLAlchemy 的集成。

初始化

pip install flask-sqlalchemy
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///blog.db"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

db = SQLAlchemy(app)

定义模型

class User(db.Model):
    __tablename__ = "users"

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(50), unique=True, nullable=False)
    email = db.Column(db.String(100), unique=True)
    password_hash = db.Column(db.String(128))
    is_admin = db.Column(db.Boolean, default=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    # 关系
    articles = db.relationship("Article", backref="author",
                               lazy="dynamic")

    def to_dict(self):
        return {
            "id": self.id,
            "username": self.username,
            "email": self.email,
            "created_at": self.created_at.isoformat() if self.created_at else None,
        }

class Article(db.Model):
    __tablename__ = "articles"

    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.Text)
    user_id = db.Column(db.Integer, db.ForeignKey("users.id"))
    category_id = db.Column(db.Integer, db.ForeignKey("categories.id"))
    views = db.Column(db.Integer, default=0)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

class Category(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50), unique=True)
    articles = db.relationship("Article", backref="category")

建表与增删改查

# 建表
with app.app_context():
    db.create_all()

# 增
user = User(username="张三", email="z@example.com")
db.session.add(user)
db.session.commit()

# 查
User.query.all()                       # 全部
User.query.get(1)                      # 按主键
User.query.filter_by(username="张三").first()
User.query.filter(User.id > 5).all()
User.query.order_by(User.created_at.desc()).limit(10).all()

# 改
user = User.query.get(1)
user.username = "李四"
db.session.commit()

# 删
db.session.delete(user)
db.session.commit()

分页

page_obj = Article.query.paginate(page=1, per_page=10)
page_obj.items      # 当前页数据
page_obj.total      # 总数
page_obj.pages      # 总页数
page_obj.has_next   # 是否有下一页
page_obj.next_num   # 下一页页码
💡 提示

Flask-SQLAlchemy 让数据库操作变成 Python 对象操作;本站 CodeNest 的数据库结构(categories/tutorials/sections)正是 ORM 模型。

视图函数

掌握 Flask 视图函数:视图的组织与模式。

视图模式

# 1. 简单视图
@app.route("/")
def index():
    return render_template("index.html")

# 2. 带参数
@app.route("/article/")
def article(aid):
    a = Article.query.get_or_404(aid)
    return render_template("detail.html", article=a)

# 3. 处理表单
@app.route("/create", methods=["GET", "POST"])
def create():
    if request.method == "POST":
        title = request.form.get("title")
        if not title:
            flash("标题必填", "error")
        else:
            db.session.add(Article(title=title))
            db.session.commit()
            flash("创建成功", "success")
            return redirect(url_for("index"))
    return render_template("create.html")

装饰器封装

from functools import wraps
from flask import redirect, url_for, session

def login_required(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        if not session.get("user_id"):
            flash("请先登录", "warning")
            return redirect(url_for("auth.login"))
        return f(*args, **kwargs)
    return wrapper

def admin_required(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        if not session.get("is_admin"):
            abort(403)
        return f(*args, **kwargs)
    return wrapper

@app.route("/dashboard")
@login_required
def dashboard():
    return render_template("dashboard.html")

@app.route("/admin")
@login_required
@admin_required
def admin():
    return render_template("admin.html")

错误视图

@app.errorhandler(404)
def not_found(e):
    return render_template("404.html"), 404

@app.errorhandler(403)
def forbidden(e):
    return render_template("403.html"), 403

视图组织建议

  • 按蓝图分模块(main/auth/admin)
  • 权限用装饰器统一管理
  • 业务逻辑抽 service 层
  • 错误页统一处理
💡 提示

视图层三件套:装饰器(权限)、flash(消息)、redirect/url_for(跳转),配合蓝图组织模块。

Flask-Migrate

掌握 Flask-Migrate:数据库迁移(版本控制表结构)。

安装与初始化

pip install flask-migrate
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///app.db"
db = SQLAlchemy(app)
migrate = Migrate(app, db)

# 模型...
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))
export FLASK_APP=app.py

# 初始化迁移目录
flask db init

# 生成迁移脚本(对比模型与数据库)
flask db migrate -m "create users table"

# 应用迁移
flask db upgrade

# 回滚一个版本
flask db downgrade

# 查看状态
flask db current
flask db history

模型变更流程

1. 修改 models.py(如加字段)
2. flask db migrate -m "add email to users"
3. 检查生成的迁移文件
4. flask db upgrade
5. 测试

迁移文件

# migrations/versions/xxx_create_users.py
def upgrade():
    op.add_column("users",
                  sa.Column("email", sa.String(100), nullable=True))

def downgrade():
    op.drop_column("users", "email")

为什么需要迁移

  • 表结构变更可追踪、可回滚
  • 团队协作同步表结构
  • 生产环境安全升级
  • 替代手动 ALTER TABLE
💡 提示

生产环境表结构变更务必用迁移:migrate 生成 → 检查 → upgrade 应用。别在生产库手动 ALTER。

关键词搜索与分页

掌握 Flask 关键词搜索与分页:内容站核心功能。

搜索实现

@app.route("/search")
def search():
    keyword = request.args.get("q", "").strip()

    if not keyword:
        return render_template("search.html", articles=[])

    # LIKE 模糊搜索
    articles = Article.query.filter(
        Article.title.contains(keyword) |
        Article.content.contains(keyword)
    ).order_by(Article.created_at.desc()).all()

    return render_template("search.html",
                           articles=articles, keyword=keyword)

分页实现

@app.route("/")
def index():
    page = request.args.get("page", 1, type=int)
    per_page = 10

    # 分页查询
    pagination = Article.query.order_by(
        Article.created_at.desc()
    ).paginate(page=page, per_page=per_page)

    return render_template("index.html", pagination=pagination)

分页模板

<!-- 分页导航 -->
{% if pagination.pages > 1 %}
<nav class="pagination">
  {% if pagination.has_prev %}
    <a href="?page={{ pagination.prev_num }}">上一页</a>
  {% endif %}

  <span>{{ pagination.page }} / {{ pagination.pages }}</span>

  {% if pagination.has_next %}
    <a href="?page={{ pagination.next_num }}">下一页</a>
  {% endif %}
</nav>
{% endif %}

搜索 + 分页组合

@app.route("/search")
def search():
    keyword = request.args.get("q", "").strip()
    page = request.args.get("page", 1, type=int)

    query = Article.query
    if keyword:
        query = query.filter(
            Article.title.contains(keyword))

    pagination = query.order_by(
        Article.created_at.desc()
    ).paginate(page=page, per_page=10)

    return render_template("search.html",
                           pagination=pagination,
                           keyword=keyword)
<!-- 分页时保留搜索词 -->
<a href="?q={{ keyword }}&page={{ pagination.next_num }}">下一页</a>

优化建议

  • 数据量大时 LIKE 慢,用全文索引(MySQL FULLTEXT)
  • 更大规模用 Elasticsearch
  • 分页加 ORDER BY(稳定顺序)
  • 关键词防注入:SQLAlchemy 自动参数化
💡 提示

搜索+分页是内容站标配:filter(条件)+ order_by(排序)+ paginate(分页)三段式。

Flask-Login

掌握 Flask-Login:用户认证扩展。

安装与初始化

pip install flask-login
from flask_login import LoginManager, UserMixin, login_user,     logout_user, login_required, current_user

login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = "auth.login"      # 未登录跳转
login_manager.login_message = "请先登录"       # 提示

# User 模型继承 UserMixin
class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(50), unique=True)
    password_hash = db.Column(db.String(128))

# 用户加载器
@login_manager.user_loader
def load_user(user_id):
    return db.session.get(User, int(user_id))

登录/登出

@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        user = User.query.filter_by(
            username=request.form["username"]).first()

        if user and check_password(user.password_hash,
                                   request.form["password"]):
            login_user(user, remember=True)   # 记住登录
            return redirect(request.args.get("next") or url_for("index"))

        flash("用户名或密码错误", "error")
    return render_template("login.html")

@app.route("/logout")
@login_required
def logout():
    logout_user()
    return redirect(url_for("index"))

保护视图

from flask_login import login_required, current_user

@app.route("/profile")
@login_required
def profile():
    return render_template("profile.html", user=current_user)

# 模板中判断
# {% if current_user.is_authenticated %}已登录{% endif %}
# {% if current_user.is_authenticated %}
#   {{ current_user.username }}
# {% else %}
#   <a href="{{ url_for('login') }}">登录</a>
# {% endif %}

权限控制

from functools import wraps

def admin_required(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        if not current_user.is_admin:
            abort(403)
        return f(*args, **kwargs)
    return wrapper

@app.route("/admin")
@login_required
@admin_required
def admin():
    return "管理后台"

其他功能

# 未认证访问 → 跳转登录页(login_view)
# remember=True → 关闭浏览器保持登录
# current_user → 模板/视图中的当前用户
# is_authenticated → 是否登录
# is_anonymous → 是否匿名
💡 提示

Flask-Login 是认证的标准方案:UserMixin 模型 + user_loader + login_user/logout_user + login_required。

表单处理

掌握 Flask 表单处理:Flask-WTF 完整实践。

安装

pip install flask-wtf

定义表单

from flask_wtf import FlaskForm
from wtforms import (StringField, PasswordField, TextAreaField,
                     BooleanField, SelectField, DateField)
from wtforms.validators import (DataRequired, Email, Length,
                                EqualTo, Optional)

class ArticleForm(FlaskForm):
    title = StringField("标题", validators=[
        DataRequired(message="标题必填"),
        Length(min=3, max=100, message="3-100 字符"),
    ])
    category = SelectField("分类", coerce=int, validators=[
        DataRequired()])
    content = TextAreaField("内容", validators=[
        DataRequired()])
    is_published = BooleanField("发布")

class RegisterForm(FlaskForm):
    username = StringField("用户名", validators=[
        DataRequired(), Length(3, 20)])
    email = StringField("邮箱", validators=[
        DataRequired(), Email()])
    password = PasswordField("密码", validators=[
        DataRequired(), Length(6, 50)])
    confirm = PasswordField("确认密码", validators=[
        EqualTo("password", message="两次密码不一致")])

视图处理

@app.route("/articles/new", methods=["GET", "POST"])
def new_article():
    form = ArticleForm()
    form.category.choices = [
        (c.id, c.name) for c in Category.query.all()]

    if form.validate_on_submit():      # POST + 校验通过
        article = Article(
            title=form.title.data,
            content=form.content.data,
            category_id=form.category.data,
            is_published=form.is_published.data,
        )
        db.session.add(article)
        db.session.commit()
        flash("文章已发布", "success")
        return redirect(url_for("index"))

    return render_template("form.html", form=form)

模板渲染

<form method="post">
  {{ form.hidden_tag() }}    <!-- CSRF 令牌(必须) -->

  <div>
    {{ form.title.label }}
    {{ form.title(class="form-control") }}
    {% for err in form.title.errors %}
      <span class="error">{{ err }}</span>
    {% endfor %}
  </div>

  <div>{{ form.category.label }} {{ form.category() }}</div>
  <div>{{ form.content.label }} {{ form.content(rows=8) }}</div>
  <div>{{ form.is_published() }} {{ form.is_published.label }}</div>

  <button type="submit">保存</button>
</form>

错误处理

@app.route("/register", methods=["GET", "POST"])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        # 处理注册...
        flash("注册成功", "success")
        return redirect(url_for("login"))

    # 校验失败:form.errors 含错误信息
    # 模板中自动显示(errors 循环)
    return render_template("register.html", form=form)

CSRF 说明

# Flask-WTF 默认开启 CSRF
# 必须配置 SECRET_KEY
# 模板必须 {{ form.hidden_tag() }}
# 或单独 token:<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">

# API 场景可关闭
# app.config["WTF_CSRF_ENABLED"] = False
💡 提示

表单三件套:FlaskForm 定义 + validators 校验 + 模板渲染。CSRF 保护默认开启,务必保留。

收藏功能与后台管理

掌握 Flask 收藏功能与后台管理:完整业务功能。

收藏模型

# models.py
class Favorite(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey("users.id"))
    article_id = db.Column(db.Integer, db.ForeignKey("articles.id"))
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    # 防止重复收藏
    __table_args__ = (
        db.UniqueConstraint("user_id", "article_id",
                            name="unique_favorite"),
    )

收藏接口

from flask_login import login_required, current_user

@app.route("/article//favorite", methods=["POST"])
@login_required
def toggle_favorite(aid):
    article = Article.query.get_or_404(aid)

    fav = Favorite.query.filter_by(
        user_id=current_user.id,
        article_id=aid).first()

    if fav:
        db.session.delete(fav)
        db.session.commit()
        return {"favorited": False}
    else:
        db.session.add(Favorite(
            user_id=current_user.id,
            article_id=aid))
        db.session.commit()
        return {"favorited": True}

@app.route("/favorites")
@login_required
def my_favorites():
    articles = Article.query.join(Favorite).filter(
        Favorite.user_id == current_user.id).all()
    return render_template("favorites.html", articles=articles)

后台管理(admin 蓝图)

# views/admin.py
from flask import Blueprint, render_template, request, redirect
from flask_login import login_required, current_user
from functools import wraps

admin_bp = Blueprint("admin", __name__, url_prefix="/admin")

def admin_required(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        if not current_user.is_admin:
            return "无权访问", 403
        return f(*args, **kwargs)
    return wrapper

@admin_bp.route("/")
@login_required
@admin_required
def dashboard():
    stats = {
        "articles": Article.query.count(),
        "users": User.query.count(),
        "favorites": Favorite.query.count(),
    }
    return render_template("admin/dashboard.html", stats=stats)

@admin_bp.route("/articles")
@login_required
@admin_required
def article_list():
    articles = Article.query.order_by(
        Article.created_at.desc()).all()
    return render_template("admin/articles.html",
                           articles=articles)

@admin_bp.route("/articles//delete", methods=["POST"])
@login_required
@admin_required
def delete_article(aid):
    article = Article.query.get_or_404(aid)
    db.session.delete(article)
    db.session.commit()
    flash("文章已删除", "success")
    return redirect(url_for("admin.article_list"))
💡 提示

收藏用唯一约束防重复;后台用 admin_required 装饰器统一鉴权;本站 CodeNest 也有类似的管理后台(admin.html)。

打包上线

掌握 Flask 打包上线:完整部署流程。

上线前检查

  • DEBUG=False
  • SECRET_KEY 从环境变量读取
  • 生产数据库配置
  • 依赖锁定(requirements.txt)
  • 数据库迁移已应用

准备部署文件

project/
├── app.py / run.py
├── config.py
├── requirements.txt
├── .env              # 生产环境变量(不入库)
└── migrations/       # 迁移文件
pip freeze > requirements.txt

Gunicorn 启动

pip install gunicorn

# 启动(4 worker,2 线程)
gunicorn -w 4 --threads 2 -b 127.0.0.1:8000 "app:create_app()"

# 或应用文件方式
gunicorn -w 4 -b 127.0.0.1:8000 wsgi:app

wsgi.py

# wsgi.py(Gunicorn 入口)
from app import create_app

app = create_app()

Nginx 配置

server {
    listen 80;
    server_name www.laowang.click;

    location /static/ {
        alias /www/wwwroot/app/static/;
        expires 7d;
    }

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

宝塔面板部署

  1. 网站 → Python 项目 → 创建项目
  2. 选择 Python 版本,上传代码
  3. 安装依赖:pip install -r requirements.txt
  4. 执行迁移:flask db upgrade
  5. 启动命令:gunicorn -w 4 -b 127.0.0.1:8000 wsgi:app
  6. 打开外网映射(自动 Nginx 反代)
  7. 申请 SSL 证书

上线验证

curl -I https://www.laowang.click/       # 200
curl http://127.0.0.1:8000/api/health    # 健康检查
tail -f /var/log/nginx/error.log          # 查错误
💡 提示

部署四步:依赖安装 → 数据库迁移 → Gunicorn 启动 → Nginx 反代。本站 CodeNest(FastAPI)部署方式与此类似。

https://palletsprojects.com/projects/flask/

Flask 官方资源:Flask 官方文档与社区。

官方文档

资源地址/说明
Flask 官方文档flask.palletsprojects.com(权威)
Flask 快速上手文档首篇教程,必读
Flask API 参考全部 API 细节
扩展索引官方维护的扩展列表

项目托管

  • GitHub:pallets/flask
  • PyPI:pypi.org/project/flask
  • 中文社区:flask 相关博客与教程

核心扩展(Pallets 官方)

扩展用途
Flask-SQLAlchemyORM
Flask-Migrate迁移
Flask-Login认证
Flask-WTF表单
Flask-Caching缓存

学习资源

  • Flask Mega-Tutorial(Miguel Grinberg)
  • 《Flask Web 开发》(Miguel Grinberg 著)
  • 官方文档的 Tutorial 部分
  • CodeNest 本站 FastAPI 教程对照学习
💡 提示

遇到问题先查官方文档,其次搜 Stack Overflow;Flask 生态成熟,绝大多数问题都有现成答案。

第 1 / 54 节