58 lines
1.7 KiB
C#
58 lines
1.7 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
|
|
namespace TaxBaik.Web.Services;
|
|
|
|
internal static class TelegramAlertGate
|
|
{
|
|
private sealed record GateEntry(DateTimeOffset WindowStart, int Count);
|
|
|
|
private static readonly ConcurrentDictionary<string, GateEntry> Gates = new();
|
|
|
|
public static bool ShouldSend(string category, string content, TimeSpan window, int maxPerWindow = 1)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(category))
|
|
return false;
|
|
|
|
var now = DateTimeOffset.UtcNow;
|
|
var key = $"{category}:{Fingerprint(content)}";
|
|
|
|
while (true)
|
|
{
|
|
if (!Gates.TryGetValue(key, out var current))
|
|
{
|
|
var initial = new GateEntry(now, 1);
|
|
if (Gates.TryAdd(key, initial))
|
|
return true;
|
|
continue;
|
|
}
|
|
|
|
if (now - current.WindowStart >= window)
|
|
{
|
|
var reset = new GateEntry(now, 1);
|
|
if (Gates.TryUpdate(key, reset, current))
|
|
return true;
|
|
continue;
|
|
}
|
|
|
|
if (current.Count >= maxPerWindow)
|
|
return false;
|
|
|
|
var incremented = current with { Count = current.Count + 1 };
|
|
if (Gates.TryUpdate(key, incremented, current))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private static string Fingerprint(string content)
|
|
{
|
|
if (string.IsNullOrEmpty(content))
|
|
return "empty";
|
|
|
|
var normalized = content.Length > 1500 ? content[..1500] : content;
|
|
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(normalized));
|
|
return Convert.ToHexString(bytes);
|
|
}
|
|
}
|