Flask 完全指南
NOTE
本文档最后更新于 2026年4月,涵盖 Flask 3 新特性、微服务架构、扩展生态及与 FastAPI 的深度对比。
目录
- Flask 概述与定位
- Flask 3 新特性
- 路由系统详解
- Blueprint 模块化
- 模板引擎 Jinja2
- Flask 扩展生态
- Flask 微服务架构
- Flask vs FastAPI vs Django 对比
- 实战场景与选型建议
- 参考资料
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()), 201Blueprints 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 "", 204Blueprint 组织结构
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-SQLAlchemy | ORM | 官方 |
| Flask-Migrate | 数据库迁移 | 官方 |
| Flask-Login | 用户认证 | 官方 |
| Flask-WTF | 表单处理 | 官方 |
| Flask-CORS | 跨域支持 | 社区 |
| Flask-RESTful | REST API | 社区 |
| Flask-Marshmallow | 序列化/验证 | 社区 |
| Flask-JWT-Extended | JWT 认证 | 社区 |
| Flask-SocketIO | WebSocket | 社区 |
| 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 "", 204Flask-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 dataFlask 微服务架构
微服务最佳实践
# 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 对比
核心对比表
| 特性 | Flask | FastAPI | Django |
|---|---|---|---|
| 定位 | 微框架 | 现代 API 框架 | 全栈框架 |
| 诞生时间 | 2010 | 2019 | 2005 |
| 核心理念 | 简约 + 灵活 | 性能 + 类型安全 | 全功能 |
| 异步支持 | 需扩展(Flask-COROUTING) | 原生 async/await | 有限(Channels) |
| 数据验证 | Flask-WTF/Flask-Marshmallow | Pydantic(原生) | Form/DRF Serializer |
| 自动文档 | 需扩展 | OpenAPI/Swagger/ReDoc | DRF API Browser |
| ORM | Flask-SQLAlchemy | SQLAlchemy(推荐) | Django ORM(内置) |
| 管理后台 | 无(需扩展) | 无 | Django Admin(内置) |
| 认证系统 | Flask-Login/Flask-JWT | 需自行实现 | Django Auth(内置) |
| 学习曲线 | 低 | 低-中 | 中-高 |
| 性能 | 高 | 极高 | 中 |
| AI 集成 | 一般 | 最佳 | 一般 |
| 适用场景 | 微服务、轻量 API | API、AI 应用 | 全栈、内容网站 |
Flask 扩展表
| 扩展类别 | 扩展名称 | 功能 |
|---|---|---|
| 数据库 | Flask-SQLAlchemy | ORM |
| 数据库 | Flask-Migrate | 数据库迁移 |
| 数据库 | Flask-Peewee | 轻量 ORM |
| 认证 | Flask-Login | 用户会话 |
| 认证 | Flask-JWT-Extended | JWT Token |
| 认证 | Flask-HTTPAuth | HTTP Basic/Digest |
| 表单 | Flask-WTF | WTForms 集成 |
| API | Flask-RESTful | REST API |
| API | Flask-RESTX | Swagger 文档 |
| 缓存 | Flask-Caching | 多后端缓存 |
| 异步 | Flask-SocketIO | WebSocket |
| 异步 | Quart | 异步 Flask 替代 |
| 监控 | Flask-DebugToolbar | 调试面板 |
| 日志 | Flask-Logging | 日志管理 |
选型决策矩阵
| 场景 | 推荐框架 | 原因 |
|---|---|---|
| AI 应用后端 | FastAPI | LangChain/LlamaIndex 原生支持 |
| 微服务 | Flask / FastAPI | 轻量、灵活 |
| 内容管理 | Django | 自带 Admin、完整生态 |
| 快速原型 | Flask | 最灵活、最快上手 |
| RESTful API | FastAPI | 类型安全、自动文档 |
| 实时应用 | 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 官方文档 | https://flask.palletsprojects.com |
| Flask 扩展索引 | https://flask.palletsprojects.com/extensions/ |
| Pallets Projects | https://palletsprojects.com |
| Werkzeug 文档 | https://werkzeug.palletsprojects.com |
社区资源
| 资源 | 说明 |
|---|---|
| Flask GitHub | 65k+ Stars |
| Full Stack Python Flask | 全面的教程集合 |
| Flask Discord | 社区交流 |
推荐学习路径
-
入门阶段:
- Flask 官方教程
- Miguel Grinberg 的《Flask Web Development》
-
进阶阶段:
- 大规模应用架构
- 微服务设计模式
- 性能优化
-
实战阶段:
- 构建完整的 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最佳实践清单
- 应用工厂:使用工厂模式创建应用,便于测试。
- 蓝图:按功能模块划分蓝图,保持代码组织。
- 配置分离:开发、测试、生产环境配置分离。
- 扩展初始化:在扩展模块中统一初始化扩展。
- Schema 验证:使用 Marshmallow 或 Pydantic 验证输入。
- 错误处理:统一错误处理,提供一致的 API 响应。
- 日志记录:使用 Flask 内置日志系统。
- 数据库会话:确保在每个请求结束时提交或回滚。
- 安全:使用安全的密码哈希、CORS、CSRF 保护。
- 测试:编写单元测试和集成测试。
与其他框架对比
Flask vs FastAPI
| 特性 | Flask | FastAPI |
|---|---|---|
| 性能 | 中等 | 极高 |
| 异步 | 需要扩展 | 原生支持 |
| 数据验证 | Marshmallow | Pydantic |
| 自动文档 | 需要扩展 | 内置 |
| 类型 | 弱类型 | 强类型 |
| 适用场景 | 轻量应用 | 现代 API |
Flask vs Django
| 特性 | Flask | Django |
|---|---|---|
| 架构 | 微框架 | 全功能 |
| ORM | SQLAlchemy | Django ORM |
| Admin | 无 | 内置 |
| 认证 | 需要扩展 | 内置 |
| 表单 | WTForms | Django Forms |
| 适用场景 | 轻量/微服务 | 企业级全栈 |
Flask vs Express
| 特性 | Flask | Express |
|---|---|---|
| 语言 | Python | JavaScript |
| 生态 | Python 生态 | Node.js 生态 |
| 性能 | 中等 | 高 |
| 异步 | 需要扩展 | 原生支持 |
| 模板 | Jinja2 | EJS/Handlebars |
| 适用场景 | Python 团队 | JavaScript 团队 |
SUCCESS
本文档全面覆盖了 Flask 框架的核心知识,包括 Flask 3 新特性、路由系统、Blueprint 模块化、扩展生态及微服务架构设计。Flask 以其简约灵活的设计理念,在微服务和轻量级应用场景下保持着强劲的生命力,是 vibecoding 技术栈中不可或缺的 Python 后端选择。