Harden admin telemetry and deployment safeguards
TaxBaik CI/CD / build-and-deploy (push) Successful in 4m30s
TaxBaik CI/CD / build-and-deploy (push) Successful in 4m30s
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -47,14 +47,29 @@ public class TelegramNotificationService : ITelegramNotificationService
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TelegramAlertGate.ShouldSend("telegram:default", message, TimeSpan.FromMinutes(5)))
|
||||
return;
|
||||
|
||||
await SendToChat(_defaultChatId, message, ct);
|
||||
}
|
||||
|
||||
public async Task SendInquiryNotificationAsync(string message, CancellationToken ct = default) =>
|
||||
await SendToChat(_inquiryChatId, $"<b>📋 문의 사항</b>\n\n{message}", ct);
|
||||
public async Task SendInquiryNotificationAsync(string message, CancellationToken ct = default)
|
||||
{
|
||||
var text = $"<b>📋 문의 사항</b>\n\n{message}";
|
||||
if (!TelegramAlertGate.ShouldSend("telegram:inquiry", text, TimeSpan.FromMinutes(10)))
|
||||
return;
|
||||
|
||||
public async Task SendSystemNotificationAsync(string message, CancellationToken ct = default) =>
|
||||
await SendToChat(_systemChatId, $"<b>🔧 시스템 알림</b>\n\n{message}", ct);
|
||||
await SendToChat(_inquiryChatId, text, ct);
|
||||
}
|
||||
|
||||
public async Task SendSystemNotificationAsync(string message, CancellationToken ct = default)
|
||||
{
|
||||
var text = $"<b>🔧 시스템 알림</b>\n\n{message}";
|
||||
if (!TelegramAlertGate.ShouldSend("telegram:system", text, TimeSpan.FromMinutes(10)))
|
||||
return;
|
||||
|
||||
await SendToChat(_systemChatId, text, ct);
|
||||
}
|
||||
|
||||
private async Task SendToChat(string chatId, string message, CancellationToken ct)
|
||||
{
|
||||
@@ -89,18 +104,27 @@ public class TelegramNotificationService : ITelegramNotificationService
|
||||
public async Task SendErrorAsync(string title, string details, CancellationToken ct = default)
|
||||
{
|
||||
var message = $"<b>❌ {title}</b>\n\n{details}\n\n<i>{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC</i>";
|
||||
if (!TelegramAlertGate.ShouldSend("telegram:error", message, TimeSpan.FromMinutes(15)))
|
||||
return;
|
||||
|
||||
await SendToChat(_systemChatId, message, ct);
|
||||
}
|
||||
|
||||
public async Task SendInfoAsync(string title, string message, CancellationToken ct = default)
|
||||
{
|
||||
var text = $"<b>ℹ️ {title}</b>\n\n{message}\n\n<i>{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC</i>";
|
||||
await SendMessageAsync(text, ct);
|
||||
if (!TelegramAlertGate.ShouldSend("telegram:info", text, TimeSpan.FromMinutes(30)))
|
||||
return;
|
||||
|
||||
await SendToChat(_defaultChatId, text, ct);
|
||||
}
|
||||
|
||||
public async Task SendReportAsync(string reportTitle, string reportContent, CancellationToken ct = default)
|
||||
{
|
||||
var text = $"<b>📊 {reportTitle}</b>\n\n{reportContent}\n\n<i>{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC</i>";
|
||||
if (!TelegramAlertGate.ShouldSend("telegram:report", text, TimeSpan.FromHours(20)))
|
||||
return;
|
||||
|
||||
await SendToChat(_systemChatId, text, ct);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user