jiachenlong/backend/app/core/error_handler.py

175 lines
6.0 KiB
Python
Raw Permalink Normal View History

2026-03-23 11:08:52 +08:00
# 统一错误处理
from fastapi import FastAPI, Request, HTTPException, status
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from pydantic import ValidationError
from app.core.logging_config import logger
# 错误码定义
ERROR_CODES = {
# 认证错误 (10-19)
401: "E00010", # 未授权
403: "E00014", # 禁止访问
# 验证错误 (20-29)
400: "E00000", # 请求错误
422: "E00000", # 验证错误
# 资源错误 (30-39)
404: "E00033", # 资源不存在
# 服务器错误 (50-59)
500: "E00003", # 服务器内部错误
}
# 错误信息映射
ERROR_MESSAGES = {
"E00010": "未登录或登录已过期",
"E00011": "用户名或密码错误",
"E00012": "验证码错误",
"E00014": "无权访问此资源",
"E00015": "令牌无效或已过期",
"E00020": "请输入用户名和密码",
"E00021": "用户名至少 3 个字符",
"E00022": "密码至少 6 个字符",
"E00023": "用户名已存在",
"E00024": "邮箱已被注册",
"E00030": "藏品名称不能为空",
"E00031": "藏品名称至少 2 个字符",
"E00032": "藏品分类不能为空",
"E00033": "藏品不存在",
"E00034": "禁止重复:此冠字号已存在",
"E00035": "成本价格必须>=0",
"E00036": "目标价格必须>=0",
"E00037": "发行年份必须是 4 位数字",
"E00040": "请选择图片文件",
"E00041": "图片尺寸太小,无法识别",
"E00042": "OCR 识别失败,请重试",
"E00050": "仅管理员可访问",
"E00051": "用户不存在",
"E00000": "请求失败",
"E00001": "网络连接失败",
"E00003": "服务器内部错误",
}
def setup_error_handlers(app: FastAPI):
"""设置全局错误处理器"""
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
"""处理 HTTP 异常"""
# 从 detail 中提取错误码
detail = exc.detail
error_code = ERROR_CODES.get(exc.status_code, "E00000")
# 如果 detail 已经包含错误码,直接使用
if isinstance(detail, str) and detail.startswith("E"):
parts = detail.split(":", 1)
error_code = parts[0]
message = parts[1].strip() if len(parts) > 1 else ERROR_MESSAGES.get(error_code, detail)
else:
message = ERROR_MESSAGES.get(error_code, detail if isinstance(detail, str) else "请求失败")
return JSONResponse(
status_code=exc.status_code,
content={
"error": {
"code": error_code,
"message": message,
"status": exc.status_code
}
}
)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
"""处理请求验证错误"""
errors = exc.errors()
if errors:
error = errors[0]
field = ".".join(str(x) for x in error.get("loc", []))
msg = error.get("msg", "验证失败")
# 根据字段和消息匹配错误码
# 先检查请求路径,区分用户接口和藏品接口
path = request.url.path
if "cost_price" in field or "价格" in msg:
error_code = "E00035"
message = "成本价格必须>=0"
elif "target_price" in field:
error_code = "E00036"
message = "目标价格必须>=0"
elif "issue_year" in field or "年份" in msg:
error_code = "E00037"
message = "发行年份必须是 4 位数字"
elif "name" in field:
# 根据路径区分用户 name 和藏品 name
if "/auth/" in path or "/users/" in path or "/admin/users/" in path:
error_code = "E00021"
message = "用户名至少 3 个字符"
else:
error_code = "E00031"
message = "藏品名称至少 2 个字符"
elif "category" in field:
error_code = "E00032"
message = "藏品分类不能为空"
else:
error_code = "E00000"
message = f"{field}: {msg}"
return JSONResponse(
status_code=422,
content={
"error": {
"code": error_code,
"message": message,
"status": 422,
"field": field
}
}
)
return JSONResponse(
status_code=422,
content={
"error": {
"code": "E00000",
"message": "验证失败",
"status": 422
}
}
)
@app.exception_handler(404)
async def not_found_handler(request: Request, exc: Exception):
"""处理 404 错误"""
return JSONResponse(
status_code=404,
content={
"error": {
"code": "E00033",
"message": "接口不存在",
"status": 404
}
}
)
@app.exception_handler(Exception)
async def general_exception_handler(request: Request, exc: Exception):
"""处理未捕获的异常"""
import traceback
error_trace = traceback.format_exc()
logger.error(f"未捕获异常:{str(exc)}\n{error_trace}")
return JSONResponse(
status_code=500,
content={
"error": {
"code": "E00003",
"message": "服务器内部错误",
"status": 500
}
}
)