Files
qhmes/yy-admin-master/YY.Admin/Infrastructure/Network/NetworkMonitor.cs

171 lines
4.9 KiB
C#

using Microsoft.Extensions.Configuration;
using Prism.Events;
using System.Net.Http;
using YY.Admin.Core.Events;
using YY.Admin.Core.Services;
using YY.Admin.Helper;
namespace YY.Admin.Infrastructure.Network;
public class NetworkMonitor : INetworkMonitor
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IConfiguration _configuration;
private readonly IEventAggregator _eventAggregator;
private readonly SemaphoreSlim _startLock = new(1, 1);
private readonly object _aggregateLock = new();
private Task? _loopTask;
private CancellationTokenSource? _cts;
private volatile bool _httpProbeOnline;
private volatile bool _stompTransportOnline;
private volatile bool _isOnline;
public NetworkMonitor(
IHttpClientFactory httpClientFactory,
IConfiguration configuration,
IEventAggregator eventAggregator)
{
_httpClientFactory = httpClientFactory;
_configuration = configuration;
_eventAggregator = eventAggregator;
}
public bool IsOnline => _isOnline;
public event Action<bool>? StatusChanged;
/// <inheritdoc />
public void SetStompTransportOnline(bool online)
{
_stompTransportOnline = online;
RecomputeAggregatedOnline();
}
public async Task StartAsync(CancellationToken cancellationToken = default)
{
await _startLock.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
if (_cts != null)
{
return;
}
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
_loopTask = Task.Run(() => MonitorLoopAsync(_cts.Token), _cts.Token);
}
finally
{
_startLock.Release();
}
}
private async Task MonitorLoopAsync(CancellationToken cancellationToken)
{
// 启动后立即探活一次,避免首屏 10 秒内 IsOnline 恒为 false
await RunHttpProbeAndRecomputeAsync(cancellationToken).ConfigureAwait(false);
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(10));
while (await timer.WaitForNextTickAsync(cancellationToken).ConfigureAwait(false))
{
await RunHttpProbeAndRecomputeAsync(cancellationToken).ConfigureAwait(false);
}
}
private async Task RunHttpProbeAndRecomputeAsync(CancellationToken cancellationToken)
{
var httpOk = await ProbeAsync(cancellationToken).ConfigureAwait(false);
_httpProbeOnline = httpOk;
RecomputeAggregatedOnline();
}
private void RecomputeAggregatedOnline()
{
var combined = ComputeCombinedOnline();
bool newValue;
lock (_aggregateLock)
{
if (combined == _isOnline)
{
return;
}
_isOnline = combined;
newValue = combined;
}
StatusChanged?.Invoke(newValue);
_eventAggregator.GetEvent<NetworkStatusChangedEvent>().Publish(new NetworkStatusChangedPayload
{
IsOnline = newValue,
ChangedAt = DateTime.UtcNow
});
}
private bool ComputeCombinedOnline()
{
if (IsDisconnectedByUser())
{
return false;
}
return _httpProbeOnline || _stompTransportOnline;
}
private async Task<bool> ProbeAsync(CancellationToken cancellationToken)
{
if (IsDisconnectedByUser())
{
return false;
}
var baseUrl = _configuration.GetValue<string>("JeecgIntegration:BaseUrl")?.TrimEnd('/');
if (string.IsNullOrWhiteSpace(baseUrl))
{
return false;
}
// 探活策略:优先调用免登录接口,失败后再降级到健康检查接口
var probeUrls = new[]
{
$"{baseUrl}/sys/user/scada/queryUser?current=1&pageSize=1",
$"{baseUrl}/sys/dict/scada/queryDictItem?pageNo=1&pageSize=1",
$"{baseUrl}/actuator/health"
};
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
timeoutCts.CancelAfter(TimeSpan.FromSeconds(3));
var client = _httpClientFactory.CreateClient("JeecgApi");
foreach (var url in probeUrls)
{
try
{
using var req = new HttpRequestMessage(HttpMethod.Get, url);
using var resp = await client.SendAsync(req, timeoutCts.Token).ConfigureAwait(false);
if (resp.IsSuccessStatusCode)
{
return true;
}
}
catch
{
// 当前探活地址失败后继续尝试下一个降级地址
}
}
return false;
}
private static bool IsDisconnectedByUser()
{
try
{
return ServerSettingsStore.Load().DisconnectConnection;
}
catch
{
return false;
}
}
}