SPMS_API/SPMS.Infrastructure/Webhook/WebhookService.cs
2026-02-11 10:10:11 +09:00

142 lines
4.8 KiB
C#

using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using SPMS.Application.DTOs.Webhook;
using SPMS.Application.Interfaces;
using SPMS.Domain.Entities;
using SPMS.Domain.Enums;
using SPMS.Domain.Interfaces;
namespace SPMS.Infrastructure.Webhook;
public class WebhookService : IWebhookService
{
private readonly IServiceProvider _serviceProvider;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger<WebhookService> _logger;
private const int MaxRetries = 3;
private const int RetryDelaySeconds = 30;
private const int TimeoutSeconds = 10;
public WebhookService(
IServiceProvider serviceProvider,
IHttpClientFactory httpClientFactory,
ILogger<WebhookService> logger)
{
_serviceProvider = serviceProvider;
_httpClientFactory = httpClientFactory;
_logger = logger;
}
public Task SendAsync(long serviceId, WebhookEvent eventType, object data)
{
_ = Task.Run(() => SendWithRetryAsync(serviceId, eventType, data));
return Task.CompletedTask;
}
private async Task SendWithRetryAsync(long serviceId, WebhookEvent eventType, object data)
{
using var scope = _serviceProvider.CreateScope();
var serviceRepo = scope.ServiceProvider.GetRequiredService<IServiceRepository>();
var webhookLogRepo = scope.ServiceProvider.GetRequiredService<IWebhookLogRepository>();
var unitOfWork = scope.ServiceProvider.GetRequiredService<IUnitOfWork>();
var service = await serviceRepo.GetByIdAsync(serviceId);
if (service == null || string.IsNullOrEmpty(service.WebhookUrl))
return;
var eventName = EventTypeToString(eventType);
if (!IsEventSubscribed(service, eventName))
return;
var payload = new WebhookPayloadDto
{
EventType = eventName,
Timestamp = DateTime.UtcNow,
ServiceCode = service.ServiceCode,
Data = data
};
var jsonPayload = JsonSerializer.Serialize(payload);
var client = _httpClientFactory.CreateClient("Webhook");
client.Timeout = TimeSpan.FromSeconds(TimeoutSeconds);
int? responseCode = null;
string? responseBody = null;
var success = false;
for (var attempt = 1; attempt <= MaxRetries; attempt++)
{
try
{
var content = new StringContent(jsonPayload, System.Text.Encoding.UTF8, "application/json");
var response = await client.PostAsync(service.WebhookUrl, content);
responseCode = (int)response.StatusCode;
responseBody = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
success = true;
_logger.LogInformation("Webhook sent successfully to {Url} (attempt {Attempt})", service.WebhookUrl, attempt);
break;
}
_logger.LogWarning("Webhook failed with status {StatusCode} (attempt {Attempt}/{MaxRetries})",
responseCode, attempt, MaxRetries);
}
catch (Exception ex)
{
responseBody = ex.Message;
_logger.LogWarning(ex, "Webhook request failed (attempt {Attempt}/{MaxRetries})", attempt, MaxRetries);
}
if (attempt < MaxRetries)
await Task.Delay(TimeSpan.FromSeconds(RetryDelaySeconds));
}
var log = new WebhookLog
{
ServiceId = serviceId,
WebhookUrl = service.WebhookUrl,
EventType = eventType,
Payload = jsonPayload,
Status = success ? WebhookStatus.Success : WebhookStatus.Failed,
ResponseCode = responseCode,
ResponseBody = responseBody?.Length > 2000 ? responseBody[..2000] : responseBody,
SentAt = DateTime.UtcNow
};
await webhookLogRepo.AddAsync(log);
await unitOfWork.SaveChangesAsync();
}
private static bool IsEventSubscribed(Service service, string eventName)
{
if (string.IsNullOrEmpty(service.WebhookEvents))
return false;
try
{
var events = JsonSerializer.Deserialize<List<string>>(service.WebhookEvents);
return events?.Contains(eventName) == true;
}
catch
{
return false;
}
}
private static string EventTypeToString(WebhookEvent eventType)
{
return eventType switch
{
WebhookEvent.PushSent => "push_sent",
WebhookEvent.PushFailed => "push_failed",
WebhookEvent.PushClicked => "push_clicked",
_ => eventType.ToString().ToLowerInvariant()
};
}
}