多组件 flask 项目

下面创建一个完整的Flask项目,包含flask_login、flask_session、flask_cors和flasgger等组件。这个项目将包含用户认证、API文档、跨域支持等功能。

功能特性

  • 用户认证系统 (Flask-Login)

    • 用户登录/注册
    • 会话管理
    • 登录状态保护
  • 会话管理 (Flask-Session)

    • 文件系统会话存储
    • 会话数据持久化
  • 跨域支持 (Flask-CORS)

    • 支持前后端分离
    • API跨域访问
  • API文档 (Flasgger)

    • Swagger UI界面
    • 自动生成API文档
    • 交互式API测试

完整代码

app.py

python 复制代码
from flask import Flask, request, jsonify, session, render_template_string
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from flask_session import Session
from flask_cors import CORS
from flasgger import Swagger, swag_from
from werkzeug.security import generate_password_hash, check_password_hash
import os
from datetime import datetime

# 创建Flask应用
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here'
app.config['SESSION_TYPE'] = 'filesystem'
app.config['SESSION_PERMANENT'] = False
app.config['SESSION_USE_SIGNER'] = True

# 初始化扩展
Session(app)
CORS(app)
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
login_manager.login_message = '请先登录'

# Swagger配置
swagger_config = {
    "headers": [],
    "specs": [
        {
            "endpoint": 'apispec_1',
            "route": '/apispec_1.json',
            "rule_filter": lambda rule: True,
            "model_filter": lambda tag: True,
        }
    ],
    "static_url_path": "/flasgger_static",
    "swagger_ui": True,
    "specs_route": "/docs/"
}

swagger_template = {
    "swagger": "2.0",
    "info": {
        "title": "Flask练手项目API",
        "description": "一个包含用户认证、会话管理的完整Flask项目",
        "version": "1.0.0"
    },
    "host": "localhost:5000",
    "basePath": "/",
    "schemes": ["http"],
    "securityDefinitions": {
        "Bearer": {
            "type": "apiKey",
            "name": "Authorization",
            "in": "header"
        }
    }
}

swagger = Swagger(app, config=swagger_config, template=swagger_template)

# 模拟用户数据库
users_db = {
    'admin': {
        'id': 1,
        'username': 'admin',
        'password_hash': generate_password_hash('admin123'),
        'email': 'admin@example.com',
        'created_at': datetime.now()
    },
    'user1': {
        'id': 2,
        'username': 'user1',
        'password_hash': generate_password_hash('user123'),
        'email': 'user1@example.com',
        'created_at': datetime.now()
    }
}

# 用户类
class User(UserMixin):
    def __init__(self, user_data):
        self.id = user_data['id']
        self.username = user_data['username']
        self.email = user_data['email']
        self.password_hash = user_data['password_hash']
        self.created_at = user_data['created_at']
    
    def check_password(self, password):
        return check_password_hash(self.password_hash, password)
    
    def to_dict(self):
        return {
            'id': self.id,
            'username': self.username,
            'email': self.email,
            'created_at': self.created_at.isoformat()
        }

@login_manager.user_loader
def load_user(user_id):
    for username, user_data in users_db.items():
        if user_data['id'] == int(user_id):
            return User(user_data)
    return None

# 首页
@app.route('/')
def index():
    """
    首页
    ---
    tags:
      - 首页
    responses:
      200:
        description: 返回首页HTML
    """
    html_template = """
    <!DOCTYPE html>
    <html>
    <head>
        <title>Flask练手项目</title>
        <meta charset="utf-8">
        <style>
            body { font-family: Arial, sans-serif; margin: 40px; }
            .container { max-width: 800px; margin: 0 auto; }
            .btn { padding: 10px 20px; margin: 5px; text-decoration: none; background: #007bff; color: white; border-radius: 5px; }
            .btn:hover { background: #0056b3; }
            .info { background: #f8f9fa; padding: 20px; border-radius: 5px; margin: 20px 0; }
        </style>
    </head>
    <body>
        <div class="container">
            <h1>Flask练手项目</h1>
            <div class="info">
                <h3>项目功能:</h3>
                <ul>
                    <li>用户认证系统 (Flask-Login)</li>
                    <li>会话管理 (Flask-Session)</li>
                    <li>跨域支持 (Flask-CORS)</li>
                    <li>API文档 (Flasgger)</li>
                </ul>
            </div>
            
            {% if current_user.is_authenticated %}
                <p>欢迎回来,{{ current_user.username }}!</p>
                <a href="/profile" class="btn">个人资料</a>
                <a href="/logout" class="btn">退出登录</a>
            {% else %}
                <p>请登录以访问更多功能</p>
                <a href="/login" class="btn">登录</a>
                <a href="/register" class="btn">注册</a>
            {% endif %}
            
            <div style="margin-top: 30px;">
                <a href="/docs/" class="btn">API文档</a>
                <a href="/api/users" class="btn">用户列表API</a>
            </div>
        </div>
    </body>
    </html>
    """
    return render_template_string(html_template)

# 登录页面
@app.route('/login', methods=['GET', 'POST'])
def login():
    """
    用户登录
    ---
    tags:
      - 用户认证
    parameters:
      - name: username
        in: formData
        type: string
        required: true
        description: 用户名
      - name: password
        in: formData
        type: string
        required: true
        description: 密码
    responses:
      200:
        description: 登录成功
      400:
        description: 登录失败
    """
    login_template = """
    <!DOCTYPE html>
    <html>
    <head>
        <title>登录</title>
        <meta charset="utf-8">
        <style>
            body { font-family: Arial, sans-serif; margin: 40px; }
            .container { max-width: 400px; margin: 0 auto; }
            .form-group { margin: 15px 0; }
            label { display: block; margin-bottom: 5px; }
            input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; }
            .btn { width: 100%; padding: 10px; background: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; }
            .btn:hover { background: #0056b3; }
            .error { color: red; margin: 10px 0; }
            .info { background: #e7f3ff; padding: 15px; border-radius: 5px; margin: 20px 0; }
        </style>
    </head>
    <body>
        <div class="container">
            <h2>用户登录</h2>
            
            <div class="info">
                <strong>测试账号:</strong><br>
                用户名: admin, 密码: admin123<br>
                用户名: user1, 密码: user123
            </div>
            
            {% if error %}
                <div class="error">{{ error }}</div>
            {% endif %}
            
            <form method="POST">
                <div class="form-group">
                    <label>用户名:</label>
                    <input type="text" name="username" required>
                </div>
                <div class="form-group">
                    <label>密码:</label>
                    <input type="password" name="password" required>
                </div>
                <button type="submit" class="btn">登录</button>
            </form>
            
            <p style="text-align: center; margin-top: 20px;">
                <a href="/">返回首页</a> | <a href="/register">注册账号</a>
            </p>
        </div>
    </body>
    </html>
    """
    if request.method == 'POST':
        if request.is_json:
            data = request.get_json()
            username = data.get('username')
            password = data.get('password')
        else:
            username = request.form.get('username')
            password = request.form.get('password')
        if not username or not password:
            if request.is_json:
                return jsonify({'error': '用户名和密码不能为空'}), 400
            return render_template_string(login_template, error='用户名和密码不能为空')
        
        user_data = users_db.get(username)
        if user_data and check_password_hash(user_data['password_hash'], password):
            user = User(user_data)
            login_user(user)
            session['user_id'] = user.id
            
            if request.is_json:
                return jsonify({
                    'message': '登录成功',
                    'user': user.to_dict()
                })
            return render_template_string(success_template, message='登录成功!', redirect_url='/')
        else:
            if request.is_json:
                return jsonify({'error': '用户名或密码错误'}), 400
            return render_template_string(login_template, error='用户名或密码错误')
    
    return render_template_string(login_template)

# 注册页面
@app.route('/register', methods=['GET', 'POST'])
def register():
    """
    用户注册
    ---
    tags:
      - 用户认证
    parameters:
      - name: username
        in: formData
        type: string
        required: true
        description: 用户名
      - name: password
        in: formData
        type: string
        required: true
        description: 密码
      - name: email
        in: formData
        type: string
        required: true
        description: 邮箱
    responses:
      200:
        description: 注册成功
      400:
        description: 注册失败
    """
    register_template = """
    <!DOCTYPE html>
    <html>
    <head>
        <title>注册</title>
        <meta charset="utf-8">
        <style>
            body { font-family: Arial, sans-serif; margin: 40px; }
            .container { max-width: 400px; margin: 0 auto; }
            .form-group { margin: 15px 0; }
            label { display: block; margin-bottom: 5px; }
            input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; }
            .btn { width: 100%; padding: 10px; background: #28a745; color: white; border: none; border-radius: 5px; cursor: pointer; }
            .btn:hover { background: #218838; }
            .error { color: red; margin: 10px 0; }
        </style>
    </head>
    <body>
        <div class="container">
            <h2>用户注册</h2>
            
            {% if error %}
                <div class="error">{{ error }}</div>
            {% endif %}
            
            <form method="POST">
                <div class="form-group">
                    <label>用户名:</label>
                    <input type="text" name="username" required>
                </div>
                <div class="form-group">
                    <label>邮箱:</label>
                    <input type="email" name="email" required>
                </div>
                <div class="form-group">
                    <label>密码:</label>
                    <input type="password" name="password" required>
                </div>
                <button type="submit" class="btn">注册</button>
            </form>
            
            <p style="text-align: center; margin-top: 20px;">
                <a href="/">返回首页</a> | <a href="/login">已有账号?登录</a>
            </p>
        </div>
    </body>
    </html>
    """
    if request.method == 'POST':
        if request.is_json:
            # 处理 JSON 请求
            data = request.get_json()
            username = data.get('username')
            password = data.get('password')
            email = data.get('email')  # 注册时使用
        else:
            # 处理表单请求
            username = request.form.get('username')
            password = request.form.get('password')
            email = request.form.get('email')  # 注册时使用

        if not all([username, password, email]):
            error_msg = '所有字段都是必填的'
            if request.is_json:
                return jsonify({'error': error_msg}), 400
            return render_template_string(register_template, error=error_msg)
        
        if username in users_db:
            error_msg = '用户名已存在'
            if request.is_json:
                return jsonify({'error': error_msg}), 400
            return render_template_string(register_template, error=error_msg)
        
        # 创建新用户
        new_user_id = max([user['id'] for user in users_db.values()]) + 1
        users_db[username] = {
            'id': new_user_id,
            'username': username,
            'password_hash': generate_password_hash(password),
            'email': email,
            'created_at': datetime.now()
        }
        
        success_msg = '注册成功!请登录'
        if request.is_json:
            return jsonify({'message': success_msg})
        return render_template_string(success_template, message=success_msg, redirect_url='/login')
    
    
    return render_template_string(register_template)

# 退出登录
@app.route('/logout')
@login_required
def logout():
    """
    用户退出登录
    ---
    tags:
      - 用户认证
    responses:
      200:
        description: 退出成功
    """
    logout_user()
    session.clear()
    
    if request.is_json:
        return jsonify({'message': '退出登录成功'})
    
    success_template = """
    <!DOCTYPE html>
    <html>
    <head>
        <title>退出成功</title>
        <meta charset="utf-8">
        <meta http-equiv="refresh" content="2;url=/">
        <style>
            body { font-family: Arial, sans-serif; margin: 40px; text-align: center; }
            .success { color: green; font-size: 18px; }
        </style>
    </head>
    <body>
        <div class="success">
            <h2>退出登录成功!</h2>
            <p>2秒后自动跳转到首页...</p>
            <a href="/">立即跳转</a>
        </div>
    </body>
    </html>
    """
    return render_template_string(success_template)

# 个人资料页面
@app.route('/profile')
@login_required
def profile():
    """
    用户个人资料
    ---
    tags:
      - 用户信息
    security:
      - Bearer: []
    responses:
      200:
        description: 返回用户信息
        schema:
          type: object
          properties:
            id:
              type: integer
            username:
              type: string
            email:
              type: string
            created_at:
              type: string
    """
    if request.is_json:
        return jsonify(current_user.to_dict())
    
    profile_template = """
    <!DOCTYPE html>
    <html>
    <head>
        <title>个人资料</title>
        <meta charset="utf-8">
        <style>
            body { font-family: Arial, sans-serif; margin: 40px; }
            .container { max-width: 600px; margin: 0 auto; }
            .profile-card { background: #f8f9fa; padding: 20px; border-radius: 10px; }
            .btn { padding: 10px 20px; margin: 5px; text-decoration: none; background: #007bff; color: white; border-radius: 5px; }
            .btn:hover { background: #0056b3; }
        </style>
    </head>
    <body>
        <div class="container">
            <h2>个人资料</h2>
            <div class="profile-card">
                <p><strong>用户ID:</strong> {{ current_user.id }}</p>
                <p><strong>用户名:</strong> {{ current_user.username }}</p>
                <p><strong>邮箱:</strong> {{ current_user.email }}</p>
                <p><strong>注册时间:</strong> {{ current_user.created_at.strftime('%Y-%m-%d %H:%M:%S') }}</p>
            </div>
            
            <div style="margin-top: 20px;">
                <a href="/" class="btn">返回首页</a>
                <a href="/logout" class="btn">退出登录</a>
            </div>
        </div>
    </body>
    </html>
    """
    return render_template_string(profile_template)

# API路由
@app.route('/api/users', methods=['GET'])
def get_users():
    """
    获取用户列表
    ---
    tags:
      - API
    responses:
      200:
        description: 用户列表
        schema:
          type: array
          items:
            type: object
            properties:
              id:
                type: integer
              username:
                type: string
              email:
                type: string
              created_at:
                type: string
    """
    users_list = []
    for user_data in users_db.values():
        user = User(user_data)
        users_list.append(user.to_dict())
    
    return jsonify({
        'users': users_list,
        'total': len(users_list)
    })

@app.route('/api/session-info')
def session_info():
    """
    获取会话信息
    ---
    tags:
      - API
    responses:
      200:
        description: 会话信息
    """
    return jsonify({
        'session_id': session.get('_id', 'No session'),
        'user_authenticated': current_user.is_authenticated,
        'user_info': current_user.to_dict() if current_user.is_authenticated else None,
        'session_data': dict(session)
    })

# 成功页面模板
success_template = """
<!DOCTYPE html>
<html>
<head>
    <title>操作成功</title>
    <meta charset="utf-8">
    <meta http-equiv="refresh" content="3;url={{ redirect_url }}">
    <style>
        body { font-family: Arial, sans-serif; margin: 40px; text-align: center; }
        .success { color: green; font-size: 18px; }
    </style>
</head>
<body>
    <div class="success">
        <h2>{{ message }}</h2>
        <p>3秒后自动跳转...</p>
        <a href="{{ redirect_url }}">立即跳转</a>
    </div>
</body>
</html>
"""

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

流程图

环境搭建

python 复制代码
uv init
uv venv
uv pip install flask flask_login flask_session flask_cors flasgger
uv run app.py



相关推荐
MarkGosling2 小时前
【开源项目】网络诊断告别命令行!NetSonar:开源多协议网络诊断利器
运维·后端·自动化运维
Codebee2 小时前
OneCode3.0 VFS分布式文件管理API速查手册
后端·架构·开源
_新一2 小时前
Go 调度器(二):一个线程的执行流程
后端
estarlee2 小时前
腾讯云轻量服务器创建镜像免费API接口教程
后端
风流 少年2 小时前
Cursor创建Spring Boot项目
java·spring boot·后端
毕设源码_钟学姐3 小时前
计算机毕业设计springboot宿舍管理信息系统 基于Spring Boot的高校宿舍管理平台设计与实现 Spring Boot框架下的宿舍管理系统开发
spring boot·后端·课程设计
方圆想当图灵3 小时前
ScheduledFutureTask 踩坑实录
后端
全栈凯哥4 小时前
16.Spring Boot 国际化完全指南
java·spring boot·后端
M1A14 小时前
Java集合框架深度解析:LinkedList vs ArrayList 的对决
java·后端
31535669135 小时前
Springboot实现一个接口加密
后端