feature: Ward Gateway Service (Local-First Clinical Path)

This commit is contained in:
voltsrage
2026-06-23 16:45:38 +08:00
parent d8e142fffe
commit 1bf8359097
100 changed files with 5474 additions and 4 deletions
@@ -0,0 +1,65 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
public sealed class GatewayHeartbeatService : BackgroundService
{
private readonly IServiceScopeFactory _scopes;
private readonly IHttpClientFactory _http;
private readonly CentralReachabilityService _reachability;
private readonly GatewayOptions _gateway;
private readonly IConfiguration _config;
private readonly ILogger<GatewayHeartbeatService> _logger;
public GatewayHeartbeatService(
IServiceScopeFactory scopes,
IHttpClientFactory http,
CentralReachabilityService reachability,
IOptions<GatewayOptions> gateway,
IConfiguration config,
ILogger<GatewayHeartbeatService> logger)
{
_scopes = scopes;
_http = http;
_reachability = reachability;
_gateway = gateway.Value;
_config = config;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken ct)
{
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(_gateway.HeartbeatIntervalSeconds));
while (await timer.WaitForNextTickAsync(ct))
await SendHeartbeatAsync(ct);
}
private async Task SendHeartbeatAsync(CancellationToken ct)
{
await using var scope = _scopes.CreateAsyncScope();
var db = scope.ServiceProvider.GetRequiredService<GatewayDbContext>();
var bufferDepth = await db.BufferedSyncItems.CountAsync(b => !b.Synced, ct);
var status = _reachability.IsCentralReachable ? "ONLINE" : "DEGRADED";
var req = new GatewayHeartbeatRequest(status, bufferDepth, DateTimeOffset.UtcNow);
try
{
var client = _http.CreateClient("central");
client.BaseAddress = new Uri(
scope.ServiceProvider.GetRequiredService<IOptions<CentralApiOptions>>().Value.BaseUrl);
client.DefaultRequestHeaders.Remove("X-Api-Key");
client.DefaultRequestHeaders.Remove("X-Gateway-Id");
client.DefaultRequestHeaders.Add("X-Api-Key", _config["ApiKey:Gateway"]);
client.DefaultRequestHeaders.Add("X-Gateway-Id", _gateway.GatewayId.ToString());
var resp = await client.PatchAsJsonAsync(
$"/api/v1/gateways/{_gateway.GatewayId}/heartbeat", req, ct);
if (!resp.IsSuccessStatusCode)
_logger.LogWarning("Heartbeat failed: {Status}", resp.StatusCode);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Heartbeat to central failed — gateway continues in DEGRADED mode");
}
}
}