项目架构重构(Blueprints):将代码模块化拆分

📂 所属阶段:第四阶段 — 实战演练(道满博客)
🔗 相关章节:路由(Routing)艺术 · Flask-Login 实战


1. 为什么需要 Blueprints?

1.1 无蓝图的问题

app.py(单文件,5000 行)😱
├── 首页路由
├── 登录路由
├── 注册路由
├── 文章列表路由
├── 文章详情路由
├── 文章发布路由
├── 评论路由
├── 搜索路由
├── 管理后台路由
├── API 路由
└── 错误处理

1.2 蓝图的优势

蓝图 = 模块化功能单元
每个蓝图管理自己的一组路由、模板和静态文件

蓝图拆分后:
├── app/
│   ├── __init__.py         # 应用工厂
│   ├── extensions.py        # 扩展
│   ├── main/               # 主蓝图
│   │   ├── __init__.py
│   │   ├── routes.py
│   │   └── templates/
│   │       └── main/
│   ├── auth/               # 认证蓝图
│   │   ├── __init__.py
│   │   ├── routes.py
│   │   └── templates/
│   │       └── auth/
│   ├── articles/           # 文章蓝图
│   │   ├── __init__.py
│   │   ├── routes.py
│   │   └── templates/
│   │       └── articles/
│   ├── admin/               # 管理后台蓝图
│   │   ├── __init__.py
│   │   └── routes.py
│   └── api/                 # API 蓝图
│       ├── __init__.py
│       └── routes.py
└── run.py

2. 创建蓝图

2.1 主蓝图(首页、关于页)

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

main_bp = Blueprint("main", __name__)

from . import routes
# app/main/routes.py
from flask import render_template
from app.main import main_bp

@main_bp.route("/")
def index():
    return render_template("main/index.html")

@main_bp.route("/about")
def about():
    return render_template("main/about.html")

3. 认证蓝图

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

auth_bp = Blueprint("auth", __name__, url_prefix="/auth")

from . import routes
# app/auth/routes.py
from flask import render_template, redirect, url_for, flash, request
from flask_login import login_user, logout_user, login_required, current_user
from werkzeug.security import generate_password_hash, check_password_hash
from app.auth import auth_bp
from app.extensions import db
from app.models import User
from app.forms import LoginForm, RegisterForm

@auth_bp.route("/login", methods=["GET", "POST"])
def login():
    if current_user.is_authenticated:
        return redirect(url_for("main.index"))
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if user and user.check_password(form.password.data):
            login_user(user, remember=form.remember_me.data)
            return redirect(url_for("main.index"))
        flash("邮箱或密码错误", "danger")
    return render_template("auth/login.html", form=form)

@auth_bp.route("/logout")
@login_required
def logout():
    logout_user()
    flash("已退出登录。", "info")
    return redirect(url_for("auth.login"))

4. 文章蓝图

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

articles_bp = Blueprint("articles", __name__, url_prefix="/articles")

from . import routes
# app/articles/routes.py
from flask import render_template, redirect, url_for, flash, abort, request
from flask_login import login_required, current_user
from app.articles import articles_bp
from app.extensions import db
from app.models import Post, Category
from app.forms import ArticleForm

@articles_bp.route("/")
def index():
    page = request.args.get("page", 1, type=int)
    pagination = Post.query.filter_by(is_published=True)\
        .order_by(Post.created_at.desc())\
        .paginate(page=page, per_page=10, error_out=False)
    return render_template("articles/index.html", pagination=pagination)

@articles_bp.route("/<int:post_id>")
def detail(post_id):
    post = Post.query.get_or_404(post_id)
    post.views += 1
    db.session.commit()
    return render_template("articles/detail.html", post=post)

@articles_bp.route("/create", methods=["GET", "POST"])
@login_required
def create():
    form = ArticleForm()
    if form.validate_on_submit():
        post = Post(
            title=form.title.data,
            content=form.content.data,
            summary=form.summary.data,
            author=current_user,
        )
        if form.is_published.data:
            post.is_published = True
        db.session.add(post)
        db.session.commit()
        flash("文章发布成功!", "success")
        return redirect(url_for("articles.detail", post_id=post.id))
    return render_template("articles/create.html", form=form)

5. 在应用工厂中注册蓝图

# app/__init__.py
from flask import Flask
from app.extensions import db, login_manager

def create_app():
    app = Flask(__name__)

    # 初始化扩展
    db.init_app(app)
    login_manager.init_app(app)

    # 注册蓝图
    from app.main import main_bp
    from app.auth import auth_bp
    from app.articles import articles_bp

    app.register_blueprint(main_bp)
    app.register_blueprint(auth_bp)
    app.register_blueprint(articles_bp)

    # 用户加载器
    @login_manager.user_loader
    def load_user(user_id):
        return db.session.get(User, int(user_id))

    return app

6. 小结

# 蓝图速查

# 创建蓝图
bp = Blueprint("name", __name__, url_prefix="/prefix")

# 注册路由
@bp.route("/path")
def handler(): ...

# 注册到应用
app.register_blueprint(bp)

# url_for 生成 URL
url_for("name.handler")  # name = Blueprint 的第一个参数

💡 最佳实践:每个蓝图对应一个功能模块,有自己的路由、模板和静态文件。蓝图之间通过 url_for("other_bp.handler") 相互引用。


🔗 扩展阅读