175 lines
6.0 KiB
Python
175 lines
6.0 KiB
Python
# 统一错误处理
|
|
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
|
|
}
|
|
}
|
|
)
|