4. 蓝图与模块化设计

说实话,我第一次接触 Flask 的时候,也犯过把所有路由写在一个文件里的毛病。那时候项目小,也就几十个接口,觉得挺爽的。直到有一天,我打开那个 app.py 文件,发现它已经膨胀到 2000 多行……

嗯,从那以后,我就再也不敢这么干了。

你想想看,一个配方管理系统,如果所有路由、模型、配置都堆在一个文件里,后期维护简直就是噩梦。这时候,蓝图(Blueprint) 就是来救场的。

为什么需要蓝图?

蓝图说白了,就是 Flask 提供的一种「模块化」方案。它允许你把不同功能的路由、视图、静态文件、模板组织成独立的模块,然后再注册到主应用中。

我个人习惯把蓝图理解为「小应用」。它有自己的路由前缀、模板文件夹、静态文件路径,但它不是独立运行的,必须挂载到主应用上。

举个例子,配方管理系统的核心功能无非就是:

  • 配方管理:增删改查配方
  • 原料管理:管理原料库存、单位
  • 分类管理:对配方或原料进行分类

这三个功能之间虽然有数据关联,但逻辑上是独立的。把它们拆成三个蓝图,每个蓝图只关心自己的事,代码清晰度直接拉满。

核心优势:
  • 解耦:每个模块独立开发、独立测试
  • 复用:蓝图可以跨项目复用(比如用户认证蓝图)
  • 可维护:修改配方模块不会影响原料模块
  • 团队协作:不同成员可以同时开发不同蓝图

蓝图注册与路由分发

蓝图的使用其实就三步:定义蓝图 → 编写路由 → 注册到应用。我带你走一遍流程。

1. 定义蓝图

在 app/recipes/ 目录下创建 __init__.py,定义蓝图对象:

from flask import Blueprint

# 第一个参数是蓝图名称,第二个参数是导入名(通常用 __name__)
# url_prefix 是路由前缀,所有该蓝图下的路由都会自动加上 /recipes
recipes_bp = Blueprint('recipes', __name__, url_prefix='/recipes')

2. 编写路由

在 app/recipes/views.py 中编写视图函数:

from . import recipes_bp

@recipes_bp.route('/')
def list_recipes():
    return "返回所有配方列表"

@recipes_bp.route('/<int:recipe_id>')
def get_recipe(recipe_id):
    return f"返回配方 {recipe_id} 的详细信息"

@recipes_bp.route('/create', methods=['POST'])
def create_recipe():
    return "创建新配方"

3. 注册到主应用

在 app/__init__.py 或 app.py 中注册蓝图:

from flask import Flask
from app.recipes import recipes_bp

def create_app():
    app = Flask(__name__)
    
    # 注册蓝图
    app.register_blueprint(recipes_bp)
    
    return app

这样,所有 /recipes/ 开头的请求都会被分发到 recipes_bp 处理。路由分发逻辑清晰,一目了然。

小技巧: 我习惯在蓝图定义时指定 template_folder 和 static_folder,这样每个蓝图可以有自己的模板和静态文件,互不干扰。

将配方、原料、分类拆分为独立模块

好,理论讲完了,咱们直接动手。下面是我在项目中常用的目录结构:

recipe_manager/
├── app/
│   ├── __init__.py          # 应用工厂
│   ├── recipes/             # 配方模块
│   │   ├── __init__.py      # 定义蓝图
│   │   ├── views.py         # 路由视图
│   │   ├── models.py        # 数据模型
│   │   └── forms.py         # 表单(如果有)
│   ├── ingredients/         # 原料模块
│   │   ├── __init__.py
│   │   ├── views.py
│   │   ├── models.py
│   │   └── forms.py
│   ├── categories/          # 分类模块
│   │   ├── __init__.py
│   │   ├── views.py
│   │   ├── models.py
│   │   └── forms.py
│   └── templates/           # 全局模板
│       ├── base.html
│       └── ...
├── config.py
└── run.py

每个模块内部结构一致,开发起来非常顺手。下面我分别展示三个蓝图的定义。

配方蓝图(recipes)

# app/recipes/__init__.py
from flask import Blueprint

recipes_bp = Blueprint(
    'recipes',
    __name__,
    url_prefix='/recipes',
    template_folder='templates',
    static_folder='static'
)

原料蓝图(ingredients)

# app/ingredients/__init__.py
from flask import Blueprint

ingredients_bp = Blueprint(
    'ingredients',
    __name__,
    url_prefix='/ingredients',
    template_folder='templates',
    static_folder='static'
)

分类蓝图(categories)

# app/categories/__init__.py
from flask import Blueprint

categories_bp = Blueprint(
    'categories',
    __name__,
    url_prefix='/categories',
    template_folder='templates',
    static_folder='static'
)

注册所有蓝图

# app/__init__.py
from flask import Flask
from app.recipes import recipes_bp
from app.ingredients import ingredients_bp
from app.categories import categories_bp

def create_app():
    app = Flask(__name__)
    
    # 注册蓝图
    app.register_blueprint(recipes_bp)
    app.register_blueprint(ingredients_bp)
    app.register_blueprint(categories_bp)
    
    return app

这样,三个模块就完全独立了。每个模块的开发者只需要关注自己的蓝图,不需要关心其他模块的路由冲突。

避坑指南: 我曾经在项目里犯过一个低级错误——两个蓝图使用了相同的 url_prefix,结果路由冲突,请求总是跑到错误的视图上。记住,每个蓝图的 url_prefix 必须是唯一的。

蓝图模块化结构图

下面这张图展示了蓝图模块化的核心逻辑:

蓝图模块化结构图 Flask 主应用 register_blueprint() recipes_bp /recipes/ ingredients_bp /ingredients/ categories_bp /categories/ 每个蓝图独立管理自己的路由、模板、静态文件 通过 url_prefix 实现路由隔离,互不干扰 recipes 模块 __init__.py views.py models.py ingredients 模块 __init__.py views.py models.py categories 模块 __init__.py views.py models.py

蓝图 vs 传统单文件路由

为了让你更直观地感受蓝图的优势,我整理了一个对比表格:

对比维度 单文件路由 蓝图模块化
代码组织 所有路由堆在一个文件 按功能拆分为独立模块
路由前缀 手动拼接 URL 前缀 蓝图自动处理 url_prefix
团队协作 容易产生合并冲突 各模块独立开发,互不影响
可测试性 需要启动整个应用才能测试 可以单独测试每个蓝图
复用性 几乎无法复用 蓝图可以跨项目复用

我个人强烈建议,从项目一开始就采用蓝图模块化设计。哪怕你现在觉得项目小、没必要,等后面功能一多,你就知道蓝图的好了。

经验之谈: 我习惯在蓝图内部再按功能拆分子模块,比如 recipes/views/ 下再分 list.py、detail.py、create.py 等。这样每个文件只负责一个功能点,查找和修改都特别快。