前四章我们都在一个 app.py 里写路由,几十行还能看懂,上百行就成一团乱麻。这一章教你用蓝图(Blueprint)把路由按业务拆开、用配置类区分开发与生产环境、用应用工厂 create_app 把整个项目「装配」起来。学完,你会得到一个真正像样的后端工程目录——这也是你后面所有章节的落脚点。
当路由越来越多,全写在一个 app.py 里就成了「面条代码」。蓝图(Blueprint)把一组相关路由打包成 user.py / article.py 等模块,主程序只负责把它们注册进来。
from flask import Flask, jsonify, request
app = Flask(__name__)
@app.route("/api/user/login", methods=["POST"])
def login():
...
return jsonify({"code": 200, "msg": "登录成功", "data": {}})
@app.route("/api/user/profile", methods=["GET"])
def profile():
...
@app.route("/api/article/list", methods=["GET"])
def article_list():
...
@app.route("/api/article/detail/<int:id>", methods=["GET"])
def article_detail(id):
...
@app.route("/api/comment/list", methods=["GET"])
def comment_list():
...
# …… 还有 13 个接口,全部挤在这一个文件里
蓝图(Blueprint)把一组相关路由打包成一个模块(像抽屉),但它自己不能独立运行,必须 register_blueprint 注册进 app 才生效。
from flask import Blueprint, jsonify, request
# ① 创建蓝图:名称、所在模块、URL 前缀
user_bp = Blueprint("user", __name__, url_prefix="/api/user")
# ② 用 user_bp 代替 app 来注册路由,路径只写前缀后面的部分
@user_bp.route("/login", methods=["POST"])
def login():
data = request.get_json()
# …… 校验用户名密码(第 8 章讲 JWT)
return jsonify({"code": 200, "msg": "登录成功", "data": {}})
@user_bp.route("/profile", methods=["GET"])
def get_profile():
return jsonify({"code": 200, "msg": "success", "data": {"id": 3, "nickname": "张三"}})
# app/api/article.py
from flask import Blueprint, jsonify
article_bp = Blueprint("article", __name__, url_prefix="/api/article")
@article_bp.route("/list", methods=["GET"])
def article_list():
return jsonify({"code": 200, "msg": "success", "data": {"list": [], "total": 0}})
# app/api/comment.py
from flask import Blueprint, jsonify
comment_bp = Blueprint("comment", __name__, url_prefix="/api/comment")
@comment_bp.route("/list", methods=["GET"])
def comment_list():
return jsonify({"code": 200, "msg": "success", "data": {"list": [], "total": 0}})
# app/api/favorite.py
from flask import Blueprint, jsonify
favorite_bp = Blueprint("favorite", __name__, url_prefix="/api/favorite")
@favorite_bp.route("/toggle", methods=["POST"])
def toggle_favorite():
return jsonify({"code": 200, "msg": "操作成功", "data": {"favorited": True}})
from flask import Flask
from app.api.user import user_bp
from app.api.article import article_bp
from app.api.comment import comment_bp
from app.api.favorite import favorite_bp
app = Flask(__name__)
# 把每个蓝图挂到应用上
app.register_blueprint(user_bp)
app.register_blueprint(article_bp)
app.register_blueprint(comment_bp)
app.register_blueprint(favorite_bp)
if __name__ == "__main__":
app.run(debug=True)
配置按「基类 + 子类」分层:公共配置放 Config 基类,DevConfig / ProdConfig 各自继承并覆盖(DEBUG、数据库地址等)。用 app.config.from_object() 按环境加载。
import os
class Config:
"""所有环境共用的基础配置"""
SECRET_KEY = os.environ.get("SECRET_KEY", "dev-secret-key")
JSON_AS_ASCII = False # 响应里的中文不转成 \uXXXX
SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevConfig(Config):
"""开发环境:本地数据库、开调试"""
DEBUG = True
SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:123456@127.0.0.1:3306/hiwen?charset=utf8mb4"
class ProdConfig(Config):
"""生产环境:密码从环境变量读,绝不明文写死"""
DEBUG = False
SECRET_KEY = os.environ["SECRET_KEY"] # 没有就启动报错,早暴露早修
SQLALCHEMY_DATABASE_URI = os.environ.get(
"DATABASE_URL",
"mysql+pymysql://user:pass@127.0.0.1:3306/hiwen?charset=utf8mb4",
)
# 环境名 → 配置类 的映射表,供 create_app 按名取用
config_map = {
"development": DevConfig,
"production": ProdConfig,
"default": DevConfig,
}
from flask import Flask
from config import config_map
def create_app(env: str = "development") -> Flask:
app = Flask(__name__)
# 一行搞定:把配置类的大写属性批量灌进 app.config
app.config.from_object(config_map[env])
print("当前数据库:", app.config["SQLALCHEMY_DATABASE_URI"])
print("调试模式:", app.config["DEBUG"])
return app
if __name__ == "__main__":
create_app("development")
用「应用工厂」create_app() 代替全局 app 单例:调用它才真正创建并组装好 app(加载配置 → 初始化扩展 → 注册蓝图)。这样同一份代码既能用开发配置、也能用测试配置反复造出不同的 app。
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_cors import CORS
# 只创建「扩展对象」,不绑定任何 app —— 这是避免循环导入的关键
db = SQLAlchemy()
migrate = Migrate()
cors = CORS()
from flask import Flask
from config import config_map
from .extensions import db, migrate, cors
def create_app(env: str = "development") -> Flask:
app = Flask(__name__)
# 1. 加载配置
app.config.from_object(config_map.get(env, config_map["default"]))
# 2. 初始化扩展:把「裸奔」的扩展对象绑到这个 app 上
db.init_app(app)
migrate.init_app(app, db)
cors.init_app(app)
# 3. 注册蓝图(注意:在函数内部 import,避免循环导入)
from .api.user import user_bp
from .api.article import article_bp
from .api.comment import comment_bp
from .api.favorite import favorite_bp
app.register_blueprint(user_bp)
app.register_blueprint(article_bp)
app.register_blueprint(comment_bp)
app.register_blueprint(favorite_bp)
return app
from app import create_app
# 从环境变量读环境名,默认开发环境
import os
app = create_app(os.environ.get("FLASK_ENV", "development"))
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000, debug=True)
tree hiflask -L 3 -I "__pycache__"
打开配套的《古典文学网 · 后端 API 接口文档》,18 个接口其实天然分成四类。按业务划蓝图,项目结构立刻清晰。
# 四个蓝图,四份前缀,各管一摊接口
user_bp = Blueprint("user", __name__, url_prefix="/api/user")
article_bp = Blueprint("article", __name__, url_prefix="/api/article")
comment_bp = Blueprint("comment", __name__, url_prefix="/api/comment")
favorite_bp = Blueprint("favorite", __name__, url_prefix="/api/favorite")
# user_bp → /register /login /profile /avatar /logout
# article_bp → /list /recommend /detail/<id> /like
# comment_bp → /list /add /like /<id>(DELETE)
# favorite_bp → /toggle /list
# 第 1 题:app/api/banner.py
from flask import Blueprint, jsonify
banner_bp = Blueprint("banner", __name__, url_prefix="/api/banner")
@banner_bp.route("/list", methods=["GET"])
def banner_list():
return jsonify({"code": 200, "msg": "success", "data": []})
# 在主程序里注册
# app.register_blueprint(banner_bp)
# 第 2 题:config.py 里追加
class TestingConfig(Config):
DEBUG = True
TESTING = True
SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:" # 内存数据库
# 第 3 题:红影赏析(电影主题,做法完全一致)
# ① 四个蓝图
movie_bp = Blueprint("movie", __name__, url_prefix="/api/movie") # 影片
review_bp = Blueprint("review", __name__, url_prefix="/api/review") # 影评
fav_bp = Blueprint("favorite", __name__, url_prefix="/api/favorite") # 收藏
user_bp = Blueprint("user", __name__, url_prefix="/api/user") # 用户
# ② 在 create_app() 里注册
# app.register_blueprint(movie_bp)
# app.register_blueprint(review_bp)
# app.register_blueprint(fav_bp)
# app.register_blueprint(user_bp)
第 3 题的关键:蓝图划分的依据是「业务归属」,不是「网站主题」。古典文学网分成「作品/书评/收藏/用户」,红影赏析分成「影片/影评/收藏/用户」——换的是模块名,不变的是「同一份前缀 + 同一类业务 = 一个蓝图」这条原则。