// 连接诊断系统 -- SSL 错误码目录 + 用户可操作提示. // // 精妙之处(CLEVER): 企业用户背后往往有 TLS 拦截代理(Zscaler,Palo Alto 等), // 拿到 "UNABLE_TO_VERIFY_LEAF_SIGNATURE" 这种 OpenSSL 错误码根本不知道怎么办. // 把 19 个高频 SSL 错误码映射为可操作的诊断提示,一条消息省一轮技术支持. // // 升华改进(ELEVATED): 不只 SSL--扩展为通用的连接诊断, // 涵盖 DNS,超时,连接拒绝等所有网络层错误. // 仓储场景同样需要:PLC 连接超时,MQTT broker 不可达,OPC-UA 证书过期. // // 替代方案:<原方案只处理 SSL,其他连接错误返回 "Connection error"> package api import ( "strings" "git.flytoex.net/yuanwei/flyto-agent/core/internal/apierror" ) // ============================================================ // DiagnosticHinter - 诊断提示接口 // ============================================================ // DiagnosticHinter 为 API 错误提供用户可操作的诊断提示. // // 升华改进(ELEVATED): 接口化而非硬编码--不同部署环境可以注册不同的 Hinter. // 例如企业内网环境注册一个知道内部代理地址的 Hinter, // 云环境注册一个检查安全组/防火墙的 Hinter. type DiagnosticHinter interface { // Hint 返回用户可操作的诊断提示,空字符串表示没有建议. Hint(err *APIError) string } // ============================================================ // SSL 错误码目录(别名转发到 apierror 包) // ============================================================ // IsSSLErrorCode reports whether an error code or message fragment is SSL/TLS // related. The catalog and this predicate moved into the apierror package // (used there by classifyConnectionError, which DefaultClassifier depends on); // this alias keeps api.IsSSLErrorCode callable for existing consumers. // // IsSSLErrorCode 判断错误码或消息片段是否 SSL/TLS 相关. 错误码目录与本判定 // 已移入 apierror 包 (在那里被 classifyConnectionError 使用, 而后者是 // DefaultClassifier 的依赖); 此别名保留 api.IsSSLErrorCode 供既有消费者调用. var IsSSLErrorCode = apierror.IsSSLErrorCode // ============================================================ // sslHints - SSL 错误码到诊断提示的映射 // ============================================================ // sslHints 将特定 SSL 错误码映射为用户可操作的提示. var sslHints = map[string]string{ "UNABLE_TO_VERIFY_LEAF_SIGNATURE": "SSL 证书验证失败。如果你在企业代理或 TLS 拦截防火墙后面,请设置 SSL_CERT_FILE 或联系 IT 部门", "UNABLE_TO_GET_ISSUER_CERT": "SSL 证书验证失败。如果你在企业代理或 TLS 拦截防火墙后面,请设置 SSL_CERT_FILE 或联系 IT 部门", "UNABLE_TO_GET_ISSUER_CERT_LOCALLY": "SSL 证书验证失败。如果你在企业代理或 TLS 拦截防火墙后面,请设置 SSL_CERT_FILE 或联系 IT 部门", "CERT_HAS_EXPIRED": "SSL 证书已过期", "CERT_REVOKED": "SSL 证书已被吊销", "DEPTH_ZERO_SELF_SIGNED_CERT": "检测到自签名证书。如果你在企业代理后面,请设置 SSL_CERT_FILE 或联系 IT 部门", "SELF_SIGNED_CERT_IN_CHAIN": "检测到自签名证书。如果你在企业代理后面,请设置 SSL_CERT_FILE 或联系 IT 部门", "ERR_TLS_CERT_ALTNAME_INVALID": "SSL 证书主机名不匹配", "HOSTNAME_MISMATCH": "SSL 证书主机名不匹配", "CERT_NOT_YET_VALID": "SSL 证书尚未生效(检查系统时钟)", "ERR_TLS_HANDSHAKE_TIMEOUT": "TLS 握手超时。检查网络连接和代理设置", } // ============================================================ // DefaultHinter - 默认诊断提示器 // ============================================================ // DefaultHinter 是默认的诊断提示器,覆盖 SSL 和常见网络错误. type DefaultHinter struct{} // Hint 返回诊断提示. func (h *DefaultHinter) Hint(err *APIError) string { if err == nil { return "" } switch err.ErrCategory { case ErrSSL: return h.sslHint(err) case ErrConnection: return h.connectionHint(err) case ErrTimeout: return "请求超时。检查网络连接和代理设置" default: return "" } } // sslHint 为 SSL 错误提供诊断提示. func (h *DefaultHinter) sslHint(err *APIError) string { if err.Cause == nil && err.Msg == "" { return "" } // 尝试从错误消息中匹配已知 SSL 错误码 msg := err.Msg if err.Cause != nil { msg = err.Cause.Error() } for code, hint := range sslHints { if strings.Contains(msg, code) { return hint } } // Go crypto/tls 错误 if strings.Contains(msg, "x509:") || strings.Contains(msg, "tls:") { return "SSL/TLS 证书错误。如果你在企业代理后面,请设置 SSL_CERT_FILE 环境变量指向 CA 证书包" } return "SSL/TLS 错误。检查证书配置和代理设置" } // connectionHint 为连接错误提供诊断提示. func (h *DefaultHinter) connectionHint(err *APIError) string { msg := err.Msg if err.Cause != nil { msg = err.Cause.Error() } lower := strings.ToLower(msg) if strings.Contains(lower, "no such host") || strings.Contains(lower, "dns") { return "DNS 解析失败。检查 API 地址和 DNS 配置" } if strings.Contains(lower, "connection refused") { return "连接被拒绝。检查 API 地址和端口是否正确" } if strings.Contains(lower, "connection reset") { return "连接被重置。可能是代理或防火墙阻断了连接" } if strings.Contains(lower, "no route to host") { return "无法到达主机。检查网络连接" } return "网络连接错误。检查网络连接和代理设置" } // ============================================================ // CompositeHinter - 叠加诊断提示器 // ============================================================ // CompositeHinter 组合多个 Hinter,第一个返回非空提示的胜出. type CompositeHinter struct { hinters []DiagnosticHinter } // NewCompositeHinter 创建组合提示器. func NewCompositeHinter(hinters ...DiagnosticHinter) *CompositeHinter { return &CompositeHinter{hinters: hinters} } // Add 添加诊断提示器. func (c *CompositeHinter) Add(hinter DiagnosticHinter) { c.hinters = append(c.hinters, hinter) } // Hint 按顺序调用提示器. func (c *CompositeHinter) Hint(err *APIError) string { for _, hinter := range c.hinters { if hint := hinter.Hint(err); hint != "" { return hint } } return "" }