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 知识体系总览

下面这张图总结了本章的核心逻辑:

Flask 蓝图与项目结构知识体系 create_app() 工厂函数 app.register_blueprint() 注册蓝图 user_bp 用户模块 image_bp 图片模块 album_bp 相册模块 模块内部文件 views.py — 路由 models.py — 模型 forms.py — 表单 utils.py — 工具 模块内部文件 views.py — 路由 models.py — 模型 forms.py — 表单 utils.py — 工具 模块内部文件 views.py — 路由 models.py — 模型 forms.py — 表单 utils.py — 工具 核心原则 模块自治:每个模块独立管理自己的路由、模型、表单 统一注册:工厂函数集中注册所有蓝图

3.7 避坑指南

蓝图用起来不难,但有几个坑我替你们踩过了:

常见问题 原因 解决方案
路由冲突 不同蓝图定义了相同路径 注册时使用不同的 url_prefix
循环导入 蓝图和工厂函数互相引用 在工厂函数内部延迟导入蓝图
模板找不到 蓝图模板路径配置错误 检查 template_folder 参数
静态文件 404 蓝图静态文件 URL 路径不对 使用 url_for('蓝图名.static')

我曾经踩过的坑:循环导入是最隐蔽的。有一次我在 views.py 里导入了 app 实例,又在 __init__.py 里导入了 views.py,结果启动就报错。记住:蓝图里不要直接导入 app,只导入 db 等扩展实例。