From 0bcfeaa6d644d4aa19dd6e34b1ead0b5be639b25 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 22 May 2026 11:58:15 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BF=83=E8=B7=B3=E5=88=B7?= =?UTF-8?q?=E5=85=A5rollback=E8=AF=AF=E7=94=A8=20+=20API=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E8=A7=84=E8=8C=83=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - heartbeat_flush: 移除循环内session.rollback(),防止一个服务器 刷入失败导致整个批次session状态损坏 - assets.py: 为Query参数添加Query()类型+描述+校验约束, 统一FastAPI参数风格 (server_id/session_id/limit/parent_id) Co-Authored-By: Claude Sonnet 4.6 --- server/api/assets.py | 14 +++++++------- server/background/heartbeat_flush.py | 4 +++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/server/api/assets.py b/server/api/assets.py index 6f0cc782..20064358 100644 --- a/server/api/assets.py +++ b/server/api/assets.py @@ -3,7 +3,7 @@ All operations require JWT authentication. """ from typing import Optional -from fastapi import APIRouter, Depends, HTTPException, Request +from fastapi import APIRouter, Depends, HTTPException, Query from server.api.dependencies import get_db from server.api.auth_jwt import get_current_admin @@ -119,7 +119,7 @@ async def delete_platform( @router.get("/nodes", response_model=list) async def list_nodes( - parent_id: Optional[int] = None, + parent_id: Optional[int] = Query(None, description="Filter by parent node ID"), admin: Admin = Depends(get_current_admin), db: AsyncSession = Depends(get_db), ): @@ -218,8 +218,8 @@ async def delete_node( @router.get("/ssh-sessions", response_model=list) async def list_ssh_sessions( - server_id: Optional[int] = None, - limit: int = 50, + server_id: Optional[int] = Query(None, description="Filter by server ID"), + limit: int = Query(50, ge=1, le=200), admin: Admin = Depends(get_current_admin), db: AsyncSession = Depends(get_db), ): @@ -238,9 +238,9 @@ async def list_ssh_sessions( @router.get("/command-logs", response_model=list) async def list_command_logs( - server_id: Optional[int] = None, - session_id: Optional[str] = None, - limit: int = 200, + server_id: Optional[int] = Query(None, description="Filter by server ID"), + session_id: Optional[str] = Query(None, description="Filter by SSH session ID"), + limit: int = Query(200, ge=1, le=500), admin: Admin = Depends(get_current_admin), db: AsyncSession = Depends(get_db), ): diff --git a/server/background/heartbeat_flush.py b/server/background/heartbeat_flush.py index 38e00552..7bb75f6d 100644 --- a/server/background/heartbeat_flush.py +++ b/server/background/heartbeat_flush.py @@ -91,7 +91,9 @@ async def heartbeat_flush_loop(): flushed += 1 except Exception as e: logger.warning(f"Failed to flush server {key}: {e}") - await session.rollback() + # Don't rollback here — just skip this server and continue. + # A partial UPDATE on a single server won't corrupt others. + # The final commit() will persist all successful updates. await session.commit() logger.info(f"Heartbeat flush: {flushed}/{len(keys)} servers updated to MySQL")