01-从零入门教程

知识库
知识库文档
/tech-stacks/flask/tutorial/01-从零入门教程.md

文档

Flask 入门教程 —— 从零构建 Web 应用

本章目标

  • 理解 Flask 的核心设计理念
  • 搭建完整的 Flask 项目骨架
  • 掌握路由、模板、静态文件三大基础
  • 实现一个简单的留言板应用

1. Flask 的设计哲学

Flask 遵循微内核 + 扩展的设计模式:

Flask 核心(最小集合)
├── 路由系统(werkzeug.routing)
├── 请求/响应封装(werkzeug.wrappers)
├── 模板渲染(Jinja2)
├── 开发服务器
└── 测试客户端

按需扩展(pip install 自由搭配)
├── flask-sqlalchemy    → 数据库
├── flask-login         → 认证
├── flask-admin         → 后台管理
├── flask-socketio      → WebSocket
└── ...上百个扩展

对比 Django:Django 是"大而全"的框架,自带 ORM、后台、表单、认证;Flask 是一张白纸,你可以自由选择每个组件。

2. 项目骨架

my_flask_app/
├── app/                    # 应用包
│   ├── __init__.py         # 工厂函数 create_app()
│   ├── routes.py           # 路由视图
│   ├── models.py           # 数据库模型
│   ├── forms.py            # 表单定义
│   ├── templates/          # Jinja2 模板
│   │   ├── base.html
│   │   ├── index.html
│   │   └── post.html
│   └── static/             # 静态文件
│       ├── css/
│       └── js/
├── migrations/             # 数据库迁移
├── config.py               # 配置类
├── requirements.txt        # 依赖
└── run.py                  # 启动入口

3. 应用程序工厂模式

# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

db = SQLAlchemy()
migrate = Migrate()


def create_app():
    app = Flask(__name__)
    app.config.from_object("config.Config")

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

    # 注册蓝图
    from app.routes import main_bp
    app.register_blueprint(main_bp)

    return app

为什么用工厂模式?

  • 支持多环境配置(开发/测试/生产)
  • 便于单元测试(每次 create_app() 都是全新实例)
  • 延迟初始化,避免循环导入

4. 蓝图(Blueprint)组织路由

# app/routes.py
from flask import Blueprint, render_template, request, redirect, url_for
from app.models import Message
from app import db

main_bp = Blueprint("main", __name__)


@main_bp.route("/")
def index():
    messages = Message.query.order_by(Message.created_at.desc()).all()
    return render_template("index.html", messages=messages)


@main_bp.route("/post", methods=["POST"])
def post():
    content = request.form.get("content", "").strip()
    if content:
        msg = Message(content=content)
        db.session.add(msg)
        db.session.commit()
    return redirect(url_for("main.index"))

蓝图的好处

  • 将大型应用拆分为独立的"迷你应用"
  • 每个蓝图可有自己的模板/静态文件目录
  • 支持 URL 前缀(如 /admin/api/v1

5. Jinja2 模板实战

<!-- app/templates/base.html -->
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}留言板{% endblock %}</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
    <div class="container">
        {% with messages = get_flashed_messages(with_categories=true) %}
            {% if messages %}
                {% for category, message in messages %}
                    <div class="alert alert-{{ category }}">{{ message }}</div>
                {% endfor %}
            {% endif %}
        {% endwith %}

        {% block content %}{% endblock %}
    </div>
</body>
</html>
<!-- app/templates/index.html -->
{% extends "base.html" %}
{% block title %}留言板 - 首页{% endblock %}

{% block content %}
<h1>📝 留言板</h1>

<form method="post" action="{{ url_for('main.post') }}">
    <textarea name="content" rows="3" placeholder="说点什么吧..." required></textarea>
    <button type="submit">发布</button>
</form>

<hr>

{% for msg in messages %}
    <div class="message">
        <p>{{ msg.content }}</p>
        <small>{{ msg.created_at.strftime('%Y-%m-%d %H:%M') }}</small>
    </div>
{% else %}
    <p>还没有留言,来写第一条吧!</p>
{% endfor %}
{% endblock %}

Jinja2 关键语法

语法 说明
{% block name %} 定义可被子模板替换的块
{% extends "base.html" %} 继承基础模板
{% for ... %} / {% endfor %} 循环
{% if ... %} / {% endif %} 条件判断
{% with ... %} 变量作用域
{{ variable }} 输出变量(自动 HTML 转义)
{{ | safe }} 不转义输出(慎用)
url_for() 反向生成 URL(路由改名时自动更新)

6. 使用 Flask-Login 实现用户认证

from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user

login_manager = LoginManager()
login_manager.login_view = "auth.login"


class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True, nullable=False)
    password_hash = db.Column(db.String(128), nullable=False)

    def set_password(self, password):
        from werkzeug.security import generate_password_hash
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        from werkzeug.security import check_password_hash
        return check_password_hash(self.password_hash, password)


@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))


# 保护路由
@main_bp.route("/secret")
@login_required
def secret():
    return f"只有登录用户能看到,你好 {current_user.username}!"

7. 配置管理最佳实践

# config.py
import os
from dotenv import load_dotenv

load_dotenv()  # 加载 .env 文件


class Config:
    SECRET_KEY = os.getenv("SECRET_KEY", "dev-key-change-me")
    SQLALCHEMY_DATABASE_URI = os.getenv("DATABASE_URL", "sqlite:///app.db")
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    JSON_AS_ASCII = False  # 支持中文 JSON 输出


class DevelopmentConfig(Config):
    DEBUG = True


class ProductionConfig(Config):
    DEBUG = False


class TestingConfig(Config):
    TESTING = True
    SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"

思考题

  1. 蓝图与直接 @app.route 有何区别?何时应该使用蓝图?
  2. url_for() 比硬编码 URL 有哪些优势?
  3. Flask 的"微"体现在哪里?为什么说这是优势而非劣势?
  4. 工厂模式 create_app() 相比全局 app = Flask(__name__) 有什么好处?

信息

路径
/tech-stacks/flask/tutorial/01-从零入门教程.md
更新时间
2026/5/30