1
0

119 lines
4.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Flawless.Communication.Shared;
using Flawless.Server.Models;
using Microsoft.EntityFrameworkCore;
namespace Flawless.Server.Services;
public class WebhookService(
SettingFacade settings,
AppDbContext context,
IHttpClientFactory httpFactory,
ILogger<WebhookService> logger)
{
public async Task AddWebhookAsync(Repository repo, string targetUrl, WebhookEventType eventType, string? secret)
{
if (string.IsNullOrWhiteSpace(targetUrl) || !Uri.TryCreate(targetUrl, UriKind.Absolute, out _))
throw new ArgumentException("No valid target URL provided");
var webhook = new Webhook {
Repository = repo,
TargetUrl = targetUrl,
EventType = eventType,
Secret = secret,
IsActive = true
};
await context.Webhooks.AddAsync(webhook);
await context.SaveChangesAsync();
}
public async Task ToggleWebhookAsync(Repository repo, int webhookId, bool activated)
{
var hook = await context.Webhooks
.Include(x => x.Repository)
.FirstOrDefaultAsync(x => x.Id == webhookId);
if (hook == null || hook.Repository != repo) return;
if (hook.IsActive == activated) return;
hook.IsActive = activated;
context.Webhooks.Update(hook);
await context.SaveChangesAsync();
}
public async Task DeleteWebhookAsync(Repository repo, int webhookId)
{
var hook = await context.Webhooks
.Include(x => x.Repository)
.FirstOrDefaultAsync(x => x.Id == webhookId);
if (hook == null || hook.Repository != repo) return;
context.Webhooks.Remove(hook);
await context.SaveChangesAsync();
}
public async Task<IEnumerable<Webhook>> GetWebhooksAsync(Repository repo)
{
return await context.Webhooks
.Where(w => w.Repository == repo)
.ToListAsync();
}
public async Task TriggerWebhooksAsync(Repository repo, WebhookEventType eventType, object payload)
{
if (!settings.UseWebHook) return;
var hooks = await context.Webhooks
.Where(w => w.Repository == repo && w.EventType == eventType && w.IsActive)
.ToListAsync();
using var client = httpFactory.CreateClient();
client.Timeout = TimeSpan.FromSeconds(settings.WebhookTimeout);
foreach (var hook in hooks)
{
for (var retry = 0; retry < settings.WebhookMaxRetries; retry++)
{
try
{
var content = new StringContent(JsonSerializer.Serialize(payload),
Encoding.UTF8, "application/json");
if (hook.Secret != null)
{
var signature = HMACSHA256.HashData(
Encoding.UTF8.GetBytes(hook.Secret),
await content.ReadAsByteArrayAsync());
content.Headers.Add("X-Signature", $"sha256={Convert.ToHexString(signature)}");
}
var response = await client.PostAsync(hook.TargetUrl, content);
if (response.IsSuccessStatusCode) break;
logger.LogWarning($"Webhook {hook.Id} Failed{response.StatusCode}");
await Task.Delay(1000 * (int)Math.Pow(2, retry)); // 指数退避
}
catch (Exception ex)
{
logger.LogError(ex, $"Webhook {hook.Id} Failed for {retry + 1} times.");
if (retry == settings.WebhookMaxRetries - 1)
await DeactivateFailedWebhook(hook);
}
}
}
}
private async Task DeactivateFailedWebhook(Webhook hook)
{
hook.IsActive = false;
context.Webhooks.Update(hook);
await context.SaveChangesAsync();
}
}