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/') 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/') 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/', 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//') 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')