Browse Source

feat: enhance operation rate limiting with current count tracking and reset behavior

pull/25024/head
maliming 4 weeks ago
parent
commit
471697841d
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 14
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingChecker.cs
  2. 2
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingRuleResult.cs
  3. 4
      framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Rules/FixedWindowOperationRateLimitingRule.cs
  4. 51
      framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingChecker_Tests.cs

14
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<IOperationRateLimitingRule>();
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)

2
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; }

4
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,

51
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<Microsoft.Extensions.Options.IOptions<AbpOperationRateLimitingOptions>>();
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<AbpOperationRateLimitingException>(async () =>
{
await _checker.CheckAsync("TestSimple", context);
});
}
finally
{
options.Value.IsEnabled = originalValue;
}
}
private static ClaimsPrincipal CreateClaimsPrincipal(Guid userId)
{
return new ClaimsPrincipal(

Loading…
Cancel
Save