Files
s2f/backend/app/core/error_handlers.py
freedakgmail 33b4c734aa feat: 完善前后端核心功能模块
后端:
- 新增认证(auth)、任务(tasks)、映射(mappings)、对账(reconciliation)、异常(exceptions)、导出(exports) API
- 新增核心模块: database, security, permissions, tenant, exceptions, error_handlers
- 新增数据模型: user, company, reconciliation_task, field_mapping, uploaded_file 等
- 新增服务层: ai_recognizer, file_parser, file_storage, mapping, reconciliation 等
- 添加数据库迁移脚本

前端:
- 新增登录页面和仪表盘页面
- 新增任务列表、任务详情、字段映射页面
- 新增异常处理页面和规则设置页面
- 新增 API 代理路由 /api/[...path]
- 新增 UI 组件库 (button, card, dialog, input, table 等)
- 新增 auth 组件 (ProtectedRoute, PermissionGate)
- 新增 layout 组件 (Header, Sidebar)
- 新增 mapping 组件 (FieldMappingTable, AISuggestionPanel)
- 新增 API 客户端和 hooks (useAsync, useToast, usePermission 等)
- 新增状态管理 (auth-store, company-store, ui-store)
- 集成 Tailwind CSS 和 shadcn/ui 组件库

其他:
- 添加 Alembic 数据库迁移配置
- 添加初始化示例数据脚本
- 更新项目文档
2026-07-07 09:04:47 +08:00

191 lines
4.5 KiB
Python

from typing import Any
from fastapi import HTTPException, Request, status
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from sqlalchemy.exc import IntegrityError
from app.core.exceptions import (
BusinessException,
ExternalServiceException,
ForbiddenException,
NotFoundException,
S2FException,
UnauthorizedException,
ValidationException,
)
from app.core.logging import logger
def create_error_response(
code: str,
message: str,
details: Any = None,
status_code: int = status.HTTP_500_INTERNAL_SERVER_ERROR,
) -> JSONResponse:
"""
创建统一错误响应
Args:
code: 错误代码
message: 错误消息
details: 错误详情
status_code: HTTP 状态码
Returns:
JSON 响应
"""
error_data = {
"error": {
"code": code,
"message": message,
}
}
if details:
error_data["error"]["details"] = details
return JSONResponse(
status_code=status_code,
content=error_data,
)
async def s2f_exception_handler(request: Request, exc: S2FException) -> JSONResponse:
"""
处理自定义业务异常
Args:
request: 请求对象
exc: 异常对象
Returns:
JSON 响应
"""
logger.warning(
"business_exception",
code=exc.code,
message=exc.message,
details=exc.details,
path=request.url.path,
)
# 根据异常类型映射 HTTP 状态码
status_code_map = {
NotFoundException: status.HTTP_404_NOT_FOUND,
UnauthorizedException: status.HTTP_401_UNAUTHORIZED,
ForbiddenException: status.HTTP_403_FORBIDDEN,
ValidationException: status.HTTP_422_UNPROCESSABLE_ENTITY,
BusinessException: status.HTTP_400_BAD_REQUEST,
ExternalServiceException: status.HTTP_503_SERVICE_UNAVAILABLE,
}
status_code = status_code_map.get(type(exc), status.HTTP_500_INTERNAL_SERVER_ERROR)
return create_error_response(
code=exc.code,
message=exc.message,
details=exc.details,
status_code=status_code,
)
async def validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
"""
处理请求验证异常
Args:
request: 请求对象
exc: 验证异常
Returns:
JSON 响应
"""
logger.warning(
"validation_error",
errors=exc.errors(),
body=exc.body,
path=request.url.path,
)
return create_error_response(
code="VALIDATION_ERROR",
message="请求参数验证失败",
details={"errors": exc.errors()},
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
)
async def http_exception_handler(request: Request, exc: HTTPException) -> JSONResponse:
"""
处理 HTTP 异常
Args:
request: 请求对象
exc: HTTP 异常
Returns:
JSON 响应
"""
logger.warning(
"http_exception",
status_code=exc.status_code,
detail=exc.detail,
path=request.url.path,
)
return create_error_response(
code="HTTP_ERROR",
message=str(exc.detail),
status_code=exc.status_code,
)
async def integrity_error_handler(request: Request, exc: IntegrityError) -> JSONResponse:
"""
处理数据库完整性错误
Args:
request: 请求对象
exc: 完整性错误
Returns:
JSON 响应
"""
logger.error(
"database_integrity_error",
error=str(exc),
path=request.url.path,
)
return create_error_response(
code="DATABASE_ERROR",
message="数据库操作失败,可能存在重复或约束冲突",
status_code=status.HTTP_409_CONFLICT,
)
async def generic_exception_handler(request: Request, exc: Exception) -> JSONResponse:
"""
处理未捕获的异常
Args:
request: 请求对象
exc: 异常对象
Returns:
JSON 响应
"""
logger.error(
"unhandled_exception",
error=str(exc),
error_type=type(exc).__name__,
path=request.url.path,
exc_info=True,
)
return create_error_response(
code="INTERNAL_ERROR",
message="服务器内部错误",
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
)