# 统一错误处理 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 } } )