ソースを参照

Fix: 清理SSH客户端代码并增加命令延迟到2秒

- 移除所有残留的Shell模式代码
- 使用简洁的session.Run模式
- 命令间延迟从500ms增加到2秒,防止H3C速率限制
- 超时时间设置为30秒
Your Name 1 ヶ月 前
コミット
4edc1b67fc
2 ファイル変更37 行追加139 行削除
  1. 2 2
      internal/device/parser.go
  2. 35 137
      internal/ssh/client.go

+ 2 - 2
internal/device/parser.go

@@ -80,9 +80,9 @@ func DiscoverDevice(ip string, deviceType models.DeviceType, username, password
 	// 执行命令 - 允许部分命令失败,增加详细日志和延迟防止设备速率限制
 	outputs := make([]string, 0, len(commands))
 	for i, cmd := range commands {
-		// 每个命令之间等待500毫秒,防止设备速率限制导致返回空数据
+		// 每个命令之间等待2秒,防止H3C交换机速率限制导致返回空数据或执行失败
 		if i > 0 {
-			time.Sleep(500 * time.Millisecond)
+			time.Sleep(2 * time.Second)
 		}
 		
 		fmt.Printf("[PARSER] Executing command %d/%d: %s\n", i+1, len(commands), cmd)

+ 35 - 137
internal/ssh/client.go

@@ -5,34 +5,32 @@ import (
 	"fmt"
 	"net"
 	"os"
-	"strings"
 	"time"
-	"regexp"
 
 	"golang.org/x/crypto/ssh"
 )
 
 // Client SSH客户端
 type Client struct {
-	client           *ssh.Client
-	timeout          time.Duration
-	host             string
-	port             int
-	username         string
-	password         string
-	keyFile          string
-	insecureCiphers  bool
+	client          *ssh.Client
+	timeout         time.Duration
+	host            string
+	port            int
+	username        string
+	password        string
+	keyFile         string
+	insecureCiphers bool
 }
 
 // Config SSH客户端配置
 type Config struct {
-	Host              string
-	Port              int
-	Username          string
-	Password          string
-	KeyFile           string
-	Timeout           time.Duration
-	InsecureCiphers   bool // 启用不安全的加密算法(用于兼容老旧设备)
+	Host            string
+	Port            int
+	Username        string
+	Password        string
+	KeyFile         string
+	Timeout         time.Duration
+	InsecureCiphers bool // 启用不安全的加密算法(用于兼容老旧设备)
 }
 
 // NewClient 创建新的SSH客户端
@@ -41,24 +39,24 @@ func NewClient(config Config) *Client {
 		config.Port = 22
 	}
 	if config.Timeout == 0 {
-		config.Timeout = 10 * time.Second
+		config.Timeout = 30 * time.Second
 	}
 	return &Client{
-		host:             config.Host,
-		port:             config.Port,
-		username:         config.Username,
-		password:         config.Password,
-		keyFile:          config.KeyFile,
-		timeout:          config.Timeout,
-		insecureCiphers:  config.InsecureCiphers,
+		host:            config.Host,
+		port:            config.Port,
+		username:        config.Username,
+		password:        config.Password,
+		keyFile:         config.KeyFile,
+		timeout:         config.Timeout,
+		insecureCiphers: config.InsecureCiphers,
 	}
 }
 
 // Connect 连接到SSH服务器
 func (c *Client) Connect() error {
 	config := &ssh.ClientConfig{
-		User: c.username,
-		Auth: []ssh.AuthMethod{},
+		User:            c.username,
+		Auth:            []ssh.AuthMethod{},
 		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
 		Timeout:         c.timeout,
 	}
@@ -117,129 +115,29 @@ func (c *Client) Close() error {
 	return nil
 }
 
-// ExecuteCommand 执行命令并返回输出
+// ExecuteCommand 执行命令并返回输出(每个命令使用新会话)
 func (c *Client) ExecuteCommand(command string) (string, error) {
 	if c.client == nil {
 		return "", fmt.Errorf("not connected")
 	}
 
-	// 重试机制,处理会话创建失败的情况
-	var session *ssh.Session
-	var err error
-	maxRetries := 3
-	
-	for i := 0; i < maxRetries; i++ {
-		session, err = c.client.NewSession()
-		if err == nil {
-			break
-		}
-		
-		// 如果是会话拒绝错误,等待后重试
-		if i < maxRetries-1 {
-			time.Sleep(time.Duration(i+1) * 500 * time.Millisecond)
-		}
-	}
-	
+	// 创建新会话
+	session, err := c.client.NewSession()
 	if err != nil {
-		return "", fmt.Errorf("failed to create session after %d retries: %w", maxRetries, err)
+		return "", fmt.Errorf("failed to create session: %w", err)
 	}
 	defer session.Close()
 
-	// H3C/华为设备需要先禁用分页
-	// 使用 Shell 模式而不是 Run 模式
-	modes := ssh.TerminalModes{
-		ssh.ECHO:          0,     // 禁用回显
-		ssh.TTY_OP_ISPEED: 14400, // 输入速度
-		ssh.TTY_OP_OSPEED: 14400, // 输出速度
-	}
-	
-	if err := session.RequestPty("dumb", 200, 1000, modes); err != nil {
-		return "", fmt.Errorf("failed to request pty: %w", err)
-	}
-	
-	// 启动 shell
-	shell, err := session.StdinPipe()
-	if err != nil {
-		return "", fmt.Errorf("failed to get stdin pipe: %w", err)
-	}
-	
 	var stdoutBuf bytes.Buffer
 	session.Stdout = &stdoutBuf
 	session.Stderr = &stdoutBuf
-	
-	if err := session.Shell(); err != nil {
-		return "", fmt.Errorf("failed to start shell: %w", err)
-	}
-	
-	// 先发送禁用分页命令(H3C/华为)
-	if _, err := shell.Write([]byte("screen-length disable\n")); err != nil {
-		return "", fmt.Errorf("failed to send screen-length disable: %w", err)
-	}
-	time.Sleep(500 * time.Millisecond) // 等待命令执行
-	
-	// 发送实际命令
-	if _, err := shell.Write([]byte(command + "\n")); err != nil {
-		return "", fmt.Errorf("failed to send command: %w", err)
-	}
-	
-	// 等待命令执行完成
-	time.Sleep(2 * time.Second)
-	if _, err := shell.Write([]byte("exit\n")); err != nil {
-		return "", fmt.Errorf("failed to send exit: %w", err)
-	}
-	
-	// 等待会话结束
-	session.Wait()
-	
-	output := stdoutBuf.String()
-	
-	// 调试:输出原始输出长度和前100个字符
-	fmt.Printf("[SSH DEBUG] Raw output length: %d, first 100 chars: %q\n", len(output), output[:min(len(output), 100)])
-	
-	// 清理输出:移除命令回显和分页提示
-	lines := strings.Split(output, "\n")
-	var cleanLines []string
-	skipNext := false
-	for _, line := range lines {
-		trimmedLine := strings.TrimSpace(line)
-		
-		// 跳过空行
-		if trimmedLine == "" {
-			continue
-		}
-		
-		// 跳过分页提示
-		if strings.Contains(trimmedLine, "---- More ----") {
-			continue
-		}
-		
-		// 跳过命令本身的回显(精确匹配)
-		if trimmedLine == strings.TrimSpace(command) || trimmedLine == "screen-length disable" {
-			skipNext = true
-			fmt.Printf("[SSH DEBUG] Skipping command echo: %s\n", trimmedLine)
-			continue
-		}
-		
-		// 跳过提示符行(如 <hostname> 或 [hostname])
-		if regexp.MustCompile(`^[<\[]\S+[>\]]$`).MatchString(trimmedLine) {
-			fmt.Printf("[SSH DEBUG] Skipping prompt: %s\n", trimmedLine)
-			continue
-		}
-		
-		// 如果是 "screen-length disable" 后的第一行(通常是提示符),跳过
-		if skipNext {
-			skipNext = false
-			fmt.Printf("[SSH DEBUG] Skipping line after command: %s\n", trimmedLine)
-			continue
-		}
-		
-		cleanLines = append(cleanLines, trimmedLine)
+
+	err = session.Run(command)
+	if err != nil {
+		return stdoutBuf.String(), fmt.Errorf("command '%s' failed: %w", command, err)
 	}
-	
-	cleanOutput := strings.Join(cleanLines, "\n")
-	fmt.Printf("[SSH DEBUG] Clean output length: %d, first 100 chars: %q\n", len(cleanOutput), cleanOutput[:min(len(cleanOutput), 100)])
-	
-	return cleanOutput, nil
+
+	return stdoutBuf.String(), nil
 }
 
 // ExecuteCommands 执行多个命令