创建 outlook_web/middleware/ 目录,从 legacy.py 迁移: 新增文件: - middleware/__init__.py - 模块导出 - middleware/trace.py - trace_id 中间件 - ensure_trace_id(): 为每个请求生成/透传 trace_id - attach_trace_id_and_normalize_errors(): 统一写入 X-Trace-Id,标准化错误格式 - middleware/error_handler.py - 错误处理中间件 - handle_http_exception(): 处理 HTTP 异常 - handle_exception(): 处理未捕获异常 更新文件: - app.py: 从 middleware 模块导入,替代 legacy 引用 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
79 lines
2.2 KiB
Python
79 lines
2.2 KiB
Python
"""
|
||
错误处理中间件
|
||
|
||
功能:
|
||
- 处理可预期的 HTTP 异常
|
||
- 处理未捕获的异常
|
||
- 返回统一错误结构
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from flask import g, jsonify, request
|
||
from werkzeug.exceptions import HTTPException
|
||
|
||
from outlook_web.errors import build_error_payload
|
||
|
||
|
||
def handle_http_exception(error: HTTPException):
|
||
"""处理可预期的 HTTP 异常,返回统一错误结构(仅对 API/JSON 请求返回 JSON)"""
|
||
status_code = error.code or 500
|
||
|
||
message_map = {
|
||
400: "请求参数错误",
|
||
401: "未授权",
|
||
403: "无权限",
|
||
404: "资源不存在",
|
||
405: "请求方法不被允许",
|
||
429: "请求过于频繁,请稍后再试",
|
||
}
|
||
message = message_map.get(status_code, "请求失败")
|
||
|
||
trace_id_value = None
|
||
try:
|
||
trace_id_value = getattr(g, 'trace_id', None)
|
||
except Exception:
|
||
trace_id_value = None
|
||
|
||
error_payload = build_error_payload(
|
||
code="HTTP_ERROR",
|
||
message=message,
|
||
err_type="HttpError",
|
||
status=status_code,
|
||
details=str(error),
|
||
trace_id=trace_id_value
|
||
)
|
||
|
||
if request.path.startswith('/api/') or request.is_json:
|
||
return jsonify({'success': False, 'error': error_payload}), status_code
|
||
|
||
return f"{message} (trace_id={error_payload.get('trace_id')})", status_code
|
||
|
||
|
||
def handle_exception(error):
|
||
"""处理未捕获的异常"""
|
||
trace_id_value = None
|
||
try:
|
||
trace_id_value = getattr(g, 'trace_id', None)
|
||
except Exception:
|
||
trace_id_value = None
|
||
|
||
try:
|
||
from flask import current_app
|
||
current_app.logger.exception("Unhandled exception trace_id=%s", trace_id_value or "unknown")
|
||
except Exception:
|
||
pass
|
||
|
||
error_payload = build_error_payload(
|
||
code="INTERNAL_ERROR",
|
||
message="服务器内部错误",
|
||
err_type="UnhandledException",
|
||
status=500,
|
||
details=str(error),
|
||
trace_id=trace_id_value
|
||
)
|
||
|
||
if request.path.startswith('/api/') or request.is_json:
|
||
return jsonify({'success': False, 'error': error_payload}), 500
|
||
|
||
return f"服务器内部错误 (trace_id={error_payload.get('trace_id')})", 500
|