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

- 移除所有残留的Shell模式代码
- 使用简洁的session.Run模式
- 命令间延迟从500ms增加到2秒,防止H3C速率限制
- 超时时间设置为30秒
Цей коміт міститься в:
Your Name
2026-04-26 03:11:07 +08:00
джерело db161fa3f2
коміт 4edc1b67fc
2 змінених файлів з 37 додано та 139 видалено
+2 -2
Переглянути файл
@@ -80,9 +80,9 @@ func DiscoverDevice(ip string, deviceType models.DeviceType, username, password
// 执行命令 - 允许部分命令失败,增加详细日志和延迟防止设备速率限制 // 执行命令 - 允许部分命令失败,增加详细日志和延迟防止设备速率限制
outputs := make([]string, 0, len(commands)) outputs := make([]string, 0, len(commands))
for i, cmd := range commands { for i, cmd := range commands {
// 每个命令之间等待500毫秒,防止设备速率限制导致返回空数据 // 每个命令之间等待2秒,防止H3C交换机速率限制导致返回空数据或执行失败
if i > 0 { 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) fmt.Printf("[PARSER] Executing command %d/%d: %s\n", i+1, len(commands), cmd)
+35 -137
Переглянути файл
@@ -5,34 +5,32 @@ import (
"fmt" "fmt"
"net" "net"
"os" "os"
"strings"
"time" "time"
"regexp"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
// Client SSH客户端 // Client SSH客户端
type Client struct { type Client struct {
client *ssh.Client client *ssh.Client
timeout time.Duration timeout time.Duration
host string host string
port int port int
username string username string
password string password string
keyFile string keyFile string
insecureCiphers bool insecureCiphers bool
} }
// Config SSH客户端配置 // Config SSH客户端配置
type Config struct { type Config struct {
Host string Host string
Port int Port int
Username string Username string
Password string Password string
KeyFile string KeyFile string
Timeout time.Duration Timeout time.Duration
InsecureCiphers bool // 启用不安全的加密算法(用于兼容老旧设备) InsecureCiphers bool // 启用不安全的加密算法(用于兼容老旧设备)
} }
// NewClient 创建新的SSH客户端 // NewClient 创建新的SSH客户端
@@ -41,24 +39,24 @@ func NewClient(config Config) *Client {
config.Port = 22 config.Port = 22
} }
if config.Timeout == 0 { if config.Timeout == 0 {
config.Timeout = 10 * time.Second config.Timeout = 30 * time.Second
} }
return &Client{ return &Client{
host: config.Host, host: config.Host,
port: config.Port, port: config.Port,
username: config.Username, username: config.Username,
password: config.Password, password: config.Password,
keyFile: config.KeyFile, keyFile: config.KeyFile,
timeout: config.Timeout, timeout: config.Timeout,
insecureCiphers: config.InsecureCiphers, insecureCiphers: config.InsecureCiphers,
} }
} }
// Connect 连接到SSH服务器 // Connect 连接到SSH服务器
func (c *Client) Connect() error { func (c *Client) Connect() error {
config := &ssh.ClientConfig{ config := &ssh.ClientConfig{
User: c.username, User: c.username,
Auth: []ssh.AuthMethod{}, Auth: []ssh.AuthMethod{},
HostKeyCallback: ssh.InsecureIgnoreHostKey(), HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: c.timeout, Timeout: c.timeout,
} }
@@ -117,129 +115,29 @@ func (c *Client) Close() error {
return nil return nil
} }
// ExecuteCommand 执行命令并返回输出 // ExecuteCommand 执行命令并返回输出(每个命令使用新会话)
func (c *Client) ExecuteCommand(command string) (string, error) { func (c *Client) ExecuteCommand(command string) (string, error) {
if c.client == nil { if c.client == nil {
return "", fmt.Errorf("not connected") return "", fmt.Errorf("not connected")
} }
// 重试机制,处理会话创建失败的情况 // 创建新会话
var session *ssh.Session session, err := c.client.NewSession()
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)
}
}
if err != nil { 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() 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 var stdoutBuf bytes.Buffer
session.Stdout = &stdoutBuf session.Stdout = &stdoutBuf
session.Stderr = &stdoutBuf session.Stderr = &stdoutBuf
if err := session.Shell(); err != nil { err = session.Run(command)
return "", fmt.Errorf("failed to start shell: %w", err) if err != nil {
return stdoutBuf.String(), fmt.Errorf("command '%s' failed: %w", command, err)
} }
// 先发送禁用分页命令(H3C/华为) return stdoutBuf.String(), nil
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)
}
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
} }
// ExecuteCommands 执行多个命令 // ExecuteCommands 执行多个命令