1
0

114 lines
3.9 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(Guid repoId, 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 {
RepositoryId = repoId,
TargetUrl = targetUrl,
EventType = eventType,
Secret = secret,
IsActive = true
};
await context.Webhooks.AddAsync(webhook);
await context.SaveChangesAsync();
}
public async Task ToggleWebhookAsync(Guid repoId, int webhookId, bool activated)
{
var hook = await context.Webhooks.FindAsync(webhookId);
if (hook == null || hook.RepositoryId != repoId) return;
if (hook.IsActive == activated) return;
hook.IsActive = activated;
context.Webhooks.Update(hook);
await context.SaveChangesAsync();
}
public async Task DeleteWebhookAsync(Guid repoId, int webhookId)
{
var hook = await context.Webhooks.FindAsync(webhookId);
if (hook == null || hook.RepositoryId != repoId) return;
context.Webhooks.Remove(hook);
await context.SaveChangesAsync();
}
public async Task<IEnumerable<Webhook>> GetWebhooksAsync(Guid repoId)
{
return await context.Webhooks
.Where(w => w.RepositoryId == repoId)
.ToListAsync();
}
public async Task TriggerWebhooksAsync(Guid repoId, WebhookEventType eventType, object payload)
{
if (!settings.UseWebHook) return;
var hooks = await context.Webhooks
.Where(w => w.RepositoryId == repoId && 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();
}
}