# Python Flask 框架入门

**学习目标**：掌握 Flask 微框架的核心概念，理解其灵活、轻量的设计理念，能够构建包含路由、模板、数据库的 Web 应用。

---

## 1. Flask 简介

Flask 是一个轻量级的 Python Web 框架，被称为 "微框架"，因为它核心精简但可通过扩展灵活增强。

### 1.1 设计哲学

- **核心精简**：只提供路由、请求处理、模板等基础功能
- **灵活扩展**：通过扩展包添加数据库、认证等功能
- **自由组合**：开发者可选择最适合的组件

### 1.2 Flask vs Django

| 特性 | Flask | Django |
|------|-------|--------|
| 定位 | 微框架 | 全栈框架 |
| 组件 | 按需添加 | 内置电池 |
| 学习曲线 | 平缓 | 较陡 |
| 数据库 | 自由选择（SQLAlchemy 等） | 内置 ORM |
| 模板 | Jinja2（内置） | Django 模板 |
| 管理后台 | 需扩展 | 内置 |
| 适用场景 | API、微服务、小型项目 | 大型应用、快速开发 |

---

## 2. 快速开始

### 2.1 安装与基础应用

```bash
# 安装 Flask
pip install flask

# 可选：安装常用扩展
pip install flask-sqlalchemy flask-wtf flask-login
```

```python
# app.py - 最小的 Flask 应用
from flask import Flask

app = Flask(__name__)

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

if __name__ == '__main__':
    app.run(debug=True, port=5000)
```

### 2.2 运行应用

```bash
# 方式1：直接运行
python app.py

# 方式2：使用环境变量
export FLASK_APP=app.py
export FLASK_ENV=development  # 开发模式（自动重载、调试信息）
flask run --host=0.0.0.0 --port=5000

# 方式3：生产环境（使用 Gunicorn）
pip install gunicorn
gunicorn -w 4 -b 0.0.0.0:5000 app:app
```

---

## 3. 路由系统

### 3.1 基本路由

```python
from flask import Flask

app = Flask(__name__)

# 静态路由
@app.route('/')
def index():
    return '首页'

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

# 动态路由 - 变量规则
@app.route('/user/<username>')
def show_user(username):
    return f'用户: {username}'

# 指定类型
@app.route('/post/<int:post_id>')
def show_post(post_id):
    return f'文章 ID: {post_id}, 类型: {type(post_id).__name__}'

@app.route('/price/<float:amount>')
def show_price(amount):
    return f'价格: ¥{amount:.2f}'

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

# HTTP 方法
@app.route('/api/data', methods=['GET', 'POST'])
def handle_data():
    from flask import request
    
    if request.method == 'POST':
        return '创建数据'
    return '获取数据'
```

### 3.2 URL 构建

```python
from flask import url_for, redirect

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

@app.route('/profile/<username>')
def profile(username):
    return f'{username} 的个人中心'

with app.test_request_context():
    # 生成 URL
    print(url_for('login'))                    # /login
    print(url_for('profile', username='john')) # /profile/john
    print(url_for('profile', username='john', next='/dashboard'))
    # /profile/john?next=%2Fdashboard
```

---

## 4. 请求与响应

### 4.1 请求对象

```python
from flask import Flask, request

app = Flask(__name__)

@app.route('/request-info', methods=['GET', 'POST'])
def request_info():
    """展示请求信息"""
    info = {
        'method': request.method,
        'url': request.url,
        'path': request.path,
        'remote_addr': request.remote_addr,
        'headers': dict(request.headers),
    }
    
    # GET 参数
    if request.args:
        info['query_params'] = dict(request.args)
    
    # POST 数据
    if request.form:
        info['form_data'] = dict(request.form)
    
    # JSON 数据
    if request.is_json:
        info['json_data'] = request.get_json()
    
    # 文件上传
    if request.files:
        info['files'] = list(request.files.keys())
    
    # Cookies
    info['cookies'] = request.cookies
    
    return info

# 文件上传示例
@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
        return '没有文件', 400
    
    file = request.files['file']
    if file.filename == '':
        return '未选择文件', 400
    
    # 保存文件
    file.save(f'uploads/{file.filename}')
    return f'文件 {file.filename} 上传成功'
```

### 4.2 响应构造

```python
from flask import Flask, jsonify, make_response, redirect, abort

app = Flask(__name__)

# 字符串响应
@app.route('/text')
def text_response():
    return '纯文本响应'

# JSON 响应
@app.route('/api/user')
def json_response():
    return jsonify({
        'id': 1,
        'name': '张三',
        'email': 'zhangsan@example.com'
    })

# 自定义状态码和头部
@app.route('/custom')
def custom_response():
    response = make_response('自定义响应', 201)
    response.headers['X-Custom-Header'] = 'MyValue'
    response.set_cookie('session', 'abc123')
    return response

# 重定向
@app.route('/old-page')
def old_page():
    return redirect('/new-page', code=301)

# 错误处理
@app.route('/user/<int:user_id>')
def get_user(user_id):
    if user_id < 1:
        abort(400, '无效的用户 ID')
    
    users = {1: '张三', 2: '李四'}
    if user_id not in users:
        abort(404, '用户不存在')
    
    return {'id': user_id, 'name': users[user_id]}

# 自定义错误页面
@app.errorhandler(404)
def not_found(error):
    return jsonify({'error': '页面不存在'}), 404

@app.errorhandler(500)
def internal_error(error):
    return jsonify({'error': '服务器内部错误'}), 500
```

---

## 5. 模板引擎（Jinja2）

### 5.1 基本使用

```python
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/hello/<name>')
def hello(name):
    return render_template('hello.html', name=name, items=['A', 'B', 'C'])
```

```html
<!-- templates/hello.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Hello</title>
</head>
<body>
    <h1>Hello, {{ name|title }}!</h1>
    
    <!-- 条件判断 -->
    {% if name == 'admin' %}
        <p>欢迎管理员</p>
    {% else %}
        <p>欢迎访客</p>
    {% endif %}
    
    <!-- 循环 -->
    <ul>
    {% for item in items %}
        <li>{{ loop.index }}. {{ item }}</li>
    {% else %}
        <li>列表为空</li>
    {% endfor %}
    </ul>
    
    <!-- 过滤器 -->
    <p>{{ name|upper }}</p>
    <p>{{ "hello world"|title }}</p>
    <p>{{ 1234567|filesizeformat }}</p>
</body>
</html>
```

### 5.2 模板继承

```html
<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}Flask App{% endblock %}</title>
    {% block head %}{% endblock %}
</head>
<body>
    <nav>
        <a href="{{ url_for('index') }}">首页</a>
        <a href="{{ url_for('about') }}">关于</a>
    </nav>
    
    {% with messages = get_flashed_messages() %}
        {% if messages %}
            <ul class="flashes">
            {% for message in messages %}
                <li>{{ message }}</li>
            {% endfor %}
            </ul>
        {% endif %}
    {% endwith %}
    
    <main>
        {% block content %}{% endblock %}
    </main>
</body>
</html>

<!-- templates/index.html -->
{% extends 'base.html' %}

{% block title %}首页 - Flask App{% endblock %}

{% block content %}
    <h1>欢迎来到 Flask</h1>
    <p>这是一个模板继承示例</p>
{% endblock %}
```

---

## 6. 会话与消息闪现

### 6.1 Session（客户端会话）

```python
from flask import Flask, session, request, redirect, url_for

app = Flask(__name__)
app.secret_key = 'your-secret-key-here'  # 用于加密 session

@app.route('/login', methods=['POST'])
def do_login():
    username = request.form['username']
    # 验证用户...
    session['username'] = username
    session['logged_in'] = True
    return redirect(url_for('dashboard'))

@app.route('/dashboard')
def dashboard():
    if 'username' not in session:
        return redirect(url_for('login'))
    return f'欢迎, {session["username"]}!'

@app.route('/logout')
def logout():
    session.pop('username', None)
    session.pop('logged_in', None)
    return redirect(url_for('index'))
```

### 6.2 消息闪现（Flash Messages）

```python
from flask import Flask, flash, redirect, render_template

app = Flask(__name__)
app.secret_key = 'secret-key'

@app.route('/create', methods=['POST'])
def create_item():
    # 创建逻辑...
    flash('项目创建成功！', 'success')
    return redirect(url_for('index'))

@app.route('/delete/<int:id>')
def delete_item(id):
    # 删除逻辑...
    flash('项目已删除', 'warning')
    return redirect(url_for('index'))
```

---

## 7. 数据库集成（SQLAlchemy）

### 7.1 配置与模型

```python
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

class User(db.Model):
    """用户模型"""
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    
    def __repr__(self):
        return f'<User {self.username}>'

class Post(db.Model):
    """文章模型"""
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(200), nullable=False)
    content = db.Column(db.Text, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    author = db.relationship('User', backref=db.backref('posts', lazy=True))
    created_at = db.Column(db.DateTime, default=db.func.current_timestamp())

# 创建表
with app.app_context():
    db.create_all()
```

### 7.2 CRUD 操作

```python
from flask import Flask, request, jsonify

# 创建
@app.route('/users', methods=['POST'])
def create_user():
    data = request.get_json()
    user = User(username=data['username'], email=data['email'])
    db.session.add(user)
    db.session.commit()
    return jsonify({'id': user.id}), 201

# 查询
@app.route('/users')
def get_users():
    users = User.query.all()
    return jsonify([{
        'id': u.id, 
        'username': u.username, 
        'email': u.email
    } for u in users])

@app.route('/users/<int:user_id>')
def get_user(user_id):
    user = User.query.get_or_404(user_id)
    return jsonify({
        'id': user.id,
        'username': user.username,
        'email': user.email,
        'posts': [{'title': p.title} for p in user.posts]
    })

# 更新
@app.route('/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
    user = User.query.get_or_404(user_id)
    data = request.get_json()
    user.username = data.get('username', user.username)
    user.email = data.get('email', user.email)
    db.session.commit()
    return jsonify({'message': '更新成功'})

# 删除
@app.route('/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
    user = User.query.get_or_404(user_id)
    db.session.delete(user)
    db.session.commit()
    return jsonify({'message': '删除成功'})
```

---

## 8. 蓝图（Blueprint）

蓝图用于组织大型应用的模块化路由。

```python
# blog.py - 博客蓝图
from flask import Blueprint, render_template

blog_bp = Blueprint('blog', __name__, url_prefix='/blog')

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

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

# auth.py - 认证蓝图
from flask import Blueprint

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

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

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

# app.py - 注册蓝图
from flask import Flask
from blog import blog_bp
from auth import auth_bp

app = Flask(__name__)
app.register_blueprint(blog_bp)
app.register_blueprint(auth_bp)

# 访问:
# /blog/         → 博客首页
# /blog/post/1   → 博客文章
# /auth/login    → 登录页
```

---

## 9. 应用工厂模式

```python
# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

def create_app(config_name='development'):
    """应用工厂"""
    app = Flask(__name__)
    
    # 加载配置
    if config_name == 'development':
        app.config.from_mapping(
            SECRET_KEY='dev',
            SQLALCHEMY_DATABASE_URI='sqlite:///dev.db',
            SQLALCHEMY_TRACK_MODIFICATIONS=False
        )
    elif config_name == 'production':
        app.config.from_pyfile('config.py')
    
    # 初始化扩展
    db.init_app(app)
    
    # 注册蓝图
    from . import blog, auth
    app.register_blueprint(blog.bp)
    app.register_blueprint(auth.bp)
    
    # 注册错误处理
    app.register_error_handler(404, lambda e: ('Not Found', 404))
    
    return app

# 启动
# from app import create_app
# app = create_app('development')
```

---

## 10. 小结

| 概念 | Flask 实现 | 说明 |
|------|-----------|------|
| 路由 | `@app.route()` | 装饰器注册 |
| 请求 | `request` 对象 | 全局上下文 |
| 响应 | `return` / `jsonify()` | 自动推断或显式构造 |
| 模板 | Jinja2 | 继承 Django 模板风格 |
| 会话 | `session` | 客户端 Cookie 存储 |
| 数据库 | Flask-SQLAlchemy | 扩展集成 |
| 模块化 | Blueprint | 应用拆分 |

---

## 11. 练习题

1. 使用 Flask 创建一个 RESTful API，实现图书的增删改查（使用内存存储或 SQLite）。
2. 为上述 API 添加请求参数验证和错误处理。
3. 使用蓝图将应用拆分为 `api` 和 `web` 两个模块。
4. 对比 Flask 和 Django 的相同功能实现，体会两者的设计差异。

---

> **下节预告**：我们将学习 FastAPI，这个现代异步框架，体验自动文档生成和类型提示的强大功能。

