This commit is contained in:
3 2025-06-18 16:18:42 +08:00
parent b3e6aa7d5c
commit 60359b54ee
5 changed files with 40 additions and 20 deletions

View File

@ -19,7 +19,8 @@ class CommandParser:
# 本地无法解析则调用AI服务 # 本地无法解析则调用AI服务
return await self.ai_service.parse_command(command) 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) -> dict[str, str | list[Any]] | dict[str, str] | None:
""" """
尝试本地解析常见命令 尝试本地解析常见命令
""" """
@ -32,7 +33,7 @@ class CommandParser:
if vlan_id: if vlan_id:
return { return {
"type": "vlan", "type": "vlan",
"vlan_id": int(vlan_id), "vlan_id": vlan_id,
"name": f"VLAN{vlan_id}", "name": f"VLAN{vlan_id}",
"interfaces": [] "interfaces": []
} }
@ -62,7 +63,7 @@ class CommandParser:
if "vlan" in command: if "vlan" in command:
vlan_id = next((p for p in parts if p.isdigit()), None) vlan_id = next((p for p in parts if p.isdigit()), None)
if vlan_id: if vlan_id:
config["vlan"] = int(vlan_id) config["vlan"] = vlan_id
return config return config

View File

@ -20,7 +20,7 @@ async def batch_apply_config(request: BatchConfigRequest):
for ip in request.switch_ips: for ip in request.switch_ips:
try: try:
configurator = SwitchConfigurator() configurator = SwitchConfigurator()
results[ip] = await configurator.apply_config(ip, request.config) results[ip] = await configurator.safe_apply(ip, request.config)
except Exception as e: except Exception as e:
results[ip] = str(e) results[ip] = str(e)
return {"results": results} return {"results": results}
@ -80,7 +80,7 @@ async def apply_config(request: ConfigRequest):
password=settings.SWITCH_PASSWORD, password=settings.SWITCH_PASSWORD,
timeout=settings.SWITCH_TIMEOUT timeout=settings.SWITCH_TIMEOUT
) )
result = await configurator.apply_config(request.switch_ip, request.config) result = await configurator.safe_apply(request.switch_ip, request.config)
return {"success": True, "result": result} return {"success": True, "result": result}
except Exception as e: except Exception as e:
raise HTTPException( raise HTTPException(

View File

@ -64,7 +64,7 @@ class SwitchConfigurator:
self.ensp_delay = ensp_command_delay self.ensp_delay = ensp_command_delay
self.ssh_options = ssh_options self.ssh_options = ssh_options
async def _apply_config(self, ip: str, config: Union[Dict, SwitchConfig]) -> str: async def apply_config(self, ip: str, config: Union[Dict, SwitchConfig]) -> str:
"""实际配置逻辑""" """实际配置逻辑"""
if isinstance(config, dict): if isinstance(config, dict):
config = SwitchConfig(**config) config = SwitchConfig(**config)
@ -127,6 +127,7 @@ class SwitchConfigurator:
# 关闭连接 # 关闭连接
writer.close() writer.close()
# noinspection PyBroadException
try: try:
await writer.wait_closed() await writer.wait_closed()
except: except:
@ -253,7 +254,7 @@ class SwitchConfigurator:
"""安全配置应用(自动回滚)""" """安全配置应用(自动回滚)"""
backup_path = await self._backup_config(ip) backup_path = await self._backup_config(ip)
try: try:
result = await self._apply_config(ip, config) result = await self.apply_config(ip, config)
if not await self._validate_config(ip, config): if not await self._validate_config(ip, config):
raise SwitchConfigException("配置验证失败") raise SwitchConfigException("配置验证失败")
return { return {

View File

@ -1,5 +1,4 @@
from fastapi import HTTPException, status from fastapi import HTTPException, status
from typing import Optional
class AICommandParseException(HTTPException): class AICommandParseException(HTTPException):
def __init__(self, detail: str): def __init__(self, detail: str):
@ -23,9 +22,10 @@ class ConfigBackupException(SwitchConfigException):
"""配置备份失败异常""" """配置备份失败异常"""
def __init__(self, ip: str): def __init__(self, ip: str):
super().__init__( super().__init__(
detail=f"无法备份设备 {ip} 的配置", detail=f"无法备份设备 {ip} 的配置"
recovery_guide="检查设备存储空间或权限"
) )
# 将恢复指南作为实例属性
self.recovery_guide = "检查设备存储空间或权限"
class ConfigRollbackException(SwitchConfigException): class ConfigRollbackException(SwitchConfigException):
"""回滚失败异常""" """回滚失败异常"""
@ -34,3 +34,10 @@ class ConfigRollbackException(SwitchConfigException):
detail=f"设备 {ip} 回滚失败(原始错误:{original_error}", detail=f"设备 {ip} 回滚失败(原始错误:{original_error}",
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY status_code=status.HTTP_422_UNPROCESSABLE_ENTITY
) )
class SiliconFlowAPIException(HTTPException):
def __init__(self, detail: str, status_code: int = 400):
super().__init__(
status_code=status_code,
detail=f"SiliconFlow API error: {detail}"
)

View File

@ -1,33 +1,44 @@
import logging import logging
from loguru import logger from loguru import logger
import sys import sys
class InterceptHandler(logging.Handler): class InterceptHandler(logging.Handler):
def emit(self, record): def emit(self, record):
# Get corresponding Loguru level if it exists # 获取对应的Loguru日志级别
try: try:
level = logger.level(record.levelname).name level = logger.level(record.levelname).name
except ValueError: except ValueError:
level = record.levelno level = record.levelno
# Find caller from where originated the logged message # 查找日志来源
frame, depth = logging.currentframe(), 2 frame, depth = logging.currentframe(), 2
while frame.f_code.co_filename == logging.__file__: while frame and frame.f_code.co_filename == logging.__file__:
frame = frame.f_back frame = frame.f_back
depth += 1 depth += 1
# 使用Loguru记录日志
logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage()) logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
def setup_logging(): def setup_logging():
# 拦截标准logging # 拦截标准logging
logging.basicConfig(handlers=[InterceptHandler()], level=0) logging.basicConfig(handlers=[InterceptHandler()], level=logging.NOTSET)
# 配置loguru # 移除所有现有处理器
logger.configure( logger.remove()
handlers=[
{"sink": sys.stdout, # 直接添加处理器避免configure方法
"format": "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>"} logger.add(
] sys.stdout,
format=(
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
"<level>{level: <8}</level> | "
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
"<level>{message}</level>"
),
level="DEBUG",
enqueue=True
) )