Clinical Sync Batch Engine: first commit

This commit is contained in:
voltsrage
2026-06-23 03:02:30 +08:00
parent 90b8baa2a1
commit c9994b1ba2
60 changed files with 3547 additions and 31 deletions
@@ -0,0 +1,77 @@
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Options;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
public sealed class ClinicalSyncBatchConsumer : BackgroundService
{
private readonly IServiceScopeFactory _scopes;
private readonly RabbitMqOptions _rabbitOpts;
private readonly ILogger<ClinicalSyncBatchConsumer> _logger;
public ClinicalSyncBatchConsumer(
IServiceScopeFactory scopes,
IOptions<RabbitMqOptions> rabbitOpts,
ILogger<ClinicalSyncBatchConsumer> logger)
{
_scopes = scopes;
_rabbitOpts = rabbitOpts.Value;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
var factory = new ConnectionFactory
{
HostName = _rabbitOpts.Host,
Port = _rabbitOpts.Port,
UserName = _rabbitOpts.Username,
Password = _rabbitOpts.Password,
DispatchConsumersAsync = true
};
using var connection = factory.CreateConnection("clinical-sync-consumer");
using var channel = connection.CreateModel();
channel.BasicQos(0, prefetchCount: 5, global: false);
var consumer = new AsyncEventingBasicConsumer(channel);
consumer.Received += async (_, ea) =>
{
try
{
await HandleMessageAsync(channel, ea, stoppingToken);
}
catch (JsonException ex)
{
_logger.LogError(ex, "Invalid sync batch message — NACK no requeue");
channel.BasicNack(ea.DeliveryTag, false, requeue: false);
}
catch (Exception ex)
{
_logger.LogError(ex, "Sync batch consumer failed — NACK requeue");
channel.BasicNack(ea.DeliveryTag, false, requeue: true);
}
};
channel.BasicConsume(RabbitMqTopologyProvisioner.SyncBatchQueue, autoAck: false, consumer);
_logger.LogInformation("ClinicalSyncBatchConsumer consuming {Queue}", RabbitMqTopologyProvisioner.SyncBatchQueue);
await Task.Delay(Timeout.Infinite, stoppingToken);
}
private async Task HandleMessageAsync(IModel channel, BasicDeliverEventArgs ea, CancellationToken ct)
{
var payload = Encoding.UTF8.GetString(ea.Body.Span);
var doc = JsonDocument.Parse(payload);
var batchId = Guid.Parse(doc.RootElement.GetProperty("batchId").GetString()!);
await using var scope = _scopes.CreateAsyncScope();
var processor = scope.ServiceProvider.GetRequiredService<ClinicalSyncBatchProcessor>();
await processor.ProcessBatchAsync(batchId, ct);
channel.BasicAck(ea.DeliveryTag, false);
}
}