290 lines
9.4 KiB
Python
290 lines
9.4 KiB
Python
from flask import Blueprint, render_template, redirect, url_for, flash, request, session, jsonify
|
|
from flask_login import login_user, logout_user, current_user, login_required
|
|
from werkzeug.urls import url_parse
|
|
from app import db
|
|
from app.models import User
|
|
from app.forms import LoginForm, RegistrationForm, ResetPasswordRequestForm, ResetPasswordForm
|
|
from app.translations import get_text
|
|
from datetime import datetime
|
|
from app.utils.email import generate_verification_code, send_verification_email, save_verification_code
|
|
from app.forms import EmailVerificationForm, RegistrationForm
|
|
import re
|
|
from flask import send_file
|
|
from PIL import Image, ImageDraw
|
|
import io
|
|
from flask import current_app as app
|
|
|
|
main_bp = Blueprint('main', __name__)
|
|
auth_bp = Blueprint('auth', __name__)
|
|
|
|
|
|
# 辅助函数
|
|
def _get_translation(key):
|
|
lang = session.get('language', 'zh')
|
|
return get_text(key, lang)
|
|
|
|
|
|
# 主页路由
|
|
@main_bp.route('/')
|
|
def index():
|
|
if not current_user.is_authenticated:
|
|
return redirect(url_for('auth.login'))
|
|
return render_template('main/index.html')
|
|
|
|
|
|
# 切换语言
|
|
@main_bp.route('/language/<lang>')
|
|
def set_language(lang):
|
|
# 确保只接受有效的语言
|
|
if lang in ['zh', 'en']:
|
|
session['language'] = lang
|
|
if current_user.is_authenticated:
|
|
current_user.language = lang
|
|
db.session.commit()
|
|
|
|
# 确保重定向回正确的页面
|
|
next_page = request.args.get('next') or request.referrer or url_for('main.index')
|
|
return redirect(next_page)
|
|
|
|
|
|
# 切换主题
|
|
@main_bp.route('/theme/<theme>')
|
|
def set_theme(theme):
|
|
if theme not in ['light', 'dark']:
|
|
theme = 'light'
|
|
session['theme'] = theme
|
|
if current_user.is_authenticated:
|
|
current_user.theme = theme
|
|
db.session.commit()
|
|
next_page = request.args.get('next') or request.referrer or url_for('main.index')
|
|
return redirect(next_page)
|
|
|
|
|
|
# 认证路由
|
|
@auth_bp.route('/login', methods=['GET', 'POST'])
|
|
def login():
|
|
if current_user.is_authenticated:
|
|
return redirect(url_for('main.index'))
|
|
|
|
form = LoginForm()
|
|
if form.validate_on_submit():
|
|
user = User.query.filter_by(email=form.email.data).first()
|
|
if user is None or not user.check_password(form.password.data):
|
|
flash(_get_translation('invalid_credentials'))
|
|
return redirect(url_for('auth.login'))
|
|
|
|
login_user(user, remember=form.remember_me.data)
|
|
|
|
# 更新最后登录时间
|
|
user.last_login = datetime.utcnow()
|
|
db.session.commit()
|
|
|
|
# 同步用户的语言和主题设置
|
|
session['language'] = user.language
|
|
session['theme'] = user.theme
|
|
|
|
flash(_get_translation('login_success'))
|
|
|
|
next_page = request.args.get('next')
|
|
if not next_page or url_parse(next_page).netloc != '':
|
|
next_page = url_for('main.index')
|
|
return redirect(next_page)
|
|
|
|
# 添加对模板的函数
|
|
def _(key):
|
|
return _get_translation(key)
|
|
|
|
return render_template('auth/login.html', form=form, _=_, hide_language_switch=True, hide_theme_switch=True)
|
|
|
|
|
|
@auth_bp.route('/register', methods=['GET', 'POST'])
|
|
def register():
|
|
if current_user.is_authenticated:
|
|
return redirect(url_for('main.index'))
|
|
|
|
email_form = EmailVerificationForm()
|
|
registration_form = RegistrationForm()
|
|
|
|
# 处理发送验证码请求
|
|
if 'send_code' in request.form and email_form.validate_on_submit():
|
|
email = email_form.email.data
|
|
code = generate_verification_code()
|
|
|
|
# 保存验证码
|
|
save_verification_code(email, code)
|
|
|
|
# 发送验证码邮件
|
|
if send_verification_email(email, code):
|
|
flash(_get_translation('verification_code_sent'))
|
|
|
|
# 将邮箱保存到表单,用于下一步注册
|
|
return render_template(
|
|
'auth/register.html',
|
|
email_form=email_form,
|
|
registration_form=registration_form,
|
|
email_verified=True,
|
|
verified_email=email
|
|
)
|
|
else:
|
|
flash(_get_translation('email_send_failed'))
|
|
|
|
# 处理注册表单提交
|
|
if 'register' in request.form and registration_form.validate_on_submit():
|
|
user = User(
|
|
email=registration_form.email.data,
|
|
username=registration_form.username.data,
|
|
language=session.get('language', 'zh'),
|
|
theme=session.get('theme', 'light')
|
|
)
|
|
user.set_password(registration_form.password.data)
|
|
db.session.add(user)
|
|
db.session.commit()
|
|
|
|
flash(_get_translation('account_created'))
|
|
return redirect(url_for('auth.login'))
|
|
|
|
# 检查是否有保存的已验证邮箱
|
|
email_verified = False
|
|
verified_email = request.args.get('email', '')
|
|
|
|
if verified_email:
|
|
email_verified = True
|
|
registration_form.email.data = verified_email
|
|
|
|
# 添加对模板的函数
|
|
def _(key):
|
|
return _get_translation(key)
|
|
|
|
return render_template(
|
|
'auth/register.html',
|
|
email_form=email_form,
|
|
registration_form=registration_form,
|
|
email_verified=email_verified,
|
|
verified_email=verified_email,
|
|
_=_,
|
|
hide_language_switch=True,
|
|
hide_theme_switch=True
|
|
)
|
|
|
|
|
|
@auth_bp.route('/send_verification_code', methods=['POST'])
|
|
def send_verification_code():
|
|
try:
|
|
email = request.form.get('email')
|
|
|
|
if not email or not re.match(r'[^@]+@[^@]+\.[^@]+', email):
|
|
return jsonify({'success': False, 'message': _get_translation('invalid_email')})
|
|
|
|
# 检查邮箱是否已被注册
|
|
try:
|
|
if User.query.filter_by(email=email).first():
|
|
return jsonify({'success': False, 'message': _get_translation('email_already_registered')})
|
|
except Exception as e:
|
|
app.logger.error(f"数据库查询错误: {str(e)}")
|
|
return jsonify({'success': False, 'message': _get_translation('server_error')})
|
|
|
|
# 生成并保存验证码
|
|
try:
|
|
code = generate_verification_code()
|
|
save_verification_code(email, code)
|
|
except Exception as e:
|
|
app.logger.error(f"验证码保存错误: {str(e)}")
|
|
return jsonify({'success': False, 'message': _get_translation('server_error')})
|
|
|
|
# 发送验证码邮件
|
|
try:
|
|
if send_verification_email(email, code):
|
|
return jsonify({'success': True, 'message': _get_translation('verification_code_sent')})
|
|
else:
|
|
return jsonify({'success': False, 'message': _get_translation('email_send_failed')})
|
|
except Exception as e:
|
|
app.logger.error(f"邮件发送错误: {str(e)}")
|
|
return jsonify({'success': False, 'message': _get_translation('email_send_failed')})
|
|
except Exception as e:
|
|
app.logger.error(f"验证码发送路由错误: {str(e)}")
|
|
return jsonify({'success': False, 'message': _get_translation('server_error')})
|
|
|
|
|
|
@auth_bp.route('/reset_password_request', methods=['GET', 'POST'])
|
|
def reset_password_request():
|
|
if current_user.is_authenticated:
|
|
return redirect(url_for('main.index'))
|
|
|
|
form = ResetPasswordRequestForm()
|
|
if form.validate_on_submit():
|
|
user = User.query.filter_by(email=form.email.data).first()
|
|
if user:
|
|
token = user.generate_reset_token()
|
|
db.session.commit()
|
|
|
|
# 这里应该发送邮件,但目前只是模拟
|
|
# 实际项目中需要集成邮件发送功能
|
|
flash(_get_translation('password_reset_sent'))
|
|
|
|
# 用于演示,实际项目中应该通过邮件发送这个链接
|
|
reset_url = url_for('auth.reset_password', token=token, _external=True)
|
|
print(f"Password reset URL: {reset_url}")
|
|
|
|
return redirect(url_for('auth.login'))
|
|
|
|
return render_template('auth/reset_password_request.html', form=form)
|
|
|
|
|
|
@auth_bp.route('/reset_password/<token>', methods=['GET', 'POST'])
|
|
def reset_password(token):
|
|
if current_user.is_authenticated:
|
|
return redirect(url_for('main.index'))
|
|
|
|
user = User.query.filter_by(reset_token=token).first()
|
|
if not user:
|
|
flash(_get_translation('invalid_reset_token'))
|
|
return redirect(url_for('auth.login'))
|
|
|
|
form = ResetPasswordForm()
|
|
if form.validate_on_submit():
|
|
user.set_password(form.password.data)
|
|
user.reset_token = None
|
|
db.session.commit()
|
|
flash(_get_translation('password_reset_success'))
|
|
return redirect(url_for('auth.login'))
|
|
|
|
return render_template('auth/reset_password.html', form=form)
|
|
|
|
|
|
@auth_bp.route('/logout')
|
|
@login_required
|
|
def logout():
|
|
logout_user()
|
|
return redirect(url_for('auth.login'))
|
|
|
|
|
|
@main_bp.route('/api/placeholder/<width>/<height>')
|
|
def placeholder(width, height):
|
|
"""生成并返回指定尺寸的占位图像"""
|
|
try:
|
|
width = int(width)
|
|
height = int(height)
|
|
except ValueError:
|
|
width = 100
|
|
height = 100
|
|
|
|
# 限制最大尺寸
|
|
width = min(width, 1200)
|
|
height = min(height, 1200)
|
|
|
|
# 创建简单的占位图
|
|
img = Image.new('RGB', (width, height), color=(220, 220, 220))
|
|
d = ImageDraw.Draw(img)
|
|
|
|
# 绘制边框
|
|
d.rectangle([0, 0, width - 1, height - 1], outline=(200, 200, 200))
|
|
|
|
# 绘制文本
|
|
text = f"{width}x{height}"
|
|
|
|
# 将图像转换为字节流
|
|
img_byte_arr = io.BytesIO()
|
|
img.save(img_byte_arr, format='PNG')
|
|
img_byte_arr.seek(0)
|
|
|
|
return send_file(img_byte_arr, mimetype='image/png') |