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 数据库迁移配置 - 添加初始化示例数据脚本 - 更新项目文档
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
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,
|
||||
)
|
||||
Reference in New Issue
Block a user