Flask第一个应用

恭喜!你已经完成环境配置,现在让我们创建第一个Flask应用

1. 最简单的Flask应用

让我们从一个最简单的单文件Flask应用开始:

创建app.py文件:

from flask import Flask

# 创建Flask应用实例
app = Flask(__name__)

# 定义路由和视图函数
@app.route('/')
def hello_world():
    return 'Hello, World!'

# 添加另一个路由
@app.route('/hello/<name>')
def hello_name(name):
    return f'Hello, {name}! Welcome to Flask!'

# 主程序入口
if __name__ == '__main__':
    app.run(
        debug=True,      # 开启调试模式
        host='127.0.0.1', # 本地主机
        port=5000        # 端口号
    )

运行应用:

# 确保在项目目录中
python app.py
运行后,终端会显示:
* Serving Flask app 'app'
* Debug mode: on
* Running on http://127.0.0.1:5000 (Press CTRL+C to quit)

访问应用:

2. 完整的项目结构

为了更好的可维护性,我们采用标准项目结构:

myflaskproject/
│
├── app/                          # 应用包
│   ├── __init__.py              # 初始化文件,创建Flask应用
│   ├── routes.py                # 路由定义
│   ├── models.py                # 数据模型(后续章节讲解)
│   ├── forms.py                 # 表单定义(后续章节讲解)
│   ├── errors.py                # 错误处理
│   │
│   ├── templates/               # 模板目录
│   │   ├── base.html           # 基础模板
│   │   ├── index.html          # 首页模板
│   │   ├── about.html          # 关于页面
│   │   └── errors/             # 错误页面目录
│   │       ├── 404.html
│   │       └── 500.html
│   │
│   └── static/                  # 静态文件目录
│       ├── css/
│       │   └── style.css
│       ├── js/
│       │   └── main.js
│       └── images/
│           └── logo.png
│
├── migrations/                  # 数据库迁移文件(后续添加)
├── tests/                       # 测试文件
│   ├── __init__.py
│   └── test_basic.py
│
├── config.py                    # 配置文件
├── requirements.txt             # 依赖列表
├── .env                         # 环境变量(可选)
├── .gitignore                   # Git忽略文件
└── run.py                       # 启动脚本

3. 路由和视图函数

app/__init__.py:应用工厂模式

from flask import Flask
from config import Config

def create_app(config_class=Config):
    """应用工厂函数"""
    app = Flask(__name__)
    app.config.from_object(config_class)

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

    # 注册错误处理器
    from app.errors import bp as errors_bp
    app.register_blueprint(errors_bp)

    return app

config.py:配置文件

import os
from dotenv import load_dotenv

# 加载环境变量
load_dotenv()

class Config:
    """基础配置类"""
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key-123'
    DEBUG = True

    # 静态文件配置
    STATIC_FOLDER = 'static'
    TEMPLATES_FOLDER = 'templates'

app/routes.py:路由定义(使用蓝图)

from flask import Blueprint, render_template, request, jsonify
from datetime import datetime

# 创建蓝图
main_bp = Blueprint('main', __name__)

# 主页
@main_bp.route('/')
@main_bp.route('/index')
def index():
    """首页"""
    return render_template('index.html',
                          title='Flask首页',
                          current_time=datetime.now())

# 关于页面
@main_bp.route('/about')
def about():
    """关于页面"""
    return render_template('about.html',
                          title='关于我们',
                          year=datetime.now().year)

# 用户问候页面
@main_bp.route('/user/<username>')
def user_profile(username):
    """用户个人页面"""
    return render_template('user.html',
                          username=username,
                          title=f'{username}的主页')

# API接口示例
@main_bp.route('/api/data')
def get_data():
    """返回JSON数据"""
    data = {
        'status': 'success',
        'message': '数据获取成功',
        'timestamp': datetime.now().isoformat(),
        'data': {
            'users': 100,
            'active': 75
        }
    }
    return jsonify(data)

# 表单提交示例
@main_bp.route('/submit', methods=['GET', 'POST'])
def submit():
    """处理表单提交"""
    if request.method == 'POST':
        name = request.form.get('name', '匿名')
        return f'你好,{name}!感谢提交表单。'

    # GET请求返回表单页面
    return '''
    <form method="post">
        <label>姓名:</label>
        <input type="text" name="name">
        <button type="submit">提交</button>
    </form>
    '''

4. 使用Jinja2模板

templates/base.html:基础模板

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}{{ title }} - Flask应用{% endblock %}</title>

    <!-- Bootstrap 5 CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">

    <!-- 自定义CSS -->
    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">

    <!-- Font Awesome -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">

    {% block head %}{% endblock %}
</head>
<body>
    <!-- 导航栏 -->
    <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
        <div class="container">
            <a class="navbar-brand" href="{{ url_for('main.index') }}">
                <i class="fas fa-flask me-2"></i>Flask应用
            </a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav ms-auto">
                    <li class="nav-item">
                        <a class="nav-link" href="{{ url_for('main.index') }}">
                            <i class="fas fa-home me-1"></i>首页
                        </a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{{ url_for('main.about') }}">
                            <i class="fas fa-info-circle me-1"></i>关于
                        </a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{{ url_for('main.get_data') }}">
                            <i class="fas fa-database me-1"></i>API
                        </a>
                    </li>
                </ul>
            </div>
        </div>
    </nav>

    <!-- 主要内容 -->
    <main class="container my-4">
        {% with messages = get_flashed_messages() %}
            {% if messages %}
                {% for message in messages %}
                    <div class="alert alert-info alert-dismissible fade show">
                        {{ message }}
                        <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
                    </div>
                {% endfor %}
            {% endif %}
        {% endwith %}

        {% block content %}{% endblock %}
    </main>

    <!-- 页脚 -->
    <footer class="bg-light text-center py-3 mt-5">
        <div class="container">
            <p class="mb-0">
                © {{ year if year else 2023 }} Flask应用 |
                <a href="{{ url_for('main.about') }}" class="text-decoration-none">关于我们</a>
            </p>
        </div>
    </footer>

    <!-- Bootstrap JS -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>

    <!-- 自定义JS -->
    <script src="{{ url_for('static', filename='js/main.js') }}"></script>

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

templates/index.html:首页模板

{% extends "base.html" %}

{% block title %}{{ title }}{% endblock %}

{% block content %}
<div class="row">
    <div class="col-md-8 mx-auto text-center">
        <h1 class="display-4 text-primary mb-4">
            <i class="fas fa-flask"></i> 欢迎来到Flask世界!
        </h1>

        <p class="lead">
            这是一个使用Flask构建的完整Web应用示例。当前时间:{{ current_time.strftime('%Y-%m-%d %H:%M:%S') }}
        </p>

        <div class="card shadow-sm my-4">
            <div class="card-body">
                <h5 class="card-title">快速开始</h5>
                <p class="card-text">尝试以下功能:</p>

                <div class="row mt-3">
                    <div class="col-md-4">
                        <a href="{{ url_for('main.about') }}" class="btn btn-outline-primary w-100 mb-2">
                            <i class="fas fa-info-circle"></i> 关于页面
                        </a>
                    </div>
                    <div class="col-md-4">
                        <a href="{{ url_for('main.user_profile', username='访客') }}" class="btn btn-outline-success w-100 mb-2">
                            <i class="fas fa-user"></i> 用户页面
                        </a>
                    </div>
                    <div class="col-md-4">
                        <a href="{{ url_for('main.get_data') }}" class="btn btn-outline-info w-100 mb-2">
                            <i class="fas fa-database"></i> API数据
                        </a>
                    </div>
                </div>
            </div>
        </div>

        <!-- 动态表单 -->
        <div class="card shadow-sm my-4">
            <div class="card-body">
                <h5 class="card-title">互动表单</h5>
                <form action="{{ url_for('main.submit') }}" method="post" class="mt-3">
                    <div class="input-group">
                        <input type="text" name="name" class="form-control" placeholder="请输入您的姓名" required>
                        <button type="submit" class="btn btn-primary">
                            <i class="fas fa-paper-plane"></i> 提交问候
                        </button>
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>
{% endblock %}

5. 静态文件管理

static/css/style.css:自定义样式

/* 自定义样式表 */

/* 全局样式 */
body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: #f8f9fa;
}

/* 导航栏 */
.navbar-brand {
    font-weight: bold;
}

/* 卡片样式 */
.card {
    border: none;
    transition: transform 0.3s;
}

.card:hover {
    transform: translateY(-5px);
    box-shadow: 0 10px 20px rgba(0,0,0,0.1) !important;
}

/* 按钮样式 */
.btn {
    border-radius: 20px;
    padding: 8px 20px;
}

.btn-outline-primary:hover {
    background-color: #0d6efd;
    color: white;
}

/* 页脚样式 */
footer {
    border-top: 1px solid #dee2e6;
}

/* 响应式调整 */
@media (max-width: 768px) {
    .display-4 {
        font-size: 2rem;
    }

    .lead {
        font-size: 1rem;
    }
}

static/js/main.js:JavaScript脚本

// 主JavaScript文件

document.addEventListener('DOMContentLoaded', function() {
    console.log('Flask应用已加载');

    // 示例:显示当前时间
    function updateTime() {
        const timeElement = document.getElementById('current-time');
        if (timeElement) {
            const now = new Date();
            timeElement.textContent = now.toLocaleString();
        }
    }

    // 每秒钟更新时间
    setInterval(updateTime, 1000);
    updateTime();

    // 表单提交提示
    const forms = document.querySelectorAll('form');
    forms.forEach(form => {
        form.addEventListener('submit', function(e) {
            const submitBtn = this.querySelector('button[type="submit"]');
            if (submitBtn) {
                submitBtn.innerHTML = ' 处理中...';
                submitBtn.disabled = true;
            }
        });
    });

    // 返回顶部按钮
    const backToTop = document.createElement('button');
    backToTop.innerHTML = '';
    backToTop.className = 'btn btn-primary btn-floating';
    backToTop.style.position = 'fixed';
    backToTop.style.bottom = '20px';
    backToTop.style.right = '20px';
    backToTop.style.display = 'none';
    backToTop.style.zIndex = '1000';

    backToTop.addEventListener('click', function() {
        window.scrollTo({ top: 0, behavior: 'smooth' });
    });

    document.body.appendChild(backToTop);

    window.addEventListener('scroll', function() {
        if (window.pageYOffset > 300) {
            backToTop.style.display = 'block';
        } else {
            backToTop.style.display = 'none';
        }
    });
});

6. 错误处理

app/errors.py:错误处理蓝图

from flask import Blueprint, render_template

bp = Blueprint('errors', __name__)

@bp.app_errorhandler(404)
def not_found_error(error):
    """404错误处理"""
    return render_template('errors/404.html', title='页面未找到'), 404

@bp.app_errorhandler(500)
def internal_error(error):
    """500错误处理"""
    return render_template('errors/500.html', title='服务器错误'), 500

@bp.app_errorhandler(403)
def forbidden_error(error):
    """403错误处理"""
    return render_template('errors/403.html', title='禁止访问'), 403

templates/errors/404.html:404错误页面

{% extends "base.html" %}

{% block content %}
<div class="text-center py-5">
    <h1 class="display-1 text-muted">404</h1>
    <h2 class="mb-4">页面未找到</h2>
    <p class="lead mb-4">
        抱歉,您访问的页面不存在或已被移除。
    </p>
    <div class="mb-3">
        <i class="fas fa-search fa-4x text-warning mb-4"></i>
    </div>
    <a href="{{ url_for('main.index') }}" class="btn btn-primary btn-lg">
        <i class="fas fa-home me-2"></i>返回首页
    </a>
</div>
{% endblock %}

7. 本地运行与部署

创建启动脚本:run.py

#!/usr/bin/env python
"""应用启动脚本"""

import os
from app import create_app
from dotenv import load_dotenv

# 加载环境变量
load_dotenv()

# 创建应用实例
app = create_app()

if __name__ == '__main__':
    # 获取环境变量中的配置,或使用默认值
    host = os.environ.get('FLASK_HOST', '127.0.0.1')
    port = int(os.environ.get('FLASK_PORT', 5000))
    debug = os.environ.get('FLASK_DEBUG', 'True').lower() in ['true', '1']

    print(f"""
    ========================================
     Flask应用启动中...
     访问地址: http://{host}:{port}
     调试模式: {debug}
    ========================================
    """)

    app.run(host=host, port=port, debug=debug)

创建requirements.txt:依赖文件

Flask==2.3.2
python-dotenv==1.0.0

# 开发环境依赖(可选)
blinker==1.6.2
click==8.1.3
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
Werkzeug==2.3.6

运行应用的不同方式:

# 方式1:直接运行(推荐)
python run.py

# 方式2:使用Flask命令行
export FLASK_APP=run.py
export FLASK_ENV=development
flask run

# 方式3:使用gunicorn(生产环境)
pip install gunicorn
gunicorn -w 4 -b 127.0.0.1:8000 run:app

创建.gitignore文件:

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
ENV/

# Flask
instance/
.webassets-cache

# IDE
.vscode/
.idea/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db

# 环境变量
.env
.env.local
.env.*.local
测试你的应用

完成以上步骤后,测试以下功能是否正常:

  • ✅ 访问 http://127.0.0.1:5000/ 显示首页
  • ✅ 访问 http://127.0.0.1:5000/about 显示关于页面
  • ✅ 访问 http://127.0.0.1:5000/user/张三 显示用户页面
  • ✅ 访问 http://127.0.0.1:5000/api/data 返回JSON数据
  • ✅ 访问不存在的页面显示404错误页
  • ✅ 表单提交功能正常工作
恭喜!你已经成功创建了第一个Flask应用

接下来可以学习:

  • 数据库集成(SQLAlchemy)
  • 用户认证系统(Flask-Login)
  • 表单验证(Flask-WTF)
  • RESTful API开发
  • 部署到生产环境