Skip to content

Flask

Flask 是一个轻量级的 Python Web 框架,被称为"微框架",因为它保持核心简单但可扩展。


🚀 学习路线图 (Roadmap)

不要试图一口气学完所有东西,按以下步骤循序渐进:

  1. 基础准备 (Prerequisites)
  • Python 基础: 必须熟悉函数、类(Class)、模块导入、装饰器(Decorator,Flask 路由的核心)。
  • HTTP 基础: 了解什么是 GET/POST 请求,什么是状态码(200, 404, 500)。
  • 前端基础: 至少能看懂简单的 HTML 和 CSS。
  1. Flask 入门 (The Basics)
  • Hello World: 安装 Flask,运行第一个“Hello World”页面。
  • 路由 (Routing): 学习 @app.route,如何处理动态 URL(如 /user/)。
  • 模板引擎 (Jinja2): 学习如何把 Python 变量传给 HTML,使用 和 {% if/for %} 循环。
  • 请求与响应: 使用 request 对象获取表单数据,使用 jsonify 返回 JSON 数据。
  1. 进阶核心 (Core Skills)
  • 表单处理: 学习 Flask-WTF 扩展,进行表单验证(必填、邮箱格式等)。
  • 数据库操作: 学习 Flask-SQLAlchemy(ORM)。不要直接写 SQL 语句,学会定义模型(Model)和进行 CRUD(增删改查)操作。
  • 用户认证: 学习 Flask-Login,实现注册、登录、登出、记住我功能。
  1. 架构与部署 (Architecture & Deployment)
  • 项目结构: 当代码超过 100 行时,学习使用 Blueprints(蓝图) 将项目拆分为多个文件/模块。
  • 配置管理: 区分开发环境(Development)和生产环境(Production)的配置。
  • 部署: 学习使用 Gunicorn + Nginx 在 Linux 服务器上部署,或者使用 Docker。

📦 快速开始

安装 Flask

bash
# 创建虚拟环境
python -m venv venv

# 激活虚拟环境
source venv/bin/activate  # macOS/Linux
# 或
venv\Scripts\activate  # Windows

# 安装 Flask
pip install flask

Hello World

python
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello, World!'

if __name__ == '__main__':
    app.run(debug=True)

运行应用:

bash
python app.py
# 访问 http://127.0.0.1:5000

🛣️ 路由 (Routing)

基本路由

python
@app.route('/')
def index():
    return '首页'

@app.route('/about')
def about():
    return '关于我们'

@app.route('/user/<username>')
def show_user_profile(username):
    return f'用户: {username}'

@app.route('/post/<int:post_id>')
def show_post(post_id):
    return f'文章 ID: {post_id}'

@app.route('/path/<path:subpath>')
def show_subpath(subpath):
    return f'子路径: {subpath}'

HTTP 方法

python
from flask import request

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        return '登录处理'
    return '登录页面'

@app.route('/api/data', methods=['GET', 'POST', 'PUT', 'DELETE'])
def api_data():
    if request.method == 'GET':
        return jsonify({'message': 'GET 请求'})
    elif request.method == 'POST':
        return jsonify({'message': 'POST 请求'})
    # ...

URL 构建

python
from flask import url_for, redirect

@app.route('/')
def index():
    return f'首页URL: {url_for("index")}'

@app.route('/user/<name>')
def user(name):
    return f'用户: {name}'

@app.route('/redirect-example')
def redirect_example():
    return redirect(url_for('user', name='张三'))

📄 模板引擎 (Jinja2)

基本语法

html
<!-- 变量 -->
<p>欢迎, {{ username }}!</p>

<!-- 条件语句 -->
{% if user %}
    <p>欢迎, {{ user.name }}!</p>
{% else %}
    <p>请先登录</p>
{% endif %}

<!-- 循环 -->
<ul>
{% for item in items %}
    <li>{{ item }}</li>
{% endfor %}
</ul>

<!-- 过滤器 -->
{{ name|upper }}
{{ price|round(2) }}
{{ content|safe }}  <!-- 渲染 HTML -->

在 Flask 中使用模板

python
from flask import render_template

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/user/<name>')
def user(name):
    return render_template('user.html', name=name, items=['苹果', '香蕉', '橙子'])

模板继承

base.html (基模板):

html
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}默认标题{% endblock %}</title>
</head>
<body>
    <nav>
        <a href="{{ url_for('index') }}">首页</a>
        <a href="{{ url_for('about') }}">关于</a>
    </nav>

    <div class="content">
        {% block content %}{% endblock %}
    </div>

    <footer>
        {% block footer %}{% endblock %}
    </footer>
</body>
</html>

index.html (子模板):

html
{% extends "base.html" %}

{% block title %}首页{% endblock %}

{% block content %}
    <h1>欢迎来到我的网站</h1>
    <p>这是首页内容</p>
{% endblock %}

📝 请求与响应

获取请求数据

python
from flask import Flask, request, jsonify

@app.route('/form', methods=['POST'])
def form():
    # 获取表单数据
    username = request.form.get('username')
    password = request.form.get('password')

    # 获取 JSON 数据
    # data = request.get_json()

    # 获取查询参数
    # page = request.args.get('page', default=1, type=int)

    return f'用户名: {username}'

@app.route('/api', methods=['POST'])
def api():
    data = request.get_json()
    return jsonify({
        'status': 'success',
        'data': data
    })
python
from flask import session, make_response

# 设置 secret_key 用于 session 加密
app.secret_key = 'your-secret-key'

@app.route('/set-cookie')
def set_cookie():
    resp = make_response('设置 Cookie')
    resp.set_cookie('username', '张三')
    return resp

@app.route('/get-cookie')
def get_cookie():
    username = request.cookies.get('username')
    return f'Cookie 中的用户名: {username}'

@app.route('/set-session')
def set_session():
    session['user_id'] = 123
    session['username'] = '张三'
    return 'Session 已设置'

@app.route('/get-session')
def get_session():
    user_id = session.get('user_id')
    username = session.get('username')
    return f'Session: {user_id}, {username}'

@app.route('/logout')
def logout():
    session.clear()
    return '已登出'

📋 表单处理 (Flask-WTF)

安装

bash
pip install flask-wtf email_validator

创建表单类

python
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email, Length

class LoginForm(FlaskForm):
    username = StringField('用户名', validators=[DataRequired()])
    password = PasswordField('密码', validators=[DataRequired(), Length(min=6)])
    email = StringField('邮箱', validators=[DataRequired(), Email()])
    submit = SubmitField('提交')

class RegisterForm(FlaskForm):
    username = StringField('用户名', validators=[DataRequired(), Length(3, 20)])
    email = StringField('邮箱', validators=[DataRequired(), Email()])
    password = PasswordField('密码', validators=[DataRequired(), Length(min=6)])
    confirm_password = PasswordField('确认密码', validators=[DataRequired()])
    submit = SubmitField('注册')

    def validate_confirm_password(self, field):
        if field.data != self.password.data:
            raise ValueError('两次输入的密码不一致')

在路由中使用表单

python
@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()

    if form.validate_on_submit():
        username = form.username.data
        password = form.password.data
        # 验证逻辑
        return f'登录成功: {username}'

    return render_template('login.html', form=form)

模板中渲染表单

html
<form method="POST">
    {{ form.hidden_tag() }}

    <p>
        {{ form.username.label }}<br>
        {{ form.username(size=20) }}
        {% if form.username.errors %}
            <span style="color: red;">{{ form.username.errors[0] }}</span>
        {% endif %}
    </p>

    <p>
        {{ form.password.label }}<br>
        {{ form.password() }}
    </p>

    <p>{{ form.submit() }}</p>
</form>

🗄️ 数据库操作 (Flask-SQLAlchemy)

安装

bash
pip install flask-sqlalchemy

配置数据库

python
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

# SQLite 配置(开发环境)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# MySQL 配置(生产环境)
# app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://user:password@localhost/db_name'

db = SQLAlchemy(app)

定义模型

python
class User(db.Model):
    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(60), nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    # 定义关系
    posts = db.relationship('Post', backref='author', lazy=True)

    def __repr__(self):
        return f"User('{self.username}', '{self.email}')"


class Post(db.Model):
    __tablename__ = 'posts'

    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.Text, nullable=False)
    date_posted = db.Column(db.DateTime, default=datetime.utcnow)

    # 外键
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)

    def __repr__(self):
        return f"Post('{self.title}', '{self.date_posted}')"

创建数据库表

python
# 在 Python shell 中
with app.app_context():
    db.create_all()

CRUD 操作

python
# 创建
user = User(username='张三', email='zhang@example.com', password='hashed_password')
db.session.add(user)
db.session.commit()

# 读取
# 获取所有用户
users = User.query.all()

# 获取单个用户
user = User.query.get(1)
user = User.query.filter_by(username='张三').first()
user = User.query.filter(User.email.endswith('@example.com')).all()

# 更新
user = User.query.get(1)
user.username = '李四'
db.session.commit()

# 删除
user = User.query.get(1)
db.session.delete(user)
db.session.commit()

# 计数
count = User.query.count()

# 排序
users = User.query.order_by(User.created_at.desc()).all()

# 分页
page = request.args.get('page', 1, type=int)
users = User.query.paginate(page=page, per_page=10)

🔐 用户认证 (Flask-Login)

安装

bash
pip install flask-login
pip install bcrypt  # 密码加密

配置

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

login_manager = LoginManager(app)
login_manager.login_view = 'login'  # 登录页面路由

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

UserMixin

python
class User(db.Model, UserMixin):
    # ... 其他字段 ...

    def set_password(self, password):
        self.password = bcrypt.generate_password_hash(password).decode('utf-8')

    def check_password(self, password):
        return bcrypt.check_password_hash(self.password, password)

登录/登出

python
@app.route('/login', methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('index'))

    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.username.data).first()

        if user and user.check_password(form.password.data):
            login_user(user, remember=form.remember.data)
            next_page = request.args.get('next')
            return redirect(next_page) if next_page else redirect(url_for('index'))

        flash('登录失败,请检查用户名和密码', 'danger')

    return render_template('login.html', title='登录', form=form)


@app.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('index'))


@app.route('/register', methods=['GET', 'POST'])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        user = User(username=form.username.data, email=form.email.data)
        user.set_password(form.password.data)
        db.session.add(user)
        db.session.commit()
        flash('注册成功!', 'success')
        return redirect(url_for('login'))
    return render_template('register.html', title='注册', form=form)


@app.route('/account')
@login_required
def account():
    return render_template('account.html', title='账户')

在模板中判断登录状态

html
{% if current_user.is_authenticated %}
    <p>欢迎, {{ current_user.username }}!</p>
    <a href="{{ url_for('logout') }}">登出</a>
{% else %}
    <a href="{{ url_for('login') }}">登录</a>
    <a href="{{ url_for('register') }}">注册</a>
{% endif %}

🧩 蓝图 (Blueprints)

创建蓝图

python
# auth.py
from flask import Blueprint, render_template, redirect, url_for

auth = Blueprint('auth', __name__)

@auth.route('/login')
def login():
    return '登录页面'

@auth.route('/register')
def register():
    return '注册页面'

@auth.route('/logout')
def logout():
    return '登出'
python
# main.py
from flask import Blueprint

main = Blueprint('main', __name__)

@main.route('/')
def index():
    return '首页'

@main.route('/about')
def about():
    return '关于我们'

注册蓝图

python
# app.py
from flask import Flask
from auth import auth as auth_blueprint
from main import main as main_blueprint

app = Flask(__name__)

# 注册蓝图,指定 URL 前缀
app.register_blueprint(auth_blueprint, url_prefix='/auth')
app.register_blueprint(main_blueprint)

# 访问路径:
# /auth/login
# /auth/register
# /
# /about

项目结构

myproject/
├── app/
│   ├── __init__.py      # Flask 应用工厂
│   ├── auth/            # 认证蓝图
│   │   ├── __init__.py
│   │   ├── routes.py
│   │   ├── forms.py
│   │   └── templates/
│   ├── main/            # 主蓝图
│   │   ├── __init__.py
│   │   ├── routes.py
│   │   └── templates/
│   ├── static/          # 静态文件
│   └── templates/       # 共享模板
├── config.py            # 配置文件
├── requirements.txt
└── run.py               # 启动文件

应用工厂模式

python
# config.py
class Config:
    SECRET_KEY = 'your-secret-key'
    SQLALCHEMY_DATABASE_URI = 'sqlite:///site.db'

class DevelopmentConfig(Config):
    DEBUG = True

class ProductionConfig(Config):
    DEBUG = False
    SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://user:password@localhost/db_name'

config = {
    'development': DevelopmentConfig,
    'production': ProductionConfig
}
python
# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager

db = SQLAlchemy()
login_manager = LoginManager()

def create_app(config_name='development'):
    app = Flask(__name__)
    app.config.from_object(config[config_name])

    db.init_app(app)
    login_manager.init_app(app)

    from app.main import main as main_blueprint
    from app.auth import auth as auth_blueprint

    app.register_blueprint(main_blueprint)
    app.register_blueprint(auth_blueprint, url_prefix='/auth')

    return app
python
# run.py
from app import create_app

app = create_app('development')

if __name__ == '__main__':
    app.run(debug=True)

🚀 部署

Gunicorn + Nginx

bash
# 安装 Gunicorn
pip install gunicorn

# 运行 Gunicorn
gunicorn -w 4 -b 127.0.0.1:5000 run:app

Nginx 配置示例:

nginx
server {
    listen 80;
    server_name yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location /static {
        alias /path/to/your/app/static;
    }
}

Docker 部署

dockerfile
# Dockerfile
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 5000

CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "run:app"]
yaml
# docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    ports:
      - "5000:5000"
    environment:
      - FLASK_ENV=production
    volumes:
      - ./data:/app/data

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: flask_db

📚 推荐教程资源 (Resources)

  1. 视频教程 (首选)
  • Corey Schafer (YouTube/Bilibili搬运):
    • 评价: 神级教程。这是公认的最好的 Flask 视频教程。
    • 内容: 从零开始构建一个完整的博客系统,涵盖了数据库、用户系统、密码加密等所有核心内容。
    • 搜索关键词: "Corey Schafer Flask Tutorial" (B站有中文字幕版)。
  • 李辉 (Grey Li) 的相关分享:
    • 他是 Flask 核心维护团队成员(中国人),他的分享非常权威。
  1. 文字教程 & 书籍
  • 官方文档 (The Flask Documentation):
    • 评价: Flask 的官方文档写得非常通俗易懂,包含一个 "Tutorial" 章节,手把手教你写一个 Flaskr 博客。
    • 地址: flask.palletsprojects.com
  • The Flask Mega-Tutorial (作者: Miguel Grinberg):
    • 评价: 这是一个非常经典的系列博客,后来集结成书。它非常详细,适合想深入了解的人。
    • 中文版: 网上有很多翻译版本,搜索 "Flask Mega-Tutorial 中文"。
  • 书籍推荐:
    • 《Flask Web 开发:基于 Python 的 Web 应用开发实战》(作者也是 Miguel Grinberg,俗称“狗书”)。