diff --git a/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingChecker.cs b/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingChecker.cs index 56f22a79c6..095fa6cbf6 100644 --- a/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingChecker.cs +++ b/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingChecker.cs @@ -147,6 +147,11 @@ public class OperationRateLimitingChecker : IOperationRateLimitingChecker, ITran public virtual async Task ResetAsync(string policyName, OperationRateLimitingContext? context = null) { + if (!Options.IsEnabled) + { + return; + } + context = EnsureContext(context); var policy = await PolicyProvider.GetAsync(policyName); var rules = CreateRules(policy); @@ -168,12 +173,11 @@ public class OperationRateLimitingChecker : IOperationRateLimitingChecker, ITran { var rules = new List(); - for (var i = 0; i < policy.Rules.Count; i++) + foreach (var ruleDefinition in policy.Rules) { rules.Add(new FixedWindowOperationRateLimitingRule( policy.Name, - i, - policy.Rules[i], + ruleDefinition, Store, CurrentUser, CurrentTenant, @@ -203,7 +207,7 @@ public class OperationRateLimitingChecker : IOperationRateLimitingChecker, ITran IsAllowed = isAllowed, RemainingCount = mostRestrictive.RemainingCount, MaxCount = mostRestrictive.MaxCount, - CurrentCount = mostRestrictive.MaxCount - mostRestrictive.RemainingCount, + CurrentCount = mostRestrictive.CurrentCount, RetryAfter = ruleResults.Any(r => !r.IsAllowed && r.RetryAfter.HasValue) ? ruleResults .Where(r => !r.IsAllowed && r.RetryAfter.HasValue) @@ -248,7 +252,7 @@ public class OperationRateLimitingChecker : IOperationRateLimitingChecker, ITran ["IsAllowed"] = ruleResult.IsAllowed, ["MaxCount"] = ruleResult.MaxCount, ["RemainingCount"] = ruleResult.RemainingCount, - ["CurrentCount"] = ruleResult.MaxCount - ruleResult.RemainingCount, + ["CurrentCount"] = ruleResult.CurrentCount, ["WindowDurationSeconds"] = (int)ruleResult.WindowDuration.TotalSeconds, ["WindowDescription"] = ruleResult.WindowDuration > TimeSpan.Zero ? formatter.Format(ruleResult.WindowDuration) diff --git a/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingRuleResult.cs b/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingRuleResult.cs index e05e6bf4fb..d725b8f7f2 100644 --- a/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingRuleResult.cs +++ b/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingRuleResult.cs @@ -8,6 +8,8 @@ public class OperationRateLimitingRuleResult public bool IsAllowed { get; set; } + public int CurrentCount { get; set; } + public int RemainingCount { get; set; } public int MaxCount { get; set; } diff --git a/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Rules/FixedWindowOperationRateLimitingRule.cs b/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Rules/FixedWindowOperationRateLimitingRule.cs index cd2dd4db32..bd869e2c5b 100644 --- a/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Rules/FixedWindowOperationRateLimitingRule.cs +++ b/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Rules/FixedWindowOperationRateLimitingRule.cs @@ -10,7 +10,6 @@ public class FixedWindowOperationRateLimitingRule : IOperationRateLimitingRule private const string HostTenantKey = "host"; protected string PolicyName { get; } - protected int RuleIndex { get; } protected OperationRateLimitingRuleDefinition Definition { get; } protected IOperationRateLimitingStore Store { get; } protected ICurrentUser CurrentUser { get; } @@ -19,7 +18,6 @@ public class FixedWindowOperationRateLimitingRule : IOperationRateLimitingRule public FixedWindowOperationRateLimitingRule( string policyName, - int ruleIndex, OperationRateLimitingRuleDefinition definition, IOperationRateLimitingStore store, ICurrentUser currentUser, @@ -27,7 +25,6 @@ public class FixedWindowOperationRateLimitingRule : IOperationRateLimitingRule IWebClientInfoProvider webClientInfoProvider) { PolicyName = policyName; - RuleIndex = ruleIndex; Definition = definition; Store = store; CurrentUser = currentUser; @@ -140,6 +137,7 @@ public class FixedWindowOperationRateLimitingRule : IOperationRateLimitingRule { RuleName = $"{PolicyName}:Rule[{(long)Definition.Duration.TotalSeconds}s,{Definition.MaxCount},{Definition.PartitionType}]", IsAllowed = storeResult.IsAllowed, + CurrentCount = storeResult.CurrentCount, RemainingCount = storeResult.MaxCount - storeResult.CurrentCount, MaxCount = storeResult.MaxCount, RetryAfter = storeResult.RetryAfter, diff --git a/framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingChecker_Tests.cs b/framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingChecker_Tests.cs index 8a967077c5..08a605c894 100644 --- a/framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingChecker_Tests.cs +++ b/framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingChecker_Tests.cs @@ -731,6 +731,57 @@ public class OperationRateLimitingChecker_Tests : OperationRateLimitingTestBase }); } + + [Fact] + public async Task Should_Return_Correct_CurrentCount_In_RuleResults() + { + var param = $"current-count-{Guid.NewGuid()}"; + var context = new OperationRateLimitingContext { Parameter = param }; + + await _checker.CheckAsync("TestSimple", context); + await _checker.CheckAsync("TestSimple", context); + + var status = await _checker.GetStatusAsync("TestSimple", context); + status.RuleResults.ShouldNotBeNull(); + status.RuleResults!.Count.ShouldBe(1); + status.RuleResults[0].CurrentCount.ShouldBe(2); + status.RuleResults[0].RemainingCount.ShouldBe(1); + status.RuleResults[0].MaxCount.ShouldBe(3); + } + + [Fact] + public async Task ResetAsync_Should_Skip_When_Disabled() + { + var options = GetRequiredService>(); + var originalValue = options.Value.IsEnabled; + + try + { + var param = $"reset-disabled-{Guid.NewGuid()}"; + var context = new OperationRateLimitingContext { Parameter = param }; + + // Exhaust the quota + await _checker.CheckAsync("TestSimple", context); + await _checker.CheckAsync("TestSimple", context); + await _checker.CheckAsync("TestSimple", context); + + // Disable and call ResetAsync — should be a no-op (counter not actually reset) + options.Value.IsEnabled = false; + await _checker.ResetAsync("TestSimple", context); + + // Re-enable: quota should still be exhausted because reset was skipped + options.Value.IsEnabled = true; + await Assert.ThrowsAsync(async () => + { + await _checker.CheckAsync("TestSimple", context); + }); + } + finally + { + options.Value.IsEnabled = originalValue; + } + } + private static ClaimsPrincipal CreateClaimsPrincipal(Guid userId) { return new ClaimsPrincipal(