diff --git a/src/backend/app/__init__.py b/src/backend/app/__init__.py index 3963a5e..e7d731a 100644 --- a/src/backend/app/__init__.py +++ b/src/backend/app/__init__.py @@ -3,8 +3,8 @@ from src.backend.app.api.endpoints import router from src.backend.app.utils.logger import setup_logging from src.backend.config import settings -app = FastAPI() -app.include_router(router,prefix="/api") +api_app = FastAPI() +api_app.include_router(router,prefix="/api") def create_app() -> FastAPI: # 设置日志 diff --git a/src/backend/app/api/command_parser.py b/src/backend/app/api/command_parser.py index 071473e..bb8288f 100644 --- a/src/backend/app/api/command_parser.py +++ b/src/backend/app/api/command_parser.py @@ -1,8 +1,7 @@ -from typing import Dict, Any +from typing import Dict, Any, Optional from src.backend.app.services.ai_services import AIService from src.backend.config import settings - class CommandParser: def __init__(self): self.ai_service = AIService(settings.SILICONFLOW_API_KEY, settings.SILICONFLOW_API_URL) @@ -19,7 +18,8 @@ class CommandParser: # 本地无法解析则调用AI服务 return await self.ai_service.parse_command(command) - def _try_local_parse(self, command: str) -> Dict[str, Any]: + @staticmethod + def _try_local_parse(command: str) -> Optional[Dict[str, Any]]: """ 尝试本地解析常见命令 """ @@ -28,12 +28,13 @@ class CommandParser: # VLAN配置 if "vlan" in command and "创建" in command: parts = command.split() - vlan_id = next((p for p in parts if p.isdigit()), None) - if vlan_id: + vlan_id_str = next((p for p in parts if p.isdigit()), None) + if vlan_id_str: + vlan_id = int(vlan_id_str) # 转换为整数 return { "type": "vlan", - "vlan_id": int(vlan_id), - "name": f"VLAN{vlan_id}", + "vlan_id": vlan_id, # 使用整数 + "name": f"VLAN{vlan_id}", # 使用转换后的整数 "interfaces": [] } @@ -60,9 +61,9 @@ class CommandParser: config["description"] = description if "vlan" in command: - vlan_id = next((p for p in parts if p.isdigit()), None) - if vlan_id: - config["vlan"] = int(vlan_id) + vlan_id_str = next((p for p in parts if p.isdigit()), None) + if vlan_id_str: + config["vlan"] = int(vlan_id_str) # 转换为整数 return config diff --git a/src/backend/app/api/endpoints.py b/src/backend/app/api/endpoints.py index 7b974f6..95383ac 100644 --- a/src/backend/app/api/endpoints.py +++ b/src/backend/app/api/endpoints.py @@ -3,7 +3,7 @@ from typing import List, Dict from pydantic import BaseModel from ...config import settings from ..services.network_scanner import NetworkScanner -from ..api.network_config import SwitchConfigurator, SwitchConfig +from ..api.network_config import SwitchConfigurator router = APIRouter(prefix="/api", tags=["API"]) scanner = NetworkScanner() diff --git a/src/backend/app/api/network_config.py b/src/backend/app/api/network_config.py index fd9b629..532bd6f 100644 --- a/src/backend/app/api/network_config.py +++ b/src/backend/app/api/network_config.py @@ -1,6 +1,7 @@ import asyncio import logging import telnetlib3 +import time from datetime import datetime from pathlib import Path from typing import Dict, List, Optional, Union @@ -142,6 +143,14 @@ class SwitchConfigurator: except Exception as e: raise EnspConnectionException(f"eNSP连接失败: {str(e)}") + async def _clean_idle_connections(self): + """连接池清理机制""" + now = time.time() + for ip, (conn, last_used) in list(self._connection_pool.items()): + if now - last_used > 300: # 5分钟空闲超时 + conn.close() + del self._connection_pool[ip] + async def _send_ssh_commands(self, ip: str, commands: List[str]) -> str: """SSH协议执行""" async with self.semaphore: diff --git a/src/backend/app/experiments/network_experiment.py b/src/backend/app/experiments/network_experiment.py new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/app/experiments/performance_analysis.py b/src/backend/app/experiments/performance_analysis.py new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/app/services/ai_configurator.py b/src/backend/app/services/ai_configurator.py new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/app/services/ai_services.py b/src/backend/app/services/ai_services.py index e4d0712..32e0473 100644 --- a/src/backend/app/services/ai_services.py +++ b/src/backend/app/services/ai_services.py @@ -58,4 +58,7 @@ class AIService: return json.loads(config_str) raise SiliconFlowAPIException("Invalid JSON format returned from AI") except httpx.HTTPError as e: - raise SiliconFlowAPIException(str(e)) \ No newline at end of file + raise SiliconFlowAPIException( + detail=f"API请求失败: {str(e)}", + status_code=e.response.status_code if hasattr(e, "response") else 500 + ) \ No newline at end of file diff --git a/src/backend/app/services/network_optimizer.py b/src/backend/app/services/network_optimizer.py new file mode 100644 index 0000000..12dd902 --- /dev/null +++ b/src/backend/app/services/network_optimizer.py @@ -0,0 +1,36 @@ + +import networkx as nx +from scipy.optimize import minimize + + +class NetworkOptimizer: + def __init__(self, devices): + """基于图论的网络优化模型""" + self.graph = self.build_topology_graph(devices) + + def build_topology_graph(self, devices): + """构建网络拓扑图""" + G = nx.Graph() + for device in devices: + G.add_node(device['ip'], type=device['type']) + # 添加连接关系(示例) + G.add_edge('192.168.1.1', '192.168.1.2', bandwidth=1000) + return G + + def optimize_path(self, source, target): + """计算最优路径""" + return nx.shortest_path(self.graph, source, target) + + def bandwidth_optimization(self): + """带宽优化模型""" + + def objective(x): + # 最小化最大链路利用率 + return max(x) + + constraints = ( + {'type': 'eq', 'fun': lambda x: sum(x) - total_bandwidth} + ) + + result = minimize(objective, initial_guess, constraints=constraints) + return result.x \ No newline at end of file diff --git a/src/backend/app/utils/exceptions.py b/src/backend/app/utils/exceptions.py index ccbd2de..c82049c 100644 --- a/src/backend/app/utils/exceptions.py +++ b/src/backend/app/utils/exceptions.py @@ -1,5 +1,14 @@ from fastapi import HTTPException, status -from typing import Optional + +class SiliconFlowAPIException(Exception): + """硅基流动API异常""" + def __init__(self, detail: str, status_code: int = 500): + self.detail = detail + self.status_code = status_code + super().__init__(detail) + + def __str__(self): + return f"SiliconFlowAPI Error [{self.status_code}]: {self.detail}" class AICommandParseException(HTTPException): def __init__(self, detail: str): @@ -23,9 +32,9 @@ class ConfigBackupException(SwitchConfigException): """配置备份失败异常""" def __init__(self, ip: str): super().__init__( - detail=f"无法备份设备 {ip} 的配置", - recovery_guide="检查设备存储空间或权限" + detail=f"无法备份设备 {ip} 的配置" ) + self.recovery_guide = "检查设备存储空间或权限" # 在子类中存储恢复指南 class ConfigRollbackException(SwitchConfigException): """回滚失败异常""" @@ -34,3 +43,4 @@ class ConfigRollbackException(SwitchConfigException): detail=f"设备 {ip} 回滚失败(原始错误:{original_error})", status_code=status.HTTP_422_UNPROCESSABLE_ENTITY ) + self.recovery_guide = "尝试手动恢复配置或重启设备" \ No newline at end of file diff --git a/src/backend/app/utils/visualization/performance_dashboard.py b/src/backend/app/utils/visualization/performance_dashboard.py new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/app/utils/visualization/topology_3d.py b/src/backend/app/utils/visualization/topology_3d.py new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/batch/bulk_config.py b/src/backend/batch/bulk_config.py index ea38b67..7847b60 100644 --- a/src/backend/batch/bulk_config.py +++ b/src/backend/batch/bulk_config.py @@ -24,8 +24,8 @@ class BulkConfigurator: return "\n".join(r.stdout for r in results) finally: await self.pool.release_connection(ip, conn) - - def _generate_commands(self, config: BulkSwitchConfig) -> List[str]: + @staticmethod + def _generate_commands(config: BulkSwitchConfig) -> List[str]: """命令生成(纯业务逻辑)""" commands = [] if config.vlan_id: diff --git a/src/backend/batch/connection_pool.py b/src/backend/batch/connection_pool.py index c29ee31..f314fb2 100644 --- a/src/backend/batch/connection_pool.py +++ b/src/backend/batch/connection_pool.py @@ -1,9 +1,7 @@ import asyncio -import time import asyncssh from typing import Dict - class SwitchConnectionPool: """ 交换机连接池(支持自动重连和负载均衡) @@ -35,10 +33,15 @@ class SwitchConnectionPool: async def release_connection(self, ip: str, conn: asyncssh.SSHClientConnection): async with self._lock: - if conn.is_connected() and self._pools[ip].qsize() < self._max_conn: + if hasattr(conn, 'is_closed') and not conn.is_closed() and self._pools[ip].qsize() < self._max_conn: + await self._pools[ip].put(conn) + elif hasattr(conn, 'closed') and not conn.closed and self._pools[ip].qsize() < self._max_conn: await self._pools[ip].put(conn) else: - conn.close() + try: + conn.close() + except: + pass async def close_all(self): async with self._lock: diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index 33a2ee0..fc90248 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -11,4 +11,9 @@ typing-extensions>=4.0.0 aiofiles>=24.1.0 telnetlib3>=2.0.4 asyncssh>=2.14.0 -aiofiles>=24.1.0 \ No newline at end of file +aiofiles>=24.1.0 +networkx==3.1 +scipy==1.11.1 +stable-baselines3==2.0.0 +plotly==5.15.0 +pandas==2.0.3 \ No newline at end of file