using System.Collections.Concurrent; namespace molilian.core { public static class SpecialBusinessRateLimiter { private static readonly ConcurrentDictionary States = new(); public static bool TryAcquire(string bucketKey, int limit, TimeSpan window) { if (string.IsNullOrWhiteSpace(bucketKey)) return true; if (limit <= 0 || window <= TimeSpan.Zero) return true; var state = States.GetOrAdd(bucketKey, _ => new SlidingWindowState()); var nowTicks = DateTime.UtcNow.Ticks; var minTicks = nowTicks - window.Ticks; lock (state.SyncRoot) { while (state.Timestamps.Count > 0 && state.Timestamps.Peek() < minTicks) { state.Timestamps.Dequeue(); } if (state.Timestamps.Count >= limit) { return false; } state.Timestamps.Enqueue(nowTicks); return true; } } /// /// 归还 acquiredSinceTicks 之后占用的一个名额(请求未真正发生时使用)。 /// 超过窗口时间的占用已自然释放,不再处理,避免误删其他请求的名额。 /// public static void Release(string bucketKey, TimeSpan window, long acquiredSinceTicks) { if (string.IsNullOrWhiteSpace(bucketKey)) return; if (!States.TryGetValue(bucketKey, out var state)) return; if (DateTime.UtcNow.Ticks - acquiredSinceTicks >= window.Ticks) return; lock (state.SyncRoot) { var timestamps = state.Timestamps.ToArray(); int index = Array.FindLastIndex(timestamps, t => t >= acquiredSinceTicks); if (index < 0) return; state.Timestamps.Clear(); for (int i = 0; i < timestamps.Length; i++) { if (i != index) state.Timestamps.Enqueue(timestamps[i]); } } } private sealed class SlidingWindowState { public object SyncRoot { get; } = new(); public Queue Timestamps { get; } = new(); } } }