84 lines
3.3 KiB
C#
84 lines
3.3 KiB
C#
using System.Security.Authentication;
|
||
using System.Text;
|
||
|
||
namespace Common.Notify
|
||
{
|
||
/// <summary>
|
||
/// 通知推送异常诊断工具
|
||
/// HttpClient 网络异常(尤其 SSL/TLS 握手失败)的真实根因藏在 InnerException 链里,
|
||
/// 只记录外层 ex.Message 会得到"see inner exception"这类无用信息。
|
||
/// 本工具展开整条异常链并针对常见网络故障给出可读排查提示,供三个 Notifier 复用。
|
||
/// </summary>
|
||
public static class NotifyError
|
||
{
|
||
/// <summary>
|
||
/// 将异常展开为"外层 -> 内层 -> ..."的可读描述,并附加常见网络故障排查提示
|
||
/// </summary>
|
||
public static string Describe(Exception? ex)
|
||
{
|
||
if (ex == null) return "未知错误";
|
||
|
||
var sb = new StringBuilder();
|
||
var current = ex;
|
||
int depth = 0;
|
||
// 最多展开 5 层,防止极端嵌套导致信息过长
|
||
while (current != null && depth < 5)
|
||
{
|
||
if (depth > 0) sb.Append(" -> ");
|
||
sb.Append(current.Message);
|
||
current = current.InnerException;
|
||
depth++;
|
||
}
|
||
|
||
if (IsTimeout(ex))
|
||
{
|
||
sb.Append("|排查:请求超时,请确认目标地址可达、Webhook 域名解析正常、无防火墙拦截出站 443");
|
||
}
|
||
else if (IsSslFailure(ex))
|
||
{
|
||
sb.Append("|排查:SSL/TLS 握手失败,常见原因为 " +
|
||
"①服务器无法访问外网(工控内网请改用\"跨网中转(Outbox)\"推送模式,由跳板机 AlertBridge 实际发送) " +
|
||
"②企业代理/防火墙做 SSL 拦截且其根证书未被本机信任 " +
|
||
"③系统时间不准导致证书校验失败 " +
|
||
"④Webhook 地址主机名或端口有误(非标准 HTTPS 端口)");
|
||
}
|
||
|
||
return sb.ToString();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否为超时(TaskCanceledException 且非用户主动取消,或 TimeoutException)
|
||
/// </summary>
|
||
private static bool IsTimeout(Exception ex)
|
||
{
|
||
for (var c = ex; c != null; c = c.InnerException)
|
||
{
|
||
if (c is TimeoutException) return true;
|
||
// HttpClient 超时表现为 TaskCanceledException,内部通常带 TimeoutException
|
||
if (c is TaskCanceledException && c.InnerException is TimeoutException) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否为 SSL/TLS 握手或证书校验失败
|
||
/// </summary>
|
||
private static bool IsSslFailure(Exception ex)
|
||
{
|
||
for (var c = ex; c != null; c = c.InnerException)
|
||
{
|
||
if (c is AuthenticationException) return true;
|
||
var msg = c.Message;
|
||
if (!string.IsNullOrEmpty(msg) &&
|
||
(msg.Contains("SSL", StringComparison.OrdinalIgnoreCase) ||
|
||
msg.Contains("TLS", StringComparison.OrdinalIgnoreCase) ||
|
||
msg.Contains("certificate", StringComparison.OrdinalIgnoreCase)))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
}
|
||
}
|