2025-11-12 15:11:05 +08:00
|
|
|
|
# 创建Web蓝图
|
2025-11-22 20:32:49 +08:00
|
|
|
|
from flask import Blueprint, render_template, request, redirect, url_for, session, flash, jsonify, current_app
|
2025-11-11 21:39:12 +08:00
|
|
|
|
from flask_login import login_user, logout_user, login_required, current_user
|
|
|
|
|
|
from app.models.admin import Admin
|
|
|
|
|
|
from app import db
|
|
|
|
|
|
|
|
|
|
|
|
web_bp = Blueprint('web', __name__)
|
2025-11-19 22:49:24 +08:00
|
|
|
|
user_bp = Blueprint('user', __name__, url_prefix='/index')
|
2025-11-11 21:39:12 +08:00
|
|
|
|
|
|
|
|
|
|
@web_bp.route('/')
|
|
|
|
|
|
def index():
|
2025-11-19 22:49:24 +08:00
|
|
|
|
"""首页 - 重定向到前端主页"""
|
|
|
|
|
|
return redirect(url_for('user.user_index'))
|
2025-11-11 21:39:12 +08:00
|
|
|
|
|
|
|
|
|
|
@web_bp.route('/login', methods=['GET', 'POST'])
|
|
|
|
|
|
def login():
|
|
|
|
|
|
"""登录页面"""
|
2025-11-22 20:32:49 +08:00
|
|
|
|
try:
|
|
|
|
|
|
if request.method == 'POST':
|
|
|
|
|
|
current_app.logger.info(f"收到登录请求 - IP: {request.remote_addr}, User-Agent: {request.headers.get('User-Agent')}")
|
|
|
|
|
|
|
|
|
|
|
|
# 检查CSRF令牌 - 生产环境部署时更宽松的验证
|
|
|
|
|
|
csrf_token = request.form.get('csrf_token')
|
|
|
|
|
|
if not csrf_token:
|
|
|
|
|
|
current_app.logger.warning(f"登录请求缺少CSRF令牌 - IP: {request.remote_addr}, User-Agent: {request.headers.get('User-Agent')}, Origin: {request.headers.get('Origin')}, Referer: {request.headers.get('Referer')}")
|
|
|
|
|
|
# 在生产环境中,如果缺少CSRF令牌,记录警告但继续处理
|
|
|
|
|
|
# 这有助于解决域名部署时的CSRF问题
|
|
|
|
|
|
if current_app.config.get('FLASK_ENV') == 'production':
|
|
|
|
|
|
current_app.logger.info("生产环境: 缺少CSRF令牌但继续处理登录请求")
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 开发环境严格验证
|
|
|
|
|
|
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
|
'success': False,
|
|
|
|
|
|
'message': '缺少CSRF令牌,请刷新页面后重试'
|
|
|
|
|
|
}), 400
|
|
|
|
|
|
flash('缺少CSRF令牌,请刷新页面后重试', 'error')
|
|
|
|
|
|
return render_template('login.html')
|
|
|
|
|
|
|
|
|
|
|
|
username = request.form.get('username', '').strip()
|
|
|
|
|
|
password = request.form.get('password', '').strip()
|
|
|
|
|
|
|
|
|
|
|
|
current_app.logger.info(f"登录尝试 - 用户名: {username}, 来源IP: {request.remote_addr}")
|
|
|
|
|
|
|
|
|
|
|
|
if not username or not password:
|
|
|
|
|
|
# 对于AJAX请求,返回JSON错误
|
|
|
|
|
|
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
|
'success': False,
|
|
|
|
|
|
'message': '请输入用户名和密码'
|
|
|
|
|
|
}), 400
|
|
|
|
|
|
# 对于普通表单提交,使用flash消息
|
|
|
|
|
|
flash('请输入用户名和密码', 'error')
|
|
|
|
|
|
return render_template('login.html')
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 查找用户(排除已软删除的用户)
|
|
|
|
|
|
admin = Admin.query.filter_by(username=username, is_deleted=0).first()
|
|
|
|
|
|
|
|
|
|
|
|
# 验证用户和密码
|
|
|
|
|
|
if not admin:
|
|
|
|
|
|
current_app.logger.warning(f"登录失败 - 用户不存在: {username}")
|
|
|
|
|
|
failure_message = '用户名或密码错误'
|
|
|
|
|
|
elif not admin.is_active:
|
|
|
|
|
|
current_app.logger.warning(f"登录失败 - 账号未激活: {username}")
|
|
|
|
|
|
failure_message = '账号未激活'
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 验证密码
|
|
|
|
|
|
password_valid = admin.check_password(password)
|
|
|
|
|
|
|
|
|
|
|
|
if password_valid:
|
|
|
|
|
|
# 登录成功
|
|
|
|
|
|
current_app.logger.info(f"登录成功 - 用户名: {username}, IP: {request.remote_addr}")
|
|
|
|
|
|
login_user(admin, remember=True)
|
|
|
|
|
|
|
|
|
|
|
|
# 更新最后登录信息
|
|
|
|
|
|
try:
|
|
|
|
|
|
admin.update_last_login(request.remote_addr)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
current_app.logger.error(f"更新最后登录信息失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
# 如果是AJAX请求,返回JSON
|
|
|
|
|
|
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
|
|
|
|
|
import secrets
|
|
|
|
|
|
token = secrets.token_urlsafe(32)
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
|
'success': True,
|
|
|
|
|
|
'token': token,
|
|
|
|
|
|
'user': {
|
|
|
|
|
|
'username': admin.username,
|
|
|
|
|
|
'role': admin.role,
|
|
|
|
|
|
'is_super_admin': admin.is_super_admin()
|
|
|
|
|
|
},
|
|
|
|
|
|
'redirect': url_for('web.dashboard')
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
# 普通请求,重定向
|
|
|
|
|
|
next_page = request.args.get('next')
|
|
|
|
|
|
if next_page:
|
|
|
|
|
|
return redirect(next_page)
|
|
|
|
|
|
return redirect(url_for('web.dashboard'))
|
|
|
|
|
|
else:
|
|
|
|
|
|
current_app.logger.warning(f"登录失败 - 密码错误: {username}")
|
|
|
|
|
|
failure_message = '用户名或密码错误'
|
|
|
|
|
|
|
|
|
|
|
|
# 登录失败,返回错误信息
|
|
|
|
|
|
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
|
'success': False,
|
|
|
|
|
|
'message': failure_message
|
|
|
|
|
|
}), 401
|
|
|
|
|
|
flash(failure_message, 'error')
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
current_app.logger.error(f"登录过程中发生错误 - 用户名: {username}, 错误: {str(e)}")
|
|
|
|
|
|
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
|
'success': False,
|
|
|
|
|
|
'message': '登录过程中发生错误,请稍后重试'
|
|
|
|
|
|
}), 500
|
|
|
|
|
|
flash('登录过程中发生错误,请稍后重试', 'error')
|
|
|
|
|
|
return render_template('login.html')
|
|
|
|
|
|
|
|
|
|
|
|
return render_template('login.html')
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
current_app.logger.error(f"登录路由发生严重错误: {str(e)}", exc_info=True)
|
|
|
|
|
|
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
|
'success': False,
|
|
|
|
|
|
'message': '服务器内部错误,请联系管理员'
|
|
|
|
|
|
}), 500
|
|
|
|
|
|
flash('服务器内部错误,请联系管理员', 'error')
|
|
|
|
|
|
return render_template('login.html')
|
2025-11-11 21:39:12 +08:00
|
|
|
|
|
|
|
|
|
|
@web_bp.route('/logout')
|
|
|
|
|
|
@login_required
|
|
|
|
|
|
def logout():
|
|
|
|
|
|
"""退出登录"""
|
|
|
|
|
|
logout_user()
|
|
|
|
|
|
flash('已退出登录', 'info')
|
|
|
|
|
|
return redirect(url_for('web.login'))
|
|
|
|
|
|
|
|
|
|
|
|
@web_bp.route('/dashboard')
|
|
|
|
|
|
@login_required
|
|
|
|
|
|
def dashboard():
|
|
|
|
|
|
"""仪表板"""
|
|
|
|
|
|
return render_template('dashboard.html')
|
|
|
|
|
|
|
|
|
|
|
|
@web_bp.route('/favicon.ico')
|
|
|
|
|
|
def favicon():
|
|
|
|
|
|
"""Favicon 处理 - 返回空响应避免404错误"""
|
|
|
|
|
|
from flask import Response
|
|
|
|
|
|
return Response(status=204) # No Content
|
|
|
|
|
|
|
|
|
|
|
|
# 导入视图函数
|
2025-11-19 22:49:24 +08:00
|
|
|
|
from . import views, user_views
|