3. 技术选型与架构设计
项目一大了,所有代码堆在一个 app.py 里,那感觉就像把整个工具箱塞进一个抽屉。找个路由得翻半天,改个模型怕影响别的功能。说实话,我早期几个项目就是这么干的,后来维护成本高得吓人。
Flask 蓝图就是来解决这个问题的。它允许你把不同功能模块拆成独立的文件,每个模块有自己的路由、模板、静态文件。最后在工厂函数里统一注册,各模块互不干扰。
核心思想:蓝图不是完整的应用,而是应用的"子模块"。它需要被注册到 Flask 应用实例上才能生效。
3.1 为什么需要蓝图?
先看一个反面教材。假设我们做图片管理系统,所有路由都写在 app.py 里:
# app.py — 所有功能揉在一起
from flask import Flask, request, jsonify
app = Flask(__name__)
# 用户相关
@app.route('/api/login', methods=['POST'])
def login():
pass
@app.route('/api/register', methods=['POST'])
def register():
pass
# 图片上传相关
@app.route('/api/upload', methods=['POST'])
def upload():
pass
@app.route('/api/images', methods=['GET'])
def list_images():
pass
# 相册相关
@app.route('/api/albums', methods=['GET'])
def list_albums():
pass
@app.route('/api/albums/<int:album_id>', methods=['GET'])
def get_album():
pass
if __name__ == '__main__':
app.run(debug=True)
这才十几个路由,已经有点乱了。等我们加上图片处理、标签管理、权限控制……这个文件会膨胀到上千行。你想想看,改一个功能要滚动半天,找 bug 更是噩梦。
我在一个中型电商项目里见过类似的 app.py,足足 3000 多行。每次部署前 code review,光翻文件就要 10 分钟。后来我们花了整整两天重构,拆成蓝图,世界清净了。
3.2 蓝图的基本用法
蓝图的使用分三步:定义蓝图、编写路由、注册蓝图。
第一步:定义蓝图
# app/modules/user/views.py
from flask import Blueprint
# 创建蓝图实例
# 第一个参数是蓝图名称,第二个参数是导入路径
user_bp = Blueprint('user', __name__)
第二步:编写路由
# app/modules/user/views.py
from flask import Blueprint, request, jsonify
user_bp = Blueprint('user', __name__)
@user_bp.route('/api/login', methods=['POST'])
def login():
# 登录逻辑
return jsonify({'message': '登录成功'})
@user_bp.route('/api/register', methods=['POST'])
def register():
# 注册逻辑
return jsonify({'message': '注册成功'})
第三步:注册蓝图
# app/__init__.py — 工厂函数
from flask import Flask
from app.modules.user.views import user_bp
def create_app():
app = Flask(__name__)
# 注册蓝图
app.register_blueprint(user_bp)
return app
嗯,这里要注意:蓝图的路由是相对路径。你可以在注册时加一个 url_prefix,所有路由自动加上前缀。
# 注册时指定前缀
app.register_blueprint(user_bp, url_prefix='/api/user')
# 现在路由变成:
# /api/user/login
# /api/user/register
我的习惯:在蓝图内部写相对路径(如 /login),在注册时统一加前缀。这样蓝图可以复用,换个前缀就能挂到不同位置。
3.3 项目目录结构设计
一个好的目录结构,应该让新人进来 5 分钟就能找到对应功能的代码。我个人比较推荐这种分层结构:
image_manager/ # 项目根目录
├── app/ # 应用主包
│ ├── __init__.py # 工厂函数
│ ├── extensions.py # 扩展初始化
│ ├── config.py # 配置管理
│ │
│ ├── modules/ # 功能模块
│ │ ├── user/ # 用户模块
│ │ │ ├── __init__.py
│ │ │ ├── views.py # 路由
│ │ │ ├── models.py # 数据库模型
│ │ │ ├── forms.py # 表单验证
│ │ │ └── utils.py # 工具函数
│ │ │
│ │ ├── image/ # 图片模块
│ │ │ ├── __init__.py
│ │ │ ├── views.py
│ │ │ ├── models.py
│ │ │ ├── forms.py
│ │ │ └── utils.py
│ │ │
│ │ └── album/ # 相册模块
│ │ ├── __init__.py
│ │ ├── views.py
│ │ ├── models.py
│ │ └── utils.py
│ │
│ ├── static/ # 静态文件
│ │ ├── css/
│ │ ├── js/
│ │ └── images/
│ │
│ └── templates/ # 全局模板
│ ├── base.html
│ └── components/
│
├── migrations/ # 数据库迁移
├── tests/ # 测试
│ ├── conftest.py
│ ├── test_user.py
│ └── test_image.py
│
├── .env # 环境变量
├── .gitignore
├── requirements.txt
└── run.py # 启动入口
这个结构有几个好处:
- 模块自治:每个模块有自己的 views、models、forms,改一个模块不影响其他模块
- 易于扩展:加新功能只需新建一个模块文件夹,注册蓝图即可
- 测试友好:tests 目录对应每个模块,测试文件清晰
- 配置分离:config.py 统一管理配置,.env 存放敏感信息
我曾经踩过的坑:不要把 models.py 放在全局目录下。所有模型都写在一个文件里,后期会变成"万用模型",牵一发而动全身。每个模块自己的模型自己管,模块间通过外键关联即可。
3.4 蓝图注册与工厂函数
工厂函数是 Flask 应用的"组装车间"。它负责创建应用实例、加载配置、注册扩展和蓝图。
# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
# 延迟初始化扩展
db = SQLAlchemy()
migrate = Migrate()
def create_app(config_name='default'):
app = Flask(__name__)
# 加载配置
app.config.from_object(f'app.config.{config_name.capitalize()}Config')
# 初始化扩展
db.init_app(app)
migrate.init_app(app, db)
# 注册蓝图
from app.modules.user.views import user_bp
from app.modules.image.views import image_bp
from app.modules.album.views import album_bp
app.register_blueprint(user_bp, url_prefix='/api/user')
app.register_blueprint(image_bp, url_prefix='/api/image')
app.register_blueprint(album_bp, url_prefix='/api/album')
return app
启动文件就很简单了:
# run.py
from app import create_app
app = create_app('development')
if __name__ == '__main__':
app.run(debug=True)
小技巧:如果蓝图太多,可以建一个 blueprints.py 统一管理注册逻辑,工厂函数里只调用一个函数。
3.5 蓝图的高级用法
蓝图不只是路由分组,它还能做更多事情。
子域名支持:
# 蓝图绑定到子域名
admin_bp = Blueprint('admin', __name__, subdomain='admin')
# 注册时指定子域名
app.register_blueprint(admin_bp, subdomain='admin')
# 访问 admin.example.com 会匹配这个蓝图
模板和静态文件:
# 蓝图可以有自己的模板和静态文件目录
user_bp = Blueprint('user', __name__,
template_folder='templates',
static_folder='static',
static_url_path='/user-static')
# 在模板中引用蓝图静态文件
# <link rel="stylesheet" href="{{ url_for('user.static', filename='style.css') }}">
蓝图装饰器:
# 蓝图级别的 before_request
@user_bp.before_request
def check_user_login():
# 每次请求前检查用户是否登录
pass
# 蓝图级别的 error_handler
@user_bp.errorhandler(404)
def user_not_found(error):
return jsonify({'error': '用户不存在'}), 404
3.6 知识体系总览
下面这张图总结了本章的核心逻辑:
3.7 避坑指南
蓝图用起来不难,但有几个坑我替你们踩过了:
| 常见问题 | 原因 | 解决方案 |
|---|---|---|
| 路由冲突 | 不同蓝图定义了相同路径 | 注册时使用不同的 url_prefix |
| 循环导入 | 蓝图和工厂函数互相引用 | 在工厂函数内部延迟导入蓝图 |
| 模板找不到 | 蓝图模板路径配置错误 | 检查 template_folder 参数 |
| 静态文件 404 | 蓝图静态文件 URL 路径不对 | 使用 url_for('蓝图名.static') |
我曾经踩过的坑:循环导入是最隐蔽的。有一次我在 views.py 里导入了 app 实例,又在 __init__.py 里导入了 views.py,结果启动就报错。记住:蓝图里不要直接导入 app,只导入 db 等扩展实例。