package sftp import ( "errors" "fmt" "strings" ) // ConnectionError SFTP 连接错误(区分阶段) type ConnectionError struct { Op string // dial / handshake / auth / sftp_init Err error } func (e *ConnectionError) Error() string { return fmt.Sprintf("SFTP 连接失败 [%s]: %v", e.Op, e.Err) } func (e *ConnectionError) Unwrap() error { return e.Err } // ToUserMessage 将底层错误转换为用户友好的中文错误(返回 error 类型) func ToUserMessage(err error) error { if err == nil { return nil } var connErr *ConnectionError if errors.As(err, &connErr) { switch connErr.Op { case "dial": return fmt.Errorf("无法连接到服务器 (%v)。请检查地址和端口是否正确", connErr.Err) case "handshake": return fmt.Errorf("SSH 握手失败,请检查服务是否正常运行") case "auth": return fmt.Errorf("认证失败,请检查用户名和密码/密钥是否正确") case "sftp_init": return fmt.Errorf("SFTP 子系统初始化失败,请确认远程服务器支持 SFTP") } } msg := err.Error() switch { case strings.Contains(msg, "unable authenticate"), strings.Contains(msg, "no auth method"): return fmt.Errorf("认证失败:用户名或密码/密钥不正确") case strings.Contains(msg, "connection refused"): return fmt.Errorf("连接被拒绝:目标机器可能未开启 SSH 服务") case strings.Contains(msg, "timeout"), strings.Contains(msg, "i/o timeout"): return fmt.Errorf("连接超时:请检查网络连通性或防火墙设置") case strings.Contains(msg, "host key mismatch"): return fmt.Errorf("主机密钥不匹配:可能存在中间人攻击风险") case strings.Contains(msg, "permission denied"): return fmt.Errorf("权限不足:当前用户无权访问该资源") case strings.Contains(msg, "no such file"), strings.Contains(msg, "not found"): return fmt.Errorf("文件或目录不存在") default: return err } } // isNetworkError 判断是否为网络层错误(需要重连) func isNetworkError(err error) bool { if err == nil { return false } msg := strings.ToLower(err.Error()) return strings.Contains(msg, "broken pipe") || strings.Contains(msg, "connection reset") || strings.Contains(msg, "timeout") }