Flask 完全指南

NOTE

本文档最后更新于 2026年4月,涵盖 Flask 3 新特性、微服务架构、扩展生态及与 FastAPI 的深度对比。


目录

  1. Flask 概述与定位
  2. Flask 3 新特性
  3. 路由系统详解
  4. Blueprint 模块化
  5. 模板引擎 Jinja2
  6. Flask 扩展生态
  7. Flask 微服务架构
  8. Flask vs FastAPI vs Django 对比
  9. 实战场景与选型建议
  10. 参考资料

Flask 概述与定位

Flask 是一个轻量级的 Python Web 框架,由 Armin Ronacher 于 2010 年发布。Flask 的设计理念是「微框架」,核心只提供必要的功能,其他功能通过扩展实现。这种设计赋予了 Flask 无与伦比的灵活性和定制能力。

核心定位

维度定位
目标用户追求灵活性的全栈开发者、微服务开发者
核心理念简约、灵活、可扩展
应用场景RESTful API、微服务、原型开发、轻量级应用
性能等级Tiers 2(高并发需优化)
学习曲线低(Python 开发者半天可上手)
扩展生态极其丰富(官方 + 社区 100+ 扩展)

基础架构

from flask import Flask, jsonify, request
 
# 创建应用实例
app = Flask(__name__)
 
# 配置
app.config["DEBUG"] = True
app.config["JSON_SORT_KEYS"] = False
 
@app.route("/")
def index():
    """根路由"""
    return jsonify({
        "message": "Welcome to Flask API",
        "version": "3.0",
        "status": "running"
    })
 
@app.route("/api/users/<int:user_id>", methods=["GET"])
def get_user(user_id: int):
    """获取用户详情"""
    return jsonify({
        "id": user_id,
        "name": "张三",
        "email": "zhangsan@example.com"
    })
 
@app.route("/api/users", methods=["POST"])
def create_user():
    """创建用户"""
    data = request.get_json()
    # 业务逻辑
    return jsonify({
        "id": 1,
        **data
    }), 201
 
if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

请求处理流程

HTTP 请求
    ↓
WSGI Server(如 Gunicorn)接收
    ↓
Flask App 匹配路由
    ↓
Before/After Request 函数
    ↓
视图函数执行
    ↓
Response 处理
    ↓
WSGI Server 返回响应

Flask 3 新特性

Flask 3.0(2024年发布)带来了多项重要更新,与现代 Python 生态深度整合。

核心新特性

特性说明优势
Python 3.12+ 支持完全支持新版本特性性能提升
Werkzeug 3.0核心库重大更新更快的路由匹配
Blueprints 2.0改进的模块化系统更好的代码组织
改进的类型提示完整的类型注解IDE 支持更好
Async 支持增强更好的异步视图高并发场景优化

Async 支持

from flask import Flask, jsonify
import asyncio
 
app = Flask(__name__)
 
@app.route("/sync")
def sync_endpoint():
    """同步端点"""
    return jsonify({"message": "同步处理"})
 
@app.route("/async")
async def async_endpoint():
    """异步端点(Flask 3.x)"""
    # 模拟异步操作
    await asyncio.sleep(0.1)
    return jsonify({"message": "异步处理完成"})
 
@app.route("/parallel")
async def parallel_endpoint():
    """并行异步操作"""
    async def fetch_data(id: int):
        await asyncio.sleep(0.1)
        return {"id": id, "data": f"item_{id}"}
    
    # 并发执行
    results = await asyncio.gather(
        fetch_data(1),
        fetch_data(2),
        fetch_data(3)
    )
    
    return jsonify({"results": results})

改进的类型注解

from flask import Flask, jsonify, Request
from typing import Annotated
 
app = Flask(__name__)
 
# 完整的类型注解支持
@app.get("/users/<int:user_id>")
def get_user(user_id: Annotated[int, "用户ID"]) -> dict[str, any]:
    """类型安全的路由处理"""
    return {"id": user_id, "name": "张三"}
 
# Pydantic 集成(Flask-Pydantic 扩展)
from flask_pydantic import validate
from pydantic import BaseModel, Field
 
class QueryParams(BaseModel):
    name: str = Field(..., min_length=2)
    age: int = Field(None, ge=0, le=150)
    limit: int = Field(10, ge=1, le=100)
 
class UserBody(BaseModel):
    username: str = Field(..., min_length=3)
    email: str
 
@app.get("/users/")
@validate(query=QueryParams)
def get_users(query: QueryParams):
    """使用 Pydantic 验证查询参数"""
    return jsonify({
        "name": query.name,
        "age": query.age,
        "limit": query.limit
    })
 
@app.post("/users/")
@validate(body=UserBody)
def create_user(body: UserBody):
    """使用 Pydantic 验证请求体"""
    return jsonify(body.model_dump()), 201

Blueprints 2.0 改进

from flask import Blueprint, Flask
from flask.views import MethodView
 
# 创建 Blueprints(支持嵌套)
api_bp = Blueprint("api", __name__, url_prefix="/api/v1")
users_bp = Blueprint("users", __name__, url_prefix="/users")
 
# 用户相关的 Blueprint
@users_bp.route("/<int:user_id>", methods=["GET"])
def get_user(user_id: int):
    return jsonify({"id": user_id, "name": "张三"})
 
# RESTful 风格的 Blueprint
class UserAPI(MethodView):
    def get(self, user_id=None):
        if user_id is None:
            return jsonify({"users": []})
        return jsonify({"id": user_id})
    
    def post(self):
        return jsonify({"message": "created"}), 201
    
    def put(self, user_id):
        return jsonify({"id": user_id, "message": "updated"})
    
    def delete(self, user_id):
        return "", 204
 
# 注册视图
users_bp.add_url_rule("/", view_func=UserAPI.as_view("users_list"), methods=["GET", "POST"])
users_bp.add_url_rule("/<int:user_id>", view_func=UserAPI.as_view("users_detail"), methods=["GET", "PUT", "DELETE"])
 
# 注册 Blueprint
app = Flask(__name__)
app.register_blueprint(api_bp)
app.register_blueprint(users_bp)

路由系统详解

基础路由

from flask import Flask, request, jsonify
from werkzeug.routing import Map, Rule
 
app = Flask(__name__)
 
# 基本路由
@app.route("/")
def home():
    return "Welcome to Flask"
 
# HTTP 方法
@app.route("/api/data", methods=["GET", "POST"])
def data():
    if request.method == "POST":
        return jsonify(request.get_json())
    return jsonify({"data": []})
 
# 动态路由参数
@app.route("/users/<int:user_id>")  # 整数
def user_by_id(user_id: int):
    return jsonify({"id": user_id})
 
@app.route("/posts/<slug:post_slug>")  # 带类型的 slug
def post_by_slug(post_slug: str):
    return jsonify({"slug": post_slug})
 
@app.route("/files/<path:filepath>")  # 路径(包含斜杠)
def file_path(filepath: str):
    return jsonify({"path": filepath})
 
@app.route("/<any(about, help):page_name>")  # 固定选项
def static_pages(page_name: str):
    return jsonify({"page": page_name})

URL 构建

from flask import url_for, redirect
 
@app.route("/")
def index():
    return "Home"
 
@app.route("/users/<int:user_id>")
def user_profile(user_id: int):
    return f"User {user_id}"
 
@app.route("/redirect-test")
def test_redirect():
    # 使用 url_for 构建 URL
    profile_url = url_for("user_profile", user_id=42)
    return redirect(profile_url)
 
# 模板中使用
# {{ url_for('static', filename='style.css') }}
# {{ url_for('user_profile', user_id=current_user.id) }}

请求对象

from flask import request, jsonify
 
@app.route("/complex-request")
def handle_request():
    """处理复杂的请求数据"""
    
    # 查询参数
    page = request.args.get("page", 1, type=int)
    per_page = request.args.get("per_page", 10, type=int)
    
    # 表单数据
    username = request.form.get("username")
    password = request.form.get("password")
    
    # JSON 数据
    json_data = request.get_json()
    if request.is_json:
        data = request.json
    
    # 请求头
    auth_token = request.headers.get("Authorization")
    content_type = request.content_type
    
    # 文件上传
    file = request.files.get("file")
    if file:
        filename = file.filename
        file.save(f"/uploads/{filename}")
    
    # Cookie
    session_id = request.cookies.get("session_id")
    
    return jsonify({
        "query": {"page": page, "per_page": per_page},
        "form": {"username": username},
        "headers": {"auth": bool(auth_token)}
    })

响应处理

from flask import (
    Flask, jsonify, make_response, 
    redirect, abort, Response
)
 
app = Flask(__name__)
 
@app.route("/json")
def json_response():
    """JSON 响应"""
    return jsonify({
        "message": "success",
        "data": {"id": 1, "name": "test"}
    })
 
@app.route("/custom-response")
def custom_response():
    """自定义响应"""
    response = make_response(jsonify({"message": "OK"}))
    response.headers["X-Custom-Header"] = "value"
    response.status_code = 201
    return response
 
@app.route("/redirect")
def handle_redirect():
    """重定向"""
    return redirect(url_for("json_response"), code=302)
 
@app.route("/error")
def handle_error():
    """错误响应"""
    abort(404)  # 自动返回 404 页面
    # 或自定义错误处理
    return jsonify({"error": "Resource not found"}), 404
 
@app.route("/streaming")
def streaming_response():
    """流式响应(用于大文件)"""
    def generate():
        for chunk in range(10):
            yield f"chunk {chunk}\n"
            import time
            time.sleep(0.1)
    
    return Response(generate(), mimetype="text/plain")
 
@app.route("/download")
def download_file():
    """文件下载"""
    import io
    buffer = io.BytesIO(b"file content here")
    return Response(
        buffer,
        mimetype="application/octet-stream",
        headers={"Content-Disposition": "attachment; filename=file.txt"}
    )

Blueprint 模块化

Blueprint 基础

# app/users/routes.py
from flask import Blueprint, jsonify, request
from functools import wraps
 
# 创建 Blueprint
users_bp = Blueprint("users", __name__)
 
# 中间件(每个 Blueprint 可独立定义)
@users_bp.before_request
def before_request():
    """请求前处理"""
    print(f"Before request: {request.path}")
 
@users_bp.after_request
def after_request(response):
    """请求后处理"""
    response.headers["X-Request-ID"] = "12345"
    return response
 
@users_bp.route("/", methods=["GET"])
def list_users():
    """用户列表"""
    return jsonify({"users": []})
 
@users_bp.route("/", methods=["POST"])
def create_user():
    """创建用户"""
    return jsonify({"id": 1}), 201
 
@users_bp.route("/<int:user_id>", methods=["GET"])
def get_user(user_id: int):
    """获取用户详情"""
    return jsonify({"id": user_id, "name": "张三"})
 
@users_bp.route("/<int:user_id>", methods=["PUT"])
def update_user(user_id: int):
    """更新用户"""
    return jsonify({"id": user_id})
 
@users_bp.route("/<int:user_id>", methods=["DELETE"])
def delete_user(user_id: int):
    """删除用户"""
    return "", 204

Blueprint 组织结构

myapp/
├── __init__.py          # 应用工厂
├── config.py            # 配置
├── extensions.py         # 扩展初始化
├── models/
│   ├── __init__.py
│   ├── user.py
│   └── post.py
├── api/                 # API Blueprint
│   ├── __init__.py
│   ├── routes.py
│   └── schemas.py
├── admin/               # 管理后台 Blueprint
│   ├── __init__.py
│   └── routes.py
├── auth/                # 认证 Blueprint
│   ├── __init__.py
│   ├── routes.py
│   └── utils.py
└── main/
    ├── __init__.py
    └── routes.py

应用工厂模式

# myapp/__init__.py
from flask import Flask
from myapp.extensions import db, migrate, login_manager
from myapp.config import config
 
def create_app(config_name: str = "development") -> Flask:
    """
    应用工厂函数:
    支持多环境配置、扩展初始化、Blueprint 注册
    """
    app = Flask(__name__)
    
    # 加载配置
    app.config.from_object(config[config_name])
    
    # 初始化扩展
    extensions.init_app(app)
    db.init_app(app)
    migrate.init_app(app, db)
    login_manager.init_app(app)
    
    # 注册 Shell 命令
    @app.shell_context_processor
    def make_shell_context():
        return {"db": db, "User": User, "Post": Post}
    
    # 注册 Blueprint
    from myapp.api import api_bp
    from myapp.admin import admin_bp
    from myapp.auth import auth_bp
    from myapp.main import main_bp
    
    app.register_blueprint(api_bp, url_prefix="/api")
    app.register_blueprint(admin_bp, url_prefix="/admin")
    app.register_blueprint(auth_bp, url_prefix="/auth")
    app.register_blueprint(main_bp)
    
    # 注册错误处理
    @app.errorhandler(404)
    def not_found(error):
        return {"error": "Not found"}, 404
    
    @app.errorhandler(500)
    def internal_error(error):
        db.session.rollback()
        return {"error": "Internal server error"}, 500
    
    return app
 
# myapp/extensions.py
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
 
db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()

模板引擎 Jinja2

基础语法

<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}默认标题{% endblock %}</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <nav>
        <a href="{{ url_for('main.home') }}">首页</a>
        {% if current_user.is_authenticated %}
            <a href="{{ url_for('auth.logout') }}">退出</a>
        {% else %}
            <a href="{{ url_for('auth.login') }}">登录</a>
        {% endif %}
    </nav>
    
    {% with messages = get_flashed_messages(with_categories=true) %}
        {% if messages %}
            {% for category, message in messages %}
                <div class="alert alert-{{ category }}">{{ message }}</div>
            {% endfor %}
        {% endif %}
    {% endwith %}
    
    <main>
        {% block content %}{% endblock %}
    </main>
    
    {% block scripts %}{% endblock %}
</body>
</html>
 
<!-- templates/users/profile.html -->
{% extends "base.html" %}
 
{% block title %}用户资料 - {{ user.username }}{% endblock %}
 
{% block content %}
<div class="profile">
    <h1>{{ user.username }}</h1>
    <p>{{ user.bio }}</p>
    
    <!-- 条件渲染 -->
    {% if user.is_premium %}
        <span class="badge">高级会员</span>
    {% endif %}
    
    <!-- 循环 -->
    <ul class="posts">
    {% for post in user.posts %}
        <li>
            <a href="{{ url_for('posts.view', post_id=post.id) }}">
                {{ post.title }}
            </a>
            <span class="date">{{ post.created_at.strftime('%Y-%m-%d') }}</span>
        </li>
    {% else %}
        <li>暂无文章</li>
    {% endfor %}
    </ul>
    
    <!-- 过滤器 -->
    <p>注册时间: {{ user.created_at | datetime }}</p>
    <p>文章数: {{ user.posts | length }}</p>
    
    <!-- 宏 -->
    {% import "macros.html" as macros %}
    {{ macros.card(post) }}
</div>
{% endblock %}

宏定义

<!-- templates/macros.html -->
{% macro render_field(field, class="form-control") %}
<div class="form-group">
    {{ field.label }}
    {{ field(class=class, **kwargs) }}
    {% if field.errors %}
        <ul class="errors">
        {% for error in field.errors %}
            <li>{{ error }}</li>
        {% endfor %}
        </ul>
    {% endif %}
</div>
{% endmacro %}
 
{% macro card(title, content) %}
<div class="card">
    <h3>{{ title }}</h3>
    <p>{{ content }}</p>
</div>
{% endmacro %}
 
{% macro pagination(pager) %}
<nav class="pagination">
    {% if pager.has_prev %}
        <a href="{{ url_for(request.endpoint, page=pager.prev_num) }}">上一页</a>
    {% endif %}
    
    {% for page in pager.iter_pages(left_edge=1, right_edge=1) %}
        {% if page %}
            {% if page == pager.page %}
                <span class="current">{{ page }}</span>
            {% else %}
                <a href="{{ url_for(request.endpoint, page=page) }}">{{ page }}</a>
            {% endif %}
        {% else %}
            <span>...</span>
        {% endif %}
    {% endfor %}
    
    {% if pager.has_next %}
        <a href="{{ url_for(request.endpoint, page=pager.next_num) }}">下一页</a>
    {% endif %}
</nav>
{% endmacro %}

Flask 扩展生态

常用扩展一览

扩展功能官方/社区
Flask-SQLAlchemyORM官方
Flask-Migrate数据库迁移官方
Flask-Login用户认证官方
Flask-WTF表单处理官方
Flask-CORS跨域支持社区
Flask-RESTfulREST API社区
Flask-Marshmallow序列化/验证社区
Flask-JWT-ExtendedJWT 认证社区
Flask-SocketIOWebSocket社区
Flask-Caching缓存社区
Flask-Babel国际化社区
Flask-DebugToolbar调试工具社区

Flask-SQLAlchemy 集成

# app/models/user.py
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime
 
db = SQLAlchemy()
 
class User(UserMixin, db.Model):
    """用户模型"""
    __tablename__ = "users"
    
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password_hash = db.Column(db.String(255), nullable=False)
    is_active = db.Column(db.Boolean, default=True)
    is_admin = db.Column(db.Boolean, default=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    
    # 关系
    posts = db.relationship("Post", backref="author", lazy="dynamic")
    
    def set_password(self, password: str):
        """设置密码(自动哈希)"""
        self.password_hash = generate_password_hash(password)
    
    def check_password(self, password: str) -> bool:
        """验证密码"""
        return check_password_hash(self.password_hash, password)
    
    def __repr__(self):
        return f"<User {self.username}>"
 
class Post(db.Model):
    """文章模型"""
    __tablename__ = "posts"
    
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(200), nullable=False)
    slug = db.Column(db.String(200), unique=True)
    content = db.Column(db.Text)
    published = db.Column(db.Boolean, default=False)
    view_count = db.Column(db.Integer, default=0)
    
    # 外键
    user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
    
    # 时间戳
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    
    def __repr__(self):
        return f"<Post {self.title}>"

Flask-RESTful 快速开发

# app/api/__init__.py
from flask import Flask
from flask_restful import Api
 
def create_app():
    app = Flask(__name__)
    api = Api(app)
    
    # 注册资源
    from app.api.resources import UserList, UserDetail, PostList
    api.add_resource(UserList, "/api/users")
    api.add_resource(UserDetail, "/api/users/<int:user_id>")
    api.add_resource(PostList, "/api/posts")
    
    return app
 
# app/api/resources.py
from flask_restful import Resource, reqparse, fields, marshal_with
from app.models import db, User, Post
 
# 请求解析器
user_parser = reqparse.RequestParser()
user_parser.add_argument("username", type=str, required=True, help="用户名必填")
user_parser.add_argument("email", type=str, required=True, help="邮箱必填")
user_parser.add_argument("password", type=str)
 
# 响应格式化
user_fields = {
    "id": fields.Integer,
    "username": fields.String,
    "email": fields.String,
    "created_at": fields.DateTime(dt_format="iso8601")
}
 
class UserList(Resource):
    @marshal_with(user_fields)
    def get(self):
        """获取用户列表"""
        users = User.query.all()
        return users
    
    def post(self):
        """创建用户"""
        args = user_parser.parse_args()
        user = User(**args)
        user.set_password(args["password"])
        db.session.add(user)
        db.session.commit()
        return user, 201
 
class UserDetail(Resource):
    @marshal_with(user_fields)
    def get(self, user_id: int):
        """获取用户详情"""
        user = User.query.get_or_404(user_id)
        return user
    
    def put(self, user_id: int):
        """更新用户"""
        user = User.query.get_or_404(user_id)
        args = user_parser.parse_args()
        for key, value in args.items():
            if value is not None:
                setattr(user, key, value)
        db.session.commit()
        return user
    
    def delete(self, user_id: int):
        """删除用户"""
        user = User.query.get_or_404(user_id)
        db.session.delete(user)
        db.session.commit()
        return "", 204

Flask-CORS 配置

from flask import Flask
from flask_cors import CORS
 
app = Flask(__name__)
 
# 全局 CORS
CORS(app)
 
# 精细化 CORS 配置
CORS(
    app,
    resources={
        r"/api/*": {
            "origins": ["https://example.com", "https://app.example.com"],
            "methods": ["GET", "POST", "PUT", "DELETE"],
            "allow_headers": ["Content-Type", "Authorization"],
            "expose_headers": ["X-Total-Count"],
            "supports_credentials": True,
            "max_age": 3600
        }
    }
)

Flask-Caching 缓存

from flask import Flask
from flask_caching import Cache
 
app = Flask(__name__)
app.config["CACHE_TYPE"] = "redis"
app.config["CACHE_REDIS_HOST"] = "localhost"
app.config["CACHE_REDIS_PORT"] = 6379
app.config["CACHE_DEFAULT_TIMEOUT"] = 300
 
cache = Cache(app)
 
@app.route("/users/<int:user_id>")
@cache.cached(timeout=60, query_string=True)
def get_user(user_id: int):
    """用户详情缓存 60 秒"""
    user = User.query.get_or_404(user_id)
    return jsonify({"id": user.id, "name": user.username})
 
@app.route("/stats")
@cache.memoize(timeout=300)
def get_stats():
    """统计信息缓存"""
    return {
        "user_count": User.query.count(),
        "post_count": Post.query.count()
    }
 
# 手动缓存控制
def expensive_operation():
    if cache.get("expensive_data"):
        return cache.get("expensive_data")
    
    data = compute_expensive_data()
    cache.set("expensive_data", data, timeout=3600)
    return data

Flask 微服务架构

微服务最佳实践

# config.py - 环境配置
import os
 
class Config:
    """基础配置"""
    SECRET_KEY = os.environ.get("SECRET_KEY", "dev-secret")
    SQLALCHEMY_TRACK_MODIFICATIONS = False
 
class DevelopmentConfig(Config):
    """开发环境"""
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = "sqlite:///dev.db"
 
class ProductionConfig(Config):
    """生产环境"""
    DEBUG = False
    SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL")
 
class TestingConfig(Config):
    """测试环境"""
    TESTING = True
    SQLALCHEMY_DATABASE_URI = "sqlite:///test.db"
 
config = {
    "development": DevelopmentConfig,
    "production": ProductionConfig,
    "testing": TestingConfig
}

服务发现与注册

# 微服务客户端
import requests
from typing import Optional
 
class ServiceClient:
    """服务客户端封装"""
    
    def __init__(self, service_name: str, base_url: Optional[str] = None):
        self.service_name = service_name
        self.base_url = base_url or self._discover_service()
    
    def _discover_service(self) -> str:
        """从配置中心或负载均衡器发现服务"""
        # 实际实现应接入 Consul/Eureka/Kubernetes
        return os.environ.get(f"{self.service_name.upper()}_URL", "http://localhost:5000")
    
    def request(self, method: str, path: str, **kwargs):
        """统一的请求方法"""
        url = f"{self.base_url}{path}"
        response = requests.request(method, url, **kwargs)
        response.raise_for_status()
        return response.json()
 
# 使用示例
user_service = ServiceClient("user-service")
order_service = ServiceClient("order-service")
 
@app.route("/users/<int:user_id>/orders")
def get_user_orders(user_id: int):
    # 调用用户服务
    user = user_service.request("GET", f"/users/{user_id}")
    
    # 调用订单服务
    orders = order_service.request("GET", f"/orders", params={"user_id": user_id})
    
    return jsonify({"user": user, "orders": orders})

Docker 容器化

# Dockerfile
FROM python:3.12-slim
 
WORKDIR /app
 
# 安装依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
 
# 复制应用
COPY . .
 
# 非 root 用户
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser
 
EXPOSE 5000
 
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:5000/health || exit 1
 
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "--threads", "2", "app:create_app()"]
# docker-compose.yml
version: "3.8"
 
services:
  api:
    build: .
    ports:
      - "5000:5000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/myapp
      - REDIS_URL=redis://redis:6379
    depends_on:
      - db
      - redis
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: "0.5"
          memory: 512M
 
  db:
    image: postgres:15-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: myapp
 
  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
 
volumes:
  postgres_data:
  redis_data:

Flask vs FastAPI vs Django 对比

核心对比表

特性FlaskFastAPIDjango
定位微框架现代 API 框架全栈框架
诞生时间201020192005
核心理念简约 + 灵活性能 + 类型安全全功能
异步支持需扩展(Flask-COROUTING)原生 async/await有限(Channels)
数据验证Flask-WTF/Flask-MarshmallowPydantic(原生)Form/DRF Serializer
自动文档需扩展OpenAPI/Swagger/ReDocDRF API Browser
ORMFlask-SQLAlchemySQLAlchemy(推荐)Django ORM(内置)
管理后台无(需扩展)Django Admin(内置)
认证系统Flask-Login/Flask-JWT需自行实现Django Auth(内置)
学习曲线低-中中-高
性能极高
AI 集成一般最佳一般
适用场景微服务、轻量 APIAPI、AI 应用全栈、内容网站

Flask 扩展表

扩展类别扩展名称功能
数据库Flask-SQLAlchemyORM
数据库Flask-Migrate数据库迁移
数据库Flask-Peewee轻量 ORM
认证Flask-Login用户会话
认证Flask-JWT-ExtendedJWT Token
认证Flask-HTTPAuthHTTP Basic/Digest
表单Flask-WTFWTForms 集成
APIFlask-RESTfulREST API
APIFlask-RESTXSwagger 文档
缓存Flask-Caching多后端缓存
异步Flask-SocketIOWebSocket
异步Quart异步 Flask 替代
监控Flask-DebugToolbar调试面板
日志Flask-Logging日志管理

选型决策矩阵

场景推荐框架原因
AI 应用后端FastAPILangChain/LlamaIndex 原生支持
微服务Flask / FastAPI轻量、灵活
内容管理Django自带 Admin、完整生态
快速原型Flask最灵活、最快上手
RESTful APIFastAPI类型安全、自动文档
实时应用FastAPI / Flask-SocketIO原生/扩展支持
企业级全栈Django生态完整
学习入门Flask最简单

TIP

Flask 的核心竞争力:在需要高度定制化、轻量级、或与现有系统集成的场景下,Flask 的灵活性是最佳选择。其「做你需要的事,不多不少」的设计哲学使其成为微服务架构的理想选择。


实战场景与选型建议

场景一:RESTful API 微服务

# app.py - Flask 微服务
from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from flask_cors import CORS
from flask_jwt_extended import JWTManager, create_access_token, jwt_required
from datetime import timedelta
 
app = Flask(__name__)
CORS(app)
 
# 配置
app.config["SQLALCHEMY_DATABASE_URI"] = "postgresql://user:pass@localhost/db"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["JWT_SECRET_KEY"] = "super-secret"
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=1)
 
# 初始化
db = SQLAlchemy(app)
ma = Marshmallow(app)
jwt = JWTManager(app)
 
# 模型
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True)
    email = db.Column(db.String(120), unique=True)
    password_hash = db.Column(db.String(255))
 
# Schema
class UserSchema(ma.Schema):
    class Meta:
        fields = ("id", "username", "email")
 
user_schema = UserSchema()
users_schema = UserSchema(many=True)
 
# 路由
@app.route("/users", methods=["GET"])
@jwt_required()
def get_users():
    users = User.query.all()
    return users_schema.jsonify(users)
 
@app.route("/users", methods=["POST"])
def create_user():
    data = request.get_json()
    user = User(**data)
    db.session.add(user)
    db.session.commit()
    return user_schema.jsonify(user), 201
 
@app.route("/login", methods=["POST"])
def login():
    data = request.get_json()
    user = User.query.filter_by(username=data["username"]).first()
    if user and user.check_password(data["password"]):
        token = create_access_token(identity=user.id)
        return jsonify({"token": token})
    return jsonify({"error": "Invalid credentials"}), 401
 
if __name__ == "__main__":
    app.run(debug=True)

场景二:GraphQL API

# 使用 Flask-GraphQL
from flask import Flask
from flask_graphql import GraphQLView
from graphene import Schema, ObjectType, String, List, Field
 
app = Flask(__name__)
 
class Query(ObjectType):
    hello = String(name=String(default_value="World"))
    users = List(String)
    
    def resolve_hello(self, info, name):
        return f"Hello {name}"
    
    def resolve_users(self, info):
        return ["Alice", "Bob", "Charlie"]
 
schema = Schema(query=Query)
 
app.add_url_rule("/graphql", view_func=GraphQLView.as_view(
    "graphql",
    schema=schema,
    graphiql=True  # 启用 GraphiQL IDE
))
 
if __name__ == "__main__":
    app.run(debug=True)

场景三:实时通知系统

# 使用 Flask-SocketIO
from flask import Flask, render_template
from flask_socketio import SocketIO, emit, join_room, leave_room
 
app = Flask(__name__)
app.config["SECRET_KEY"] = "secret"
socketio = SocketIO(app, cors_allowed_origins="*")
 
@app.route("/")
def index():
    return render_template("index.html")
 
@socketio.on("connect")
def handle_connect():
    print("Client connected")
    emit("response", {"data": "Connected"})
 
@socketio.on("join")
def handle_join(data):
    room = data["room"]
    join_room(room)
    emit("message", {"data": f"Joined {room}"}, room=room)
 
@socketio.on("leave")
def handle_leave(data):
    room = data["room"]
    leave_room(room)
    emit("message", {"data": f"Left {room}"})
 
@socketio.on("message")
def handle_message(data):
    room = data.get("room")
    if room:
        emit("message", {"data": data["message"]}, room=room)
    else:
        emit("message", {"data": data["message"]}, broadcast=True)
 
if __name__ == "__main__":
    socketio.run(app, host="0.0.0.0", port=5000)

参考资料

官方资源

社区资源

资源说明
Flask GitHub65k+ Stars
Full Stack Python Flask全面的教程集合
Flask Discord社区交流

推荐学习路径

  1. 入门阶段

    • Flask 官方教程
    • Miguel Grinberg 的《Flask Web Development》
  2. 进阶阶段

    • 大规模应用架构
    • 微服务设计模式
    • 性能优化
  3. 实战阶段

    • 构建完整的 RESTful API
    • 实现实时功能
    • 容器化部署

完整安装与环境配置

环境要求

# Python 版本要求
python --version  # >= 3.8 (Flask 3.x)
 
# pip 版本
pip --version  # >= 21.0

项目创建

# 创建虚拟环境
python -m venv venv
source venv/bin/activate  # Linux/Mac
# venv\Scripts\activate  # Windows
 
# 安装 Flask
pip install Flask
 
# 或使用 Flask 3.x
pip install Flask==3.0.0
 
# 推荐依赖
pip install flask[async]  # 异步支持

推荐的完整依赖

# 核心框架
pip install Flask==3.0.0
 
# 数据库
pip install Flask-SQLAlchemy Flask-Migrate
pip install psycopg2-binary  # PostgreSQL
pip install pymysql          # MySQL
 
# Redis
pip install Flask-Session Flask-Cache
 
# 认证
pip install Flask-Login Flask-JWT-Extended Flask-HTTPAuth
 
# REST API
pip install Flask-RESTful Flask-RESTX
pip install marshmallow marshmallow-sqlalchemy
 
# 表单
pip install Flask-WTF WTForms email-validator
 
# 实时通信
pip install Flask-SocketIO python-socketio
 
# API 文档
pip install flasgger
 
# CORS
pip install Flask-CORS
 
# 部署
pip install gunicorn
 
# 开发工具
pip install black isort mypy
pip install --save-dev pytest pytest-flask

项目结构

my-flask-app/
├── app/
│   ├── __init__.py          # 应用工厂
│   ├── config.py            # 配置
│   ├── extensions.py        # 扩展初始化
│   ├── models/              # 数据库模型
│   │   ├── __init__.py
│   │   ├── user.py
│   │   └── post.py
│   ├── schemas/             # Marshmallow schemas
│   │   ├── __init__.py
│   │   ├── user.py
│   │   └── post.py
│   ├── api/                # REST API 蓝图
│   │   ├── __init__.py
│   │   ├── users.py
│   │   ├── posts.py
│   │   └── auth.py
│   ├── cli/                # CLI 命令
│   │   └── commands.py
│   ├── services/            # 业务逻辑
│   │   ├── __init__.py
│   │   └── user_service.py
│   ├── utils/              # 工具函数
│   │   ├── __init__.py
│   │   └── decorators.py
│   └── templates/          # Jinja2 模板
│       ├── base.html
│       └── ...
├── migrations/             # 数据库迁移
├── tests/
│   ├── __init__.py
│   ├── conftest.py
│   ├── test_users.py
│   └── test_posts.py
├── .env
├── .env.example
├── requirements.txt
├── run.py                  # 应用入口
└── pytest.ini

应用工厂模式

# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from flask_jwt_extended import JWTManager
from flask_cors import CORS
from flask_cache import Cache
from flask_restx import Api
 
from app.config import config
 
# 初始化扩展
db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()
jwt = JWTManager()
cache = Cache()
api = Api()
 
# REST API 命名空间
user_ns = None
post_ns = None
 
 
def create_app(config_name='development'):
    """应用工厂函数"""
    app = Flask(__name__)
    app.config.from_object(config[config_name])
 
    # 初始化扩展
    db.init_app(app)
    migrate.init_app(app, db)
    login_manager.init_app(app)
    jwt.init_app(app)
    cache.init_app(app)
    CORS(app, resources={r"/api/*": {"origins": "*"}})
 
    # REST API
    global user_ns, post_ns
    api.init_app(app)
    user_ns = api.namespace('users', description='User operations')
    post_ns = api.namespace('posts', description='Post operations')
 
    # 导入模型和视图
    from app.models import user, post
    from app.api import users, posts, auth
 
    # 注册蓝图
    from app.api import api_bp
    app.register_blueprint(api_bp, url_prefix='/api')
 
    # 首页蓝图(如果需要)
    from app.routes import main_bp
    app.register_blueprint(main_bp)
 
    # JWT 错误处理
    @jwt.expired_token_loader
    def expired_token_callback(jwt_header, jwt_payload):
        return {'message': 'Token has expired'}, 401
 
    @jwt.invalid_token_loader
    def invalid_token_callback(error):
        return {'message': 'Invalid token'}, 401
 
    @jwt.unauthorized_loader
    def unauthorized_callback(error):
        return {'message': 'Authorization required'}, 401
 
    # 注册 CLI 命令
    from app.cli import register_commands
    register_commands(app)
 
    return app

配置文件

# app/config.py
import os
from datetime import timedelta
 
 
class Config:
    """基础配置"""
 
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key'
    SQLALCHEMY_TRACK_MODIFICATIONS = False
 
    # JWT
    JWT_SECRET_KEY = os.environ.get('JWT_SECRET_KEY')
    JWT_ACCESS_TOKEN_EXPIRES = timedelta(minutes=15)
    JWT_REFRESH_TOKEN_EXPIRES = timedelta(days=7)
 
    # Redis
    REDIS_URL = os.environ.get('REDIS_URL') or 'redis://localhost:6379/0'
 
    # 分页
    ITEMS_PER_PAGE = 20
 
 
class DevelopmentConfig(Config):
    """开发环境配置"""
 
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = os.environ.get(
        'DATABASE_URL'
    ) or 'postgresql://postgres:password@localhost:5432/myapp_dev'
 
 
class ProductionConfig(Config):
    """生产环境配置"""
 
    DEBUG = False
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
    SQLALCHEMY_ENGINE_OPTIONS = {
        'pool_size': 10,
        'pool_recycle': 3600,
        'pool_pre_ping': True,
    }
 
    # 生产环境 Redis
    CACHE_TYPE = 'redis'
    CACHE_REDIS_URL = REDIS_URL
 
 
class TestingConfig(Config):
    """测试环境配置"""
 
    TESTING = True
    SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
    WTF_CSRF_ENABLED = False
    JWT_ACCESS_TOKEN_EXPIRES = timedelta(minutes=5)
 
 
config = {
    'development': DevelopmentConfig,
    'production': ProductionConfig,
    'testing': TestingConfig,
    'default': DevelopmentConfig,
}

数据库模型

SQLAlchemy 模型

# app/models/__init__.py
from app.extensions import db
from datetime import datetime
import enum
 
 
class UserRole(enum.Enum):
    USER = 'USER'
    ADMIN = 'ADMIN'
    MODERATOR = 'MODERATOR'
 
 
class PostStatus(enum.Enum):
    DRAFT = 'DRAFT'
    PUBLISHED = 'PUBLISHED'
    ARCHIVED = 'ARCHIVED'
# app/models/user.py
from app.extensions import db
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime
 
 
class User(UserMixin, db.Model):
    __tablename__ = 'users'
 
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(255), unique=True, nullable=False, index=True)
    username = db.Column(db.String(50), unique=True, nullable=False, index=True)
    password_hash = db.Column(db.String(255), nullable=False)
    first_name = db.Column(db.String(100))
    last_name = db.Column(db.String(100))
    avatar = db.Column(db.String(500))
    bio = db.Column(db.Text)
    role = db.Column(db.String(20), default='USER')
    is_active = db.Column(db.Boolean, default=True)
    is_verified = db.Column(db.Boolean, default=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    last_login_at = db.Column(db.DateTime)
 
    # 关系
    posts = db.relationship('Post', backref='author', lazy='dynamic')
 
    def set_password(self, password):
        self.password_hash = generate_password_hash(password)
 
    def check_password(self, password):
        return check_password_hash(self.password_hash, password)
 
    @property
    def full_name(self):
        return f'{self.first_name or ""} {self.last_name or ""}'.strip()
 
    def to_dict(self):
        return {
            'id': self.id,
            'email': self.email,
            'username': self.username,
            'first_name': self.first_name,
            'last_name': self.last_name,
            'role': self.role,
            'is_active': self.is_active,
            'created_at': self.created_at.isoformat() if self.created_at else None,
        }
 
    def __repr__(self):
        return f'<User {self.username}>'
# app/models/post.py
from app.extensions import db
from datetime import datetime
import enum
 
 
class PostStatus(enum.Enum):
    DRAFT = 'DRAFT'
    PUBLISHED = 'PUBLISHED'
    ARCHIVED = 'ARCHIVED'
 
 
class Post(db.Model):
    __tablename__ = 'posts'
 
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(200), nullable=False)
    slug = db.Column(db.String(200), unique=True, nullable=False, index=True)
    content = db.Column(db.Text, nullable=False)
    excerpt = db.Column(db.String(300))
    featured_image = db.Column(db.String(500))
    status = db.Column(db.String(20), default=PostStatus.DRAFT.value)
    published_at = db.Column(db.DateTime)
    view_count = db.Column(db.Integer, default=0)
    like_count = db.Column(db.Integer, default=0)
 
    author_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
 
    category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
    category = db.relationship('Category', backref='posts')
 
    tags = db.relationship('Tag', secondary='post_tags', backref=db.backref('posts', lazy='dynamic'))
 
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
 
    def to_dict(self, include_content=False):
        data = {
            'id': self.id,
            'title': self.title,
            'slug': self.slug,
            'excerpt': self.excerpt,
            'featured_image': self.featured_image,
            'status': self.status,
            'published_at': self.published_at.isoformat() if self.published_at else None,
            'view_count': self.view_count,
            'like_count': self.like_count,
            'author': self.author.to_dict(),
            'tags': [tag.name for tag in self.tags],
            'created_at': self.created_at.isoformat() if self.created_at else None,
        }
        if include_content:
            data['content'] = self.content
        return data
 
    def __repr__(self):
        return f'<Post {self.title}>'
 
 
class Category(db.Model):
    __tablename__ = 'categories'
 
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)
    slug = db.Column(db.String(100), unique=True, nullable=False)
    description = db.Column(db.Text)
    parent_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
    order = db.Column(db.Integer, default=0)
 
    def to_dict(self):
        return {
            'id': self.id,
            'name': self.name,
            'slug': self.slug,
            'description': self.description,
        }
 
 
class Tag(db.Model):
    __tablename__ = 'tags'
 
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50), unique=True, nullable=False)
    slug = db.Column(db.String(50), unique=True, nullable=False)
 
 
# 多对多关联表
post_tags = db.Table(
    'post_tags',
    db.Column('post_id', db.Integer, db.ForeignKey('posts.id'), primary_key=True),
    db.Column('tag_id', db.Integer, db.ForeignKey('tags.id'), primary_key=True),
)

REST API 实现

Marshmallow Schemas

# app/schemas/user.py
from marshmallow import Schema, fields, validate, validates, ValidationError
 
 
class UserSchema(Schema):
    id = fields.Int(dump_only=True)
    email = fields.Email(required=True)
    username = fields.Str(required=True, validate=validate.Length(3, 50))
    first_name = fields.Str(validate=validate.Length(max=100))
    last_name = fields.Str(validate=validate.Length(max=100))
    bio = fields.Str(validate=validate.Length(max=500))
    role = fields.Str(dump_only=True)
    is_active = fields.Bool(dump_only=True)
    is_verified = fields.Bool(dump_only=True)
    created_at = fields.DateTime(dump_only=True)
 
 
class UserCreateSchema(Schema):
    email = fields.Email(required=True)
    username = fields.Str(required=True, validate=validate.Length(3, 50))
    password = fields.Str(required=True, validate=validate.Length(8), load_only=True)
 
    @validates('password')
    def validate_password(self, value):
        if not any(c.isupper() for c in value):
            raise ValidationError('Password must contain uppercase letter')
        if not any(c.islower() for c in value):
            raise ValidationError('Password must contain lowercase letter')
        if not any(c.isdigit() for c in value):
            raise ValidationError('Password must contain digit')
 
 
class UserUpdateSchema(Schema):
    email = fields.Email()
    first_name = fields.Str(validate=validate.Length(max=100))
    last_name = fields.Str(validate=validate.Length(max=100))
    bio = fields.Str(validate=validate.Length(max=500))
 
 
class UserLoginSchema(Schema):
    email = fields.Email(required=True)
    password = fields.Str(required=True, load_only=True)
 
 
user_schema = UserSchema()
users_schema = UserSchema(many=True)
user_create_schema = UserCreateSchema()
user_update_schema = UserUpdateSchema()
user_login_schema = UserLoginSchema()
# app/schemas/post.py
from marshmallow import Schema, fields, validate
 
 
class TagSchema(Schema):
    id = fields.Int(dump_only=True)
    name = fields.Str(required=True)
    slug = fields.Str()
 
 
class CategorySchema(Schema):
    id = fields.Int(dump_only=True)
    name = fields.Str(required=True)
    slug = fields.Str()
    description = fields.Str()
 
 
class PostSchema(Schema):
    id = fields.Int(dump_only=True)
    title = fields.Str(required=True, validate=validate.Length(1, 200))
    slug = fields.Str()
    content = fields.Str(required=True)
    excerpt = fields.Str(validate=validate.Length(max=300))
    featured_image = fields.Str()
    status = fields.Str()
    published_at = fields.DateTime(dump_only=True)
    view_count = fields.Int(dump_only=True)
    like_count = fields.Int(dump_only=True)
    author = fields.Nested('UserSchema', dump_only=True)
    category = fields.Nested(CategorySchema)
    tags = fields.Nested(TagSchema, many=True)
    created_at = fields.DateTime(dump_only=True)
    updated_at = fields.DateTime(dump_only=True)
 
 
class PostCreateSchema(Schema):
    title = fields.Str(required=True, validate=validate.Length(1, 200))
    content = fields.Str(required=True)
    excerpt = fields.Str(validate=validate.Length(max=300))
    featured_image = fields.Str()
    status = fields.Str(validate=validate.OneOf(['DRAFT', 'PUBLISHED']))
    category_id = fields.Int()
    tags = fields.List(fields.Str())
 
 
class PostUpdateSchema(Schema):
    title = fields.Str(validate=validate.Length(1, 200))
    content = fields.Str()
    excerpt = fields.Str(validate=validate.Length(max=300))
    featured_image = fields.Str()
    status = fields.Str(validate=validate.OneOf(['DRAFT', 'PUBLISHED', 'ARCHIVED']))
    category_id = fields.Int()
    tags = fields.List(fields.Str())
 
 
post_schema = PostSchema()
posts_schema = PostSchema(many=True)
post_create_schema = PostCreateSchema()
post_update_schema = PostUpdateSchema()

API 路由

# app/api/users.py
from flask import request, jsonify
from flask_jwt_extended import (
    create_access_token,
    create_refresh_token,
    jwt_required,
    get_jwt_identity,
)
from flask_restx import Resource, fields
 
from app.extensions import db
from app.models.user import User
from app.schemas.user import (
    user_schema,
    users_schema,
    user_create_schema,
    user_update_schema,
    user_login_schema,
)
 
ns = user_ns  # 在 __init__.py 中定义
 
# RESTX 模型
user_model = ns.model('User', {
    'id': fields.Integer(readonly=True),
    'email': fields.String(required=True),
    'username': fields.String(required=True),
    'first_name': fields.String(),
    'last_name': fields.String(),
    'role': fields.String(readonly=True),
})
 
 
@ns.route('/')
class UserList(Resource):
    @ns.doc('list_users')
    @ns.param('page', 'Page number', type=int, default=1)
    @ns.param('per_page', 'Items per page', type=int, default=20)
    @ns.marshal_list_with(user_model)
    @jwt_required()
    def get(self):
        """获取用户列表"""
        page = request.args.get('page', 1, type=int)
        per_page = request.args.get('per_page', 20, type=int)
 
        pagination = User.query.order_by(User.created_at.desc()).paginate(
            page=page, per_page=per_page, error_out=False
        )
 
        return {
            'data': users_schema.dump(pagination.items),
            'total': pagination.total,
            'page': page,
            'per_page': per_page,
            'pages': pagination.pages,
        }
 
 
@ns.route('/<int:id>')
@ns.param('id', 'User ID')
class UserDetail(Resource):
    @ns.doc('get_user')
    @ns.marshal_with(user_model)
    @jwt_required()
    def get(self, id):
        """获取单个用户"""
        user = User.query.get_or_404(id)
        return user_schema.dump(user)
 
 
@ns.route('/register')
class UserRegister(Resource):
    @ns.doc('register_user')
    @ns.expect(user_create_schema)
    def post(self):
        """用户注册"""
        try:
            data = user_create_schema.load(request.json)
        except ValidationError as err:
            return {'errors': err.messages}, 400
 
        # 检查邮箱
        if User.query.filter_by(email=data['email']).first():
            return {'error': 'Email already registered'}, 409
 
        # 检查用户名
        if User.query.filter_by(username=data['username']).first():
            return {'error': 'Username already taken'}, 409
 
        # 创建用户
        user = User(
            email=data['email'],
            username=data['username'],
        )
        user.set_password(data['password'])
 
        db.session.add(user)
        db.session.commit()
 
        return user_schema.dump(user), 201
 
 
@ns.route('/login')
class UserLogin(Resource):
    @ns.doc('login_user')
    @ns.expect(user_login_schema)
    def post(self):
        """用户登录"""
        try:
            data = user_login_schema.load(request.json)
        except ValidationError as err:
            return {'errors': err.messages}, 400
 
        user = User.query.filter_by(email=data['email']).first()
 
        if not user or not user.check_password(data['password']):
            return {'error': 'Invalid credentials'}, 401
 
        if not user.is_active:
            return {'error': 'Account is inactive'}, 401
 
        # 更新最后登录
        user.last_login_at = datetime.utcnow()
        db.session.commit()
 
        access_token = create_access_token(identity=user.id)
        refresh_token = create_refresh_token(identity=user.id)
 
        return {
            'user': user_schema.dump(user),
            'access_token': access_token,
            'refresh_token': refresh_token,
        }
 
 
@ns.route('/me')
class UserMe(Resource):
    @ns.doc('current_user')
    @ns.marshal_with(user_model)
    @jwt_required()
    def get(self):
        """获取当前用户"""
        user_id = get_jwt_identity()
        user = User.query.get_or_404(user_id)
        return user_schema.dump(user)
 
    @ns.doc('update_current_user')
    @ns.expect(user_update_schema)
    @ns.marshal_with(user_model)
    @jwt_required()
    def put(self):
        """更新当前用户"""
        user_id = get_jwt_identity()
        user = User.query.get_or_404(user_id)
 
        try:
            data = user_update_schema.load(request.json)
        except ValidationError as err:
            return {'errors': err.messages}, 400
 
        for key, value in data.items():
            setattr(user, key, value)
 
        db.session.commit()
        return user_schema.dump(user)

常见陷阱与最佳实践

陷阱 1:使用 Flask 全局对象

# ❌ 错误:在请求上下文外使用
def get_current_user():
    return current_user  # 可能在请求上下文外失败
 
 
# ✅ 正确:在请求中获取
@bp.route('/profile')
@login_required
def profile():
    return jsonify(current_user.to_dict())

陷阱 2:直接返回数据库对象

# ❌ 错误:直接返回 SQLAlchemy 对象
@bp.route('/users/<int:id>')
def get_user(id):
    user = User.query.get_or_404(id)
    return jsonify(user)  # 会导致序列化错误
 
 
# ✅ 正确:使用 Schema 序列化
@bp.route('/users/<int:id>')
def get_user(id):
    user = User.query.get_or_404(id)
    return jsonify(user_schema.dump(user))

陷阱 3:忘记提交事务

# ❌ 错误:忘记提交
@bp.route('/users', methods=['POST'])
def create_user():
    user = User(**request.json)
    db.session.add(user)
    # 忘记 db.session.commit()
 
 
# ✅ 正确:确保提交
@bp.route('/users', methods=['POST'])
def create_user():
    user = User(**request.json)
    db.session.add(user)
    db.session.commit()
    return jsonify(user_schema.dump(user)), 201

最佳实践清单

  1. 应用工厂:使用工厂模式创建应用,便于测试。
  2. 蓝图:按功能模块划分蓝图,保持代码组织。
  3. 配置分离:开发、测试、生产环境配置分离。
  4. 扩展初始化:在扩展模块中统一初始化扩展。
  5. Schema 验证:使用 Marshmallow 或 Pydantic 验证输入。
  6. 错误处理:统一错误处理,提供一致的 API 响应。
  7. 日志记录:使用 Flask 内置日志系统。
  8. 数据库会话:确保在每个请求结束时提交或回滚。
  9. 安全:使用安全的密码哈希、CORS、CSRF 保护。
  10. 测试:编写单元测试和集成测试。

与其他框架对比

Flask vs FastAPI

特性FlaskFastAPI
性能中等极高
异步需要扩展原生支持
数据验证MarshmallowPydantic
自动文档需要扩展内置
类型弱类型强类型
适用场景轻量应用现代 API

Flask vs Django

特性FlaskDjango
架构微框架全功能
ORMSQLAlchemyDjango ORM
Admin内置
认证需要扩展内置
表单WTFormsDjango Forms
适用场景轻量/微服务企业级全栈

Flask vs Express

特性FlaskExpress
语言PythonJavaScript
生态Python 生态Node.js 生态
性能中等
异步需要扩展原生支持
模板Jinja2EJS/Handlebars
适用场景Python 团队JavaScript 团队

SUCCESS

本文档全面覆盖了 Flask 框架的核心知识,包括 Flask 3 新特性、路由系统、Blueprint 模块化、扩展生态及微服务架构设计。Flask 以其简约灵活的设计理念,在微服务和轻量级应用场景下保持着强劲的生命力,是 vibecoding 技术栈中不可或缺的 Python 后端选择。