150 lines
5.8 KiB
Python
Executable File
150 lines
5.8 KiB
Python
Executable File
from flask import render_template, jsonify, request, flash, redirect, url_for
|
|
from sqlalchemy.exc import SQLAlchemyError, IntegrityError, OperationalError
|
|
from werkzeug.exceptions import HTTPException, NotFound, Forbidden, BadRequest
|
|
import traceback
|
|
|
|
class ErrorHandler:
|
|
"""
|
|
全局异常处理器
|
|
用于注册Flask应用程序的错误处理函数
|
|
"""
|
|
def __init__(self, app=None):
|
|
if app:
|
|
self.init_app(app)
|
|
|
|
def init_app(self, app):
|
|
"""
|
|
初始化应用程序的错误处理器
|
|
:param app: Flask应用实例
|
|
"""
|
|
# 处理SQLAlchemy错误
|
|
app.register_error_handler(SQLAlchemyError, self.handle_db_error)
|
|
app.register_error_handler(IntegrityError, self.handle_integrity_error)
|
|
app.register_error_handler(OperationalError, self.handle_operational_error)
|
|
|
|
# 处理HTTP错误
|
|
app.register_error_handler(NotFound, self.handle_not_found)
|
|
app.register_error_handler(Forbidden, self.handle_forbidden)
|
|
app.register_error_handler(BadRequest, self.handle_bad_request)
|
|
|
|
# 处理通用错误
|
|
app.register_error_handler(Exception, self.handle_generic_error)
|
|
|
|
# 记录初始化完成
|
|
app.logger.info("错误处理器初始化完成")
|
|
|
|
def handle_db_error(self, error):
|
|
"""
|
|
处理一般数据库错误
|
|
"""
|
|
error_details = str(error)
|
|
traceback_details = traceback.format_exc()
|
|
current_app = self._get_current_app()
|
|
current_app.logger.error(f"数据库错误: {error_details}\n{traceback_details}")
|
|
|
|
return self._handle_error("数据库操作错误",
|
|
"抱歉,数据库操作失败,请稍后重试。",
|
|
500)
|
|
|
|
def handle_integrity_error(self, error):
|
|
"""
|
|
处理数据完整性错误(如违反唯一约束)
|
|
"""
|
|
error_details = str(error)
|
|
current_app = self._get_current_app()
|
|
current_app.logger.error(f"数据完整性错误: {error_details}")
|
|
|
|
# 尝试从错误消息中提取更友好的错误消息
|
|
friendly_message = "数据冲突,请检查您的输入。"
|
|
if "Duplicate entry" in error_details:
|
|
friendly_message = "数据已存在,请检查您的输入。"
|
|
|
|
return self._handle_error("数据完整性错误", friendly_message, 400)
|
|
|
|
def handle_operational_error(self, error):
|
|
"""
|
|
处理数据库操作错误(如连接问题)
|
|
"""
|
|
error_details = str(error)
|
|
current_app = self._get_current_app()
|
|
current_app.logger.error(f"数据库操作错误: {error_details}")
|
|
|
|
return self._handle_error("数据库连接错误",
|
|
"抱歉,无法连接到数据库,请稍后重试。",
|
|
503)
|
|
|
|
def handle_not_found(self, error):
|
|
"""
|
|
处理404错误
|
|
"""
|
|
return self._handle_error("页面未找到",
|
|
"抱歉,您请求的页面不存在。",
|
|
404)
|
|
|
|
def handle_forbidden(self, error):
|
|
"""
|
|
处理403错误
|
|
"""
|
|
return self._handle_error("访问被拒绝",
|
|
"抱歉,您没有权限访问此页面。",
|
|
403)
|
|
|
|
def handle_bad_request(self, error):
|
|
"""
|
|
处理400错误
|
|
"""
|
|
return self._handle_error("请求错误",
|
|
"抱歉,服务器无法理解您的请求。",
|
|
400)
|
|
|
|
def handle_generic_error(self, error):
|
|
"""
|
|
处理未被其他处理程序捕获的通用错误
|
|
"""
|
|
error_details = str(error)
|
|
traceback_details = traceback.format_exc()
|
|
current_app = self._get_current_app()
|
|
current_app.logger.error(f"未处理异常: {error_details}\n{traceback_details}")
|
|
|
|
if isinstance(error, HTTPException):
|
|
return self._handle_error("请求错误", error.description, error.code)
|
|
|
|
return self._handle_error("服务器错误",
|
|
"抱歉,服务器遇到了未知错误,请稍后重试。",
|
|
500)
|
|
|
|
def _handle_error(self, title, message, status_code):
|
|
"""
|
|
根据请求类型返回适当的错误响应
|
|
:param title: 错误标题
|
|
:param message: 错误消息
|
|
:param status_code: HTTP状态码
|
|
"""
|
|
# 根据接受的内容类型决定返回JSON还是HTML
|
|
# 检查是否是AJAX请求,is_xhr在新版Flask中已被移除
|
|
is_ajax_request = request.headers.get('X-Requested-With') == 'XMLHttpRequest'
|
|
if is_ajax_request or request.headers.get('Accept') == 'application/json':
|
|
# 返回JSON格式的错误
|
|
return jsonify({
|
|
'error': title,
|
|
'message': message
|
|
}), status_code
|
|
else:
|
|
# 使用Flash消息并重定向到统计页面
|
|
from flask import current_app
|
|
flash(message)
|
|
|
|
# 根据错误类型决定重定向页面
|
|
if status_code == 404:
|
|
return redirect(url_for('statistics'))
|
|
elif status_code == 403:
|
|
return redirect(url_for('statistics'))
|
|
else:
|
|
return redirect(url_for('statistics'))
|
|
|
|
def _get_current_app(self):
|
|
"""
|
|
获取当前Flask应用实例
|
|
"""
|
|
from flask import current_app
|
|
return current_app |