<think>我们将使用Flask框架重构上述登录界面,将其转换为Web应用。由于Flask是Web框架,我们需要使用HTML/CSS/JavaScript来构建前端界面,但为了简化,我将使用Flask的模板渲染功能(Jinja2)来生成HTML页面。
由于代码较长,我将分步骤实现:
1. 创建Flask应用结构
2. 设计前端页面(使用Bootstrap快速构建)
3. 实现后端路由和逻辑
4. 使用SQLite数据库存储用户信息(代替之前的JSON文件)
注意:为了简化,我们将使用SQLite数据库,并使用SQLAlchemy ORM。
我们将实现以下功能:
- 用户登录(支持用户名/邮箱登录)
- 邮箱验证(发送验证码,实际开发需要邮件服务器,这里模拟)
- 密码重置(通过邮箱)
- 用户资料编辑
- 记住登录状态(使用Flask-Login实现会话管理)
- 第三方登录(模拟,实际需要OAuth集成)
由于第三方登录需要第三方平台的API,这里仅模拟流程。
步骤:
1. 初始化Flask应用
2. 配置数据库模型(User)
3. 使用Flask-Login管理用户会话
4. 编写路由和视图函数
5. 创建HTML模板
我们将使用以下扩展:
- Flask-SQLAlchemy: 用于数据库操作
- Flask-Login: 用于用户会话管理
- Flask-WTF: 用于表单处理(可选,但推荐)
由于要求代码完整,我们将尽量使用Flask-WTF来处理表单,以提高安全性和便捷性。
注意:这是一个示例程序,实际部署时需要考虑更多的安全措施(如密码哈希、CSRF保护等)。
由于代码量较大,我将分块展示。首先,创建项目结构:
项目目录结构:
/myapp
app.py
/templates
base.html
login.html
register.html
profile.html
verify_email.html
reset_password.html
dashboard.html
/static
style.css (可选,使用Bootstrap则不需要)
安装必要的包:
pip install flask flask-sqlalchemy flask-login flask-wtf email-validator
注意:由于邮箱验证需要发送邮件,我们暂时模拟发送,不实际发送邮件。
我们开始编写代码:
1. app.py
</think>下面是一个使用 Flask 框架实现的完整登录系统,运行后会启动本地 Web 服务器,用户可以通过浏览器访问登录界面。代码包含所有要求的功能:
```python
from flask import Flask, render_template, request, redirect, url_for, session, jsonify
import sqlite3
import random
import string
import hashlib
app = Flask(__name__)
app.secret_key = 'your_secret_key_here' # 设置安全的密钥
# 数据库初始化
def init_db():
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
email TEXT NOT NULL,
verified INTEGER DEFAULT 0,
profile TEXT DEFAULT '{}'
)
''')
# 添加示例用户
cursor.execute("SELECT COUNT(*) FROM users")
if cursor.fetchone()[0] == 0:
hashed_password = hashlib.sha256('admin123'.encode()).hexdigest()
cursor.execute(
"INSERT INTO users (username, password, email, verified, profile) VALUES (?, ?, ?, ?, ?)",
('admin', hashed_password, 'admin@example.com', 1, '{"name": "Admin", "age": 30, "gender": "Male"}')
)
conn.commit()
conn.close()
init_db()
# 获取数据库连接
def get_db_connection():
conn = sqlite3.connect('users.db')
conn.row_factory = sqlite3.Row
return conn
@app.route('/')
def home():
# 检查是否已登录
if 'username' in session:
return redirect(url_for('dashboard'))
return render_template('login.html')
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
remember = 'remember' in request.form
conn = get_db_connection()
cursor = conn.cursor()
hashed_password = hashlib.sha256(password.encode()).hexdigest()
# 检查用户名/邮箱和密码
cursor.execute(
"SELECT * FROM users WHERE (username = ? OR email = ?) AND password = ?",
(username, username, hashed_password)
)
user = cursor.fetchone()
if user:
# 设置会话
session['username'] = user['username']
session['user_id'] = user['id']
# 记住登录状态(使用持久会话)
if remember:
session.permanent = True
return redirect(url_for('dashboard'))
return render_template('login.html', error='用户名/邮箱或密码错误')
@app.route('/dashboard')
def dashboard():
if 'username' not in session:
return redirect(url_for('home'))
conn = get_db_connection()
user = conn.execute(
"SELECT * FROM users WHERE id = ?",
(session['user_id'],)
).fetchone()
conn.close()
# 解析JSON格式的个人资料
import json
profile = json.loads(user['profile']) if user['profile'] else {}
return render_template('dashboard.html', username=user['username'], profile=profile)
@app.route('/verify_email', methods=['GET', 'POST'])
def verify_email():
if request.method == 'POST':
email = request.form['email']
# 生成验证码并存储到session
session['verification_code'] = ''.join(random.choices(string.digits, k=6))
# 实际应用中应发送邮件,这里模拟发送
return render_template('verify_email.html',
message=f"验证码已发送到 {email}(测试用途:{session['verification_code']})")
return render_template('verify_email.html')
@app.route('/confirm_verification', methods=['POST'])
def confirm_verification():
user_code = request.form['code']
if 'verification_code' in session and user_code == session['verification_code']:
# 标记用户为已验证
conn = get_db_connection()
conn.execute(
"UPDATE users SET verified = 1 WHERE id = ?",
(session['user_id'],)
)
conn.commit()
conn.close()
session.pop('verification_code', None)
return redirect(url_for('dashboard'))
return render_template('verify_email.html', error='验证码错误')
@app.route('/reset_password', methods=['GET', 'POST'])
def reset_password():
if request.method == 'POST':
email = request.form['email']
# 生成重置令牌并存储到session
session['reset_token'] = ''.join(random.choices(string.ascii_letters + string.digits, k=32))
# 实际应用中应发送邮件,这里模拟发送
return render_template('reset_confirmation.html',
message=f"密码重置链接已发送到 {email}(测试用途令牌:{session['reset_token']})")
return render_template('reset_password.html')
@app.route('/reset_password_form', methods=['GET', 'POST'])
def reset_password_form():
if request.method == 'GET':
token = request.args.get('token')
if 'reset_token' in session and token == session['reset_token']:
return render_template('reset_form.html')
return render_template('reset_password.html', error='无效的重置令牌')
# 处理重置请求
new_password = request.form['password']
confirm_password = request.form['confirm_password']
if new_password != confirm_password:
return render_template('reset_form.html', error='两次密码输入不一致')
# 更新密码
hashed_password = hashlib.sha256(new_password.encode()).hexdigest()
conn = get_db_connection()
conn.execute(
"UPDATE users SET password = ? WHERE email = ?",
(hashed_password, request.form['email'])
)
conn.commit()
conn.close()
session.pop('reset_token', None)
return redirect(url_for('home'))
@app.route('/edit_profile', methods=['GET', 'POST'])
def edit_profile():
if 'username' not in session:
return redirect(url_for('home'))
conn = get_db_connection()
user = conn.execute(
"SELECT * FROM users WHERE id = ?",
(session['user_id'],)
).fetchone()
conn.close()
# 解析JSON格式的个人资料
import json
profile = json.loads(user['profile']) if user['profile'] else {}
if request.method == 'POST':
# 更新个人资料
new_profile = {
'name': request.form['name'],
'age': request.form['age'],
'gender': request.form['gender']
}
conn = get_db_connection()
conn.execute(
"UPDATE users SET profile = ? WHERE id = ?",
(json.dumps(new_profile), session['user_id'])
)
conn.commit()
conn.close()
return redirect(url_for('dashboard'))
return render_template('edit_profile.html', profile=profile)
@app.route('/social_login/<provider>')
def social_login(provider):
# 模拟第三方登录,实际应用需要实现OAuth流程
session['username'] = f"{provider}_user"
session.permanent = True # 记住登录状态
# 创建临时用户(实际应用应获取用户信息)
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("SELECT id FROM users WHERE username = ?", (session['username'],))
if not cursor.fetchone():
hashed_password = hashlib.sha256('social_password'.encode()).hexdigest()
cursor.execute(
"INSERT INTO users (username, password, email, verified, profile) VALUES (?, ?, ?, ?, ?)",
(session['username'], hashed_password, f'{provider}@example.com', 1, '{"name": "Social User", "age": 25, "gender": "Other"}')
)
conn.commit()
conn.close()
return redirect(url_for('dashboard'))
@app.route('/logout')
def logout():
session.pop('username', None)
session.pop('user_id', None)
return redirect(url_for('home'))
if __name__ == '__main__':
app.run(debug=True)
```
### HTML 模板文件(创建 templates 文件夹并添加以下文件)
1. **login.html** (登录页面)
```html
<!DOCTYPE html>
<html>
<head>
<title>用户登录</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5" style="max-width: 400px;">
<h2 class="mb-4">用户登录</h2>
{% if error %}
<div class="alert alert-danger">{{ error }}</div>
{% endif %}
<form action="/login" method="post">
<div class="mb-3">
<label for="username" class="form-label">用户名/邮箱</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">密码</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="remember" name="remember">
<label class="form-check-label" for="remember">记住登录状态</label>
</div>
<button type="submit" class="btn btn-primary w-100 mb-3">登录</button>
<div class="d-grid gap-2 mb-3">
<a href="/reset_password" class="btn btn-outline-secondary">忘记密码?</a>
</div>
<div class="d-grid gap-2 mb-3">
<a href="/verify_email" class="btn btn-outline-info">邮箱验证</a>
</div>
<hr>
<h5 class="text-center mt-4">第三方登录</h5>
<div class="d-flex justify-content-between mb-3">
<a href="/social_login/wechat" class="btn btn-success" style="flex: 1; margin-right: 5px;">微信登录</a>
<a href="/social_login/qq" class="btn btn-primary" style="flex: 1; margin-left: 5px;">QQ登录</a>
</div>
</form>
</div>
</body>
</html>
```
2. **dashboard.html** (主界面)
```html
<!DOCTYPE html>
<html>
<head>
<title>用户仪表盘</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>欢迎, {{ username }}!</h1>
<a href="/logout" class="btn btn-danger">退出登录</a>
</div>
<div class="card mb-4">
<div class="card-header">
<h3>用户资料</h3>
</div>
<div class="card-body">
<p><strong>姓名:</strong> {{ profile.name }}</p>
<p><strong>年龄:</strong> {{ profile.age }}</p>
<p><strong>性别:</strong> {{ profile.gender }}</p>
<a href="/edit_profile" class="btn btn-primary mt-2">编辑资料</a>
</div>
</div>
<div class="card">
<div class="card-header">
<h3>账户状态</h3>
</div>
<div class="card-body">
<p>邮箱验证状态:
{% if session.get('verified') %}
<span class="text-success">已验证</span>
{% else %}
<span class="text-danger">未验证</span>
<a href="/verify_email" class="btn btn-sm btn-outline-info ms-2">立即验证</a>
{% endif %}
</p>
</div>
</div>
</div>
</body>
</html>
```
3. **verify_email.html** (邮箱验证)
```html
<!DOCTYPE html>
<html>
<head>
<title>邮箱验证</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5" style="max-width: 500px;">
<h2 class="mb-4">邮箱验证</h2>
{% if error %}
<div class="alert alert-danger">{{ error }}</div>
{% endif %}
{% if message %}
<div class="alert alert-info">{{ message }}</div>
{% endif %}
<form action="/verify_email" method="post">
<div class="mb-3">
<label for="email" class="form-label">邮箱地址</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<button type="submit" class="btn btn-primary w-100 mb-4">发送验证码</button>
</form>
{% if message %}
<form action="/confirm_verification" method="post">
<div class="mb-3">
<label for="code" class="form-label">验证码</label>
<input type="text" class="form-control" id="code" name="code" required>
</div>
<button type="submit" class="btn btn-success w-100">验证</button>
</form>
{% endif %}
<div class="text-center mt-4">
<a href="/dashboard">返回仪表盘</a>
</div>
</div>
</body>
</html>
```
4. **reset_password.html** (密码重置)
```html
<!DOCTYPE html>
<html>
<head>
<title>重置密码</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5" style="max-width: 500px;">
<h2 class="mb-4">重置密码</h2>
{% if error %}
<div class="alert alert-danger">{{ error }}</div>
{% endif %}
<form action="/reset_password" method="post">
<div class="mb-3">
<label for="email" class="form-label">注册邮箱</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<button type="submit" class="btn btn-primary w-100">发送重置链接</button>
</form>
</div>
</body>
</html>
```
5. **reset_form.html** (重置密码表单)
```html
<!DOCTYPE html>
<html>
<head>
<title>设置新密码</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5" style="max-width: 500px;">
<h2 class="mb-4">设置新密码</h2>
{% if error %}
<div class="alert alert-danger">{{ error }}</div>
{% endif %}
<form action="/reset_password_form" method="post">
<input type="hidden" name="email" value="{{ request.args.get('email', '') }}">
<div class="mb-3">
<label for="password" class="form-label">新密码</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="mb-3">
<label for="confirm_password" class="form-label">确认密码</label>
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required>
</div>
<button type="submit" class="btn btn-success w-100">重置密码</button>
</form>
</div>
</body>
</html>
```
6. **edit_profile.html** (编辑资料)
```html
<!DOCTYPE html>
<html>
<head>
<title>编辑个人资料</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5" style="max-width: 500px;">
<h2 class="mb-4">编辑个人资料</h2>
<form action="/edit_profile" method="post">
<div class="mb-3">
<label for="name" class="form-label">姓名</label>
<input type="text" class="form-control" id="name" name="name" value="{{ profile.name }}" required>
</div>
<div class="mb-3">
<label for="age" class="form-label">年龄</label>
<input type="number" class="form-control" id="age" name="age" value="{{ profile.age }}" required>
</div>
<div class="mb-3">
<label class="form-label">性别</label>
<div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" id="male" value="Male"
{% if profile.gender == 'Male' %}checked{% endif %}>
<label class="form-check-label" for="male">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" id="female" value="Female"
{% if profile.gender == 'Female' %}checked{% endif %}>
<label class="form-check-label" for="female">女</label>
</div>
<div class="form-check form-check-inline