下面创建一个完整的Flask项目,包含flask_login、flask_session、flask_cors和flasgger等组件。这个项目将包含用户认证、API文档、跨域支持等功能。
功能特性
-
用户认证系统 (Flask-Login)
- 用户登录/注册
- 会话管理
- 登录状态保护
-
会话管理 (Flask-Session)
- 文件系统会话存储
- 会话数据持久化
-
跨域支持 (Flask-CORS)
- 支持前后端分离
- API跨域访问
-
API文档 (Flasgger)
- Swagger UI界面
- 自动生成API文档
- 交互式API测试
完整代码
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