Slashrun
100.37M · 2026-04-09
之前我们一起学习了Flask的基础知识,搭建了简单的Web应用。但说实话,你有没有遇到这种情况:
app.py就变成了"代码大杂烩",找一段路由得用搜索功能如果你有这些困扰,那么恭喜你来对地方了!今天我们要深入学习的Flask蓝图(Blueprint),就是解决这些问题的"终极武器"。
想象一下,你要建造一座大型图书馆:
没有蓝图的情况:
所有书架、借阅台、阅读区都放在一个巨大的房间里。儿童读物、科技文献、文学艺术书籍全部混在一起。管理员要找一个特定领域的书,得从成千上万本书里慢慢翻找。
使用蓝图的情况:
每个区域有自己的规则、资源和工作人员,可以独立运营和维护。
Flask蓝图就是这个"区域划分"机制!它让你能把一个大型Flask应用拆分成多个模块化的组件,每个组件专注一个功能领域。
简单来说,蓝图就是一个未实例化的Flask应用。它定义了路由、视图函数、模板目录、静态文件等,但还没有绑定到具体的应用实例。
你可以先创建多个蓝图,最后在主应用中像"搭积木"一样把它们组装起来。
让我们从最简单的例子开始。假设我们要创建一个博客系统,先来建一个处理用户认证的蓝图:
# blueprints/auth/__init__.py
from flask import Blueprint, render_template, redirect, url_for, flash, request
from flask_login import login_user, logout_user, login_required, current_user
from extensions import db
from models import User
# 创建认证蓝图
auth_bp = Blueprint(
'auth', # 蓝图名称(必须是唯一的)
__name__, # 蓝图所在的模块
template_folder='templates', # 蓝图专属模板目录
static_folder='static' # 蓝图专属静态文件目录
)
@auth_bp.route('/register', methods=['GET', 'POST'])
def register():
"""用户注册功能"""
if request.method == 'POST':
username = request.form.get('username')
email = request.form.get('email')
password = request.form.get('password')
# 验证输入是否完整
if not username or not email or not password:
flash('请填写所有必填字段', 'error')
return redirect(url_for('auth.register'))
# 检查用户名是否已存在
if User.query.filter_by(username=username).first():
flash('用户名已存在', 'error')
return redirect(url_for('auth.register'))
# 检查邮箱是否已存在
if User.query.filter_by(email=email).first():
flash('邮箱已存在', 'error')
return redirect(url_for('auth.register'))
# 创建新用户
user = User(username=username, email=email)
user.set_password(password)
db.session.add(user)
db.session.commit()
flash('注册成功!请登录', 'success')
return redirect(url_for('auth.login'))
return render_template('auth/register.html')
@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
"""用户登录功能"""
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
user = User.query.filter_by(username=username).first()
if user and user.check_password(password):
login_user(user, remember=True)
flash('登录成功!', 'success')
return redirect(url_for('main.index'))
else:
flash('用户名或密码错误', 'error')
return render_template('auth/login.html')
@auth_bp.route('/logout')
@login_required
def logout():
"""用户注销功能"""
logout_user()
flash('您已成功注销', 'info')
return redirect(url_for('main.index'))
看到了吗?我们的认证功能被完美地封装在一个独立的蓝图里。代码逻辑清晰,职责明确。
创建了蓝图之后,我们还需要把它"安装"到主应用中:
# app/__init__.py
from flask import Flask
from blueprints.auth import auth_bp
from blueprints.blog import blog_bp
from blueprints.api import api_bp
def create_app():
"""应用工厂函数"""
app = Flask(__name__)
# 加载配置
app.config.from_object('config.DevelopmentConfig')
# 初始化扩展
from extensions import db, login_manager, csrf
db.init_app(app)
login_manager.init_app(app)
csrf.init_app(app)
# 注册蓝图(这是关键步骤!)
app.register_blueprint(auth_bp, url_prefix='/auth')
app.register_blueprint(blog_bp, url_prefix='/blog')
app.register_blueprint(api_bp, url_prefix='/api/v1')
# 注册主蓝图(不需要URL前缀)
from blueprints.main import main_bp
app.register_blueprint(main_bp)
return app
注意url_prefix参数:它会给该蓝图下的所有路由添加统一的前缀。这样:
/auth/login → 登录页面/auth/register → 注册页面/auth/logout → 注销功能现在让我们把理论变成实践,一步步构建一个完整的博客系统。我们会创建四个蓝图:
一个良好的项目结构是成功的一半。下面是我们的项目目录布局:
flask-blog-system/
├── app/ # 应用主目录
│ ├── __init__.py # 应用工厂函数
│ └── blueprints/ # 蓝图目录
│ ├── auth/ # 认证蓝图
│ │ ├── __init__.py
│ │ ├── templates/
│ │ └── static/
│ ├── blog/ # 博客蓝图
│ │ ├── __init__.py
│ │ ├── templates/
│ │ └── static/
│ ├── api/ # API蓝图
│ │ ├── __init__.py
│ │ └── static/
│ └── main/ # 主蓝图
│ ├── __init__.py
│ └── templates/
├── models.py # 数据模型定义
├── extensions.py # 扩展实例
├── config.py # 配置文件
├── requirements.txt # 依赖包列表
└── run.py # 启动文件
数据模型是应用的基础。我们先定义用户和文章两个核心模型:
# models.py
from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
db = SQLAlchemy()
class User(UserMixin, db.Model):
"""用户模型"""
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, nullable=False, index=True)
email = db.Column(db.String(120), unique=True, nullable=False, index=True)
password_hash = db.Column(db.String(255))
created_at = db.Column(db.DateTime, default=datetime.utcnow)
is_active = db.Column(db.Boolean, default=True)
# 关系:一个用户可以有多篇文章
posts = db.relationship('Post', backref='author', lazy='dynamic',
cascade='all, delete-orphan')
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)
def to_dict(self):
"""转换为字典,方便API返回"""
return {
'id': self.id,
'username': self.username,
'email': self.email,
'created_at': self.created_at.isoformat() if self.created_at else None,
'is_active': self.is_active
}
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)
content = db.Column(db.Text, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
def to_dict(self):
"""转换为字典,方便API返回"""
return {
'id': self.id,
'title': self.title,
'content': self.content[:200] + '...' if len(self.content) > 200 else self.content,
'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
'author': self.author.username if self.author else None
}
def __repr__(self):
return f'<Post {self.title}>'
博客蓝图是系统的核心,负责文章的增删改查:
# blueprints/blog/__init__.py
from flask import Blueprint, render_template, redirect, url_for, flash, request, abort
from flask_login import login_required, current_user
from extensions import db
from models import Post
# 创建博客蓝图
blog_bp = Blueprint(
'blog',
__name__,
template_folder='templates',
static_folder='static',
url_prefix='/blog'
)
@blog_bp.route('/')
def index():
"""博客首页 - 显示所有文章(支持分页)"""
page = request.args.get('page', 1, type=int)
per_page = 10
posts = Post.query.order_by(Post.created_at.desc()).paginate(
page=page, per_page=per_page, error_out=False
)
return render_template('blog/index.html', posts=posts)
@blog_bp.route('/post/<int:post_id>')
def view_post(post_id):
"""查看文章详情"""
post = Post.query.get_or_404(post_id)
return render_template('blog/post.html', post=post)
@blog_bp.route('/create', methods=['GET', 'POST'])
@login_required
def create_post():
"""创建新文章"""
if request.method == 'POST':
title = request.form.get('title')
content = request.form.get('content')
if not title or not content:
flash('标题和内容不能为空', 'error')
return redirect(url_for('blog.create_post'))
post = Post(title=title, content=content, user_id=current_user.id)
db.session.add(post)
db.session.commit()
flash('文章创建成功!', 'success')
return redirect(url_for('blog.view_post', post_id=post.id))
return render_template('blog/create.html')
# 更多功能:编辑、删除、搜索等...
现代Web应用通常需要提供API接口,我们可以单独创建一个API蓝图:
# blueprints/api/__init__.py
from flask import Blueprint, jsonify, request, abort
from flask_login import login_required, current_user
from extensions import db
from models import User, Post
# 创建API蓝图
api_bp = Blueprint(
'api',
__name__,
url_prefix='/api/v1'
)
@api_bp.route('/users', methods=['GET'])
def get_users():
"""获取用户列表(API版本)"""
users = User.query.all()
return jsonify({
'data': [user.to_dict() for user in users],
'count': len(users)
})
@api_bp.route('/posts', methods=['GET'])
def get_posts():
"""获取文章列表(API版本,支持分页)"""
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
posts = Post.query.order_by(Post.created_at.desc()).paginate(
page=page, per_page=per_page, error_out=False
)
return jsonify({
'data': [post.to_dict() for post in posts.items],
'pagination': {
'page': posts.page,
'per_page': posts.per_page,
'total': posts.total,
'pages': posts.pages
}
})
# 更多API端点:创建用户、创建文章、更新、删除等...
对于超大型应用,我们可以在蓝图内部再嵌套蓝图:
# blueprints/api/v1/__init__.py
from flask import Blueprint
from .users import users_bp
from .posts import posts_bp
# 创建API v1主蓝图
api_v1_bp = Blueprint('api_v1', __name__, url_prefix='/api/v1')
# 注册子蓝图
api_v1_bp.register_blueprint(users_bp, url_prefix='/users')
api_v1_bp.register_blueprint(posts_bp, url_prefix='/posts')
# 在主应用中注册
app.register_blueprint(api_v1_bp)
这样,我们的API URL结构会更加清晰:
GET /api/v1/users → 获取用户列表GET /api/v1/posts → 获取文章列表POST /api/v1/users → 创建新用户虽然蓝图是独立的,但它们有时需要共享数据。我们应该通过主应用的上下文来传递数据,而不是直接导入其他蓝图:
# blueprints/blog/__init__.py 中
@blog_bp.before_app_request
def load_blog_config():
"""在所有请求之前加载博客配置"""
from flask import g
g.blog_name = "技术博客"
g.blog_description = "分享编程知识与实战经验"
# 在其他蓝图中使用
@auth_bp.route('/profile')
def profile():
blog_name = getattr(g, 'blog_name', '默认博客')
return f"欢迎来到{blog_name}的个人资料页"
应用工厂模式是现代Flask开发的最佳实践:
# config.py
import os
from dotenv import load_dotenv
basedir = os.path.abspath(os.path.dirname(__file__))
load_dotenv(os.path.join(basedir, '.env'))
class Config:
"""基础配置"""
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key-change-in-production'
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or
'sqlite:///' + os.path.join(basedir, 'app.db')
SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevelopmentConfig(Config):
"""开发环境配置"""
DEBUG = True
SQLALCHEMY_ECHO = True
class ProductionConfig(Config):
"""生产环境配置"""
DEBUG = False
# 生产环境必须使用环境变量
SECRET_KEY = os.environ.get('SECRET_KEY')
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
config = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
然后在应用工厂中选择配置:
# app/__init__.py
def create_app(config_name='default'):
app = Flask(__name__)
# 加载指定环境的配置
app.config.from_object(config[config_name])
# 初始化扩展、注册蓝图等...
return app
这样,我们可以轻松切换开发和生产环境:
create_app('development')create_app('production')如何确定一个蓝图应该包含哪些功能?遵循"单一职责原则":
这是蓝图开发中最常见的问题。解决方案:
# 错误做法:在模块顶层导入
from blueprints.auth import auth_bp # 可能导致循环导入
# 正确做法:在函数内部导入
def register_blueprints(app):
from blueprints.auth import auth_bp # 延迟导入
app.register_blueprint(auth_bp)
蓝图可以有自己的静态文件和模板,但要注意优先级:
# 蓝图配置
bp = Blueprint(
'blog',
__name__,
template_folder='templates', # 蓝图专属模板目录
static_folder='static', # 蓝图专属静态文件目录
static_url_path='/blog/static' # 访问路径
)
# 模板查找顺序:
# 1. 应用全局的 templates/ 目录
# 2. 蓝图专属的 templates/ 目录
蓝图的注册顺序会影响路由匹配的优先级:
# 先注册的蓝图优先级更高
app.register_blueprint(auth_bp, url_prefix='/auth') # 优先级高
app.register_blueprint(blog_bp, url_prefix='/blog') # 优先级低
对于生产环境,建议使用Gunicorn作为WSGI服务器:
# 安装Gunicorn
pip install gunicorn
# 启动应用(使用4个工作进程)
gunicorn -w 4 -b 0.0.0.0:8000 "app:create_app('production')"
# nginx配置示例
server {
listen 80;
server_name your-domain.com;
location / {
proxy_pass ;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 静态文件由Nginx直接服务,提高性能
location /static {
alias /path/to/your/static/files;
expires 30d;
}
}
# 配置结构化日志
import logging
from logging.handlers import RotatingFileHandler
def configure_logging(app):
# 文件处理器(日志轮转,最大10MB,保留5个备份)
file_handler = RotatingFileHandler(
'logs/flask-blog.log',
maxBytes=1024 * 1024 * 10,
backupCount=5
)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('Flask Blog Startup')
在软件开发中,结构决定一切。一个好的架构能让:
我知道,看教程和实际动手之间总有一道鸿沟。但请相信我,今天就是你跨越这道鸿沟的最佳时机!
你的下一步行动:
auth蓝图,实现登录注册功能blog蓝图,实现文章管理如果你在实践过程中遇到问题,或者想学习更高级的Flask技巧,我为你准备了:
记住:编程不是看会的,是练会的。每一个优秀的开发者,都是从一行行代码敲出来的。
如果你觉得这篇教程对你有帮助,不妨:
好了,教程到这里就结束了。但我更希望,这不是结束,而是你Flask开发新篇章的开始!
去吧,打开你的编辑器,开始你的蓝图之旅!
P.S. 如果你在实践过程中有任何问题,或者想分享你的学习成果,欢迎随时交流。记住:每一个大项目都是由小模块组成的,今天你就迈出了模块化开发的第一步!