""" External API Router. Provides endpoints for external system integration (e.g., Sample Center). All endpoints require admin authentication. """ from fastapi import APIRouter, HTTPException, status, Request from schemas.external import ( ProjectInitRequest, ProjectInitResponse, ProgressResponse, ExternalExportRequest, ExternalExportResponse, ErrorResponse ) from services.external_service import ExternalService import logging logger = logging.getLogger(__name__) router = APIRouter( prefix="/api/external", tags=["external"] ) def verify_admin(request: Request) -> dict: """ 验证请求者是否为管理员 Args: request: FastAPI Request对象 Returns: 用户信息字典 Raises: HTTPException: 401 如果未认证,403 如果不是管理员 """ user = getattr(request.state, 'user', None) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail={ "error_code": "INVALID_TOKEN", "message": "Token无效或已过期" } ) if user.get("role") != "admin": raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail={ "error_code": "PERMISSION_DENIED", "message": "权限不足,需要管理员权限" } ) return user @router.post("/projects/init", response_model=ProjectInitResponse, status_code=status.HTTP_201_CREATED) async def init_project(request: Request, init_request: ProjectInitRequest): """ 项目初始化接口 创建新的标注项目并批量导入任务数据。 Args: request: FastAPI Request对象 init_request: 项目初始化请求 Returns: ProjectInitResponse: 项目初始化响应,包含项目ID Raises: HTTPException: 401 未授权,403 权限不足,400 请求参数错误 """ # 验证管理员权限 user = verify_admin(request) # 验证请求数据 if not init_request.data: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail={ "error_code": "INVALID_REQUEST", "message": "任务数据列表不能为空" } ) try: # 调用服务创建项目 result = ExternalService.init_project(init_request, user["id"]) logger.info(f"外部系统创建项目成功: {result.project_id}, 任务数: {result.task_count}") return result except Exception as e: logger.error(f"创建项目失败: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail={ "error_code": "INTERNAL_ERROR", "message": f"创建项目失败: {str(e)}" } ) @router.get("/projects/{project_id}/progress", response_model=ProgressResponse) async def get_project_progress(request: Request, project_id: str): """ 项目进度查询接口 查询指定项目的标注进度和人员完成情况。 Args: request: FastAPI Request对象 project_id: 项目ID Returns: ProgressResponse: 项目进度响应 Raises: HTTPException: 401 未授权,403 权限不足,404 项目不存在 """ # 验证管理员权限 verify_admin(request) try: # 调用服务获取进度 result = ExternalService.get_project_progress(project_id) if not result: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={ "error_code": "PROJECT_NOT_FOUND", "message": "项目不存在", "details": {"project_id": project_id} } ) return result except HTTPException: raise except Exception as e: logger.error(f"查询项目进度失败: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail={ "error_code": "INTERNAL_ERROR", "message": f"查询进度失败: {str(e)}" } ) @router.post("/projects/{project_id}/export", response_model=ExternalExportResponse) async def export_project_data( request: Request, project_id: str, export_request: ExternalExportRequest ): """ 数据导出接口 导出项目的标注数据,支持多种格式。 Args: request: FastAPI Request对象 project_id: 项目ID export_request: 导出请求 Returns: ExternalExportResponse: 导出响应,包含下载链接 Raises: HTTPException: 401 未授权,403 权限不足,404 项目不存在 """ # 验证管理员权限 verify_admin(request) # 检查项目是否存在 if not ExternalService.check_project_exists(project_id): raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={ "error_code": "PROJECT_NOT_FOUND", "message": "项目不存在", "details": {"project_id": project_id} } ) try: # 获取基础URL base_url = str(request.base_url).rstrip('/') # 调用服务导出数据 result = ExternalService.export_project_data( project_id, export_request, base_url ) if not result: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail={ "error_code": "EXPORT_FAILED", "message": "导出失败" } ) logger.info(f"项目数据导出成功: {project_id}, 格式: {export_request.format.value}, 数量: {result.total_exported}") # 如果有回调URL,异步发送通知(这里简化处理,实际应使用后台任务) if export_request.callback_url: logger.info(f"回调URL已配置: {export_request.callback_url}") # TODO: 实现异步回调通知 return result except HTTPException: raise except Exception as e: logger.error(f"导出项目数据失败: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail={ "error_code": "EXPORT_FAILED", "message": f"导出失败: {str(e)}" } )