- Implement RedisCounterService for rate limiting - Implement RuleLoader with timer refresh - Implement RiskEvaluator for local rule evaluation - Implement SamplingService for CAP events - Implement CapEventPublisher for async event publishing - Implement FailoverStrategy for Redis failure handling - Add configuration classes and DI extensions - Add unit tests (9 tests) - Add NuGet publishing script
142 lines
4.5 KiB
C#
142 lines
4.5 KiB
C#
using Fengling.RiskControl.Configuration;
|
|
using Fengling.RiskControl.Counter;
|
|
using Fengling.RiskControl.Evaluation;
|
|
using Fengling.RiskControl.Events;
|
|
using Fengling.RiskControl.Failover;
|
|
using Fengling.RiskControl.Rules;
|
|
using Fengling.RiskControl.Sampling;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Fengling.RiskControl;
|
|
|
|
public interface IRiskControlClient
|
|
{
|
|
Task<RiskEvaluationResult> EvaluateRiskAsync(RiskEvaluationRequest request);
|
|
Task<bool> IsAllowedAsync(RiskEvaluationRequest request);
|
|
Task IncrementMetricAsync(string memberId, string metric, int value = 1);
|
|
Task<Dictionary<string, int>> GetMemberMetricsAsync(string memberId);
|
|
RiskControlMode GetCurrentMode();
|
|
}
|
|
|
|
public class RiskControlClient : IRiskControlClient, IDisposable
|
|
{
|
|
private readonly IRiskEvaluator _evaluator;
|
|
private readonly IRiskCounterService _counterService;
|
|
private readonly IRiskEventPublisher _eventPublisher;
|
|
private readonly ISamplingService _samplingService;
|
|
private readonly IFailoverStrategy _failoverStrategy;
|
|
private readonly RiskControlClientOptions _options;
|
|
private readonly ILogger<RiskControlClient> _logger;
|
|
private bool _disposed;
|
|
|
|
public RiskControlClient(
|
|
IRiskEvaluator evaluator,
|
|
IRiskCounterService counterService,
|
|
IRiskEventPublisher eventPublisher,
|
|
ISamplingService samplingService,
|
|
IFailoverStrategy failoverStrategy,
|
|
RiskControlClientOptions options,
|
|
ILogger<RiskControlClient> logger)
|
|
{
|
|
_evaluator = evaluator;
|
|
_counterService = counterService;
|
|
_eventPublisher = eventPublisher;
|
|
_samplingService = samplingService;
|
|
_failoverStrategy = failoverStrategy;
|
|
_options = options;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<RiskEvaluationResult> EvaluateRiskAsync(RiskEvaluationRequest request)
|
|
{
|
|
return await _failoverStrategy.ExecuteWithFailoverAsync(async () =>
|
|
{
|
|
var result = await _evaluator.EvaluateAsync(request);
|
|
|
|
if (_samplingService.ShouldSample(result))
|
|
{
|
|
await _eventPublisher.PublishRiskAssessmentAsync(request, result);
|
|
}
|
|
|
|
if (result.Blocked)
|
|
{
|
|
await _eventPublisher.PublishRiskAlertAsync(request, result);
|
|
}
|
|
|
|
return result;
|
|
});
|
|
}
|
|
|
|
public async Task<bool> IsAllowedAsync(RiskEvaluationRequest request)
|
|
{
|
|
var result = await EvaluateRiskAsync(request);
|
|
return !result.Blocked;
|
|
}
|
|
|
|
public async Task IncrementMetricAsync(string memberId, string metric, int value = 1)
|
|
{
|
|
await _failoverStrategy.ExecuteWithFailoverAsync(async () =>
|
|
{
|
|
await _counterService.IncrementAsync(memberId, metric, value);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
public async Task<Dictionary<string, int>> GetMemberMetricsAsync(string memberId)
|
|
{
|
|
return await _failoverStrategy.ExecuteWithFailoverAsync(async () =>
|
|
{
|
|
return await _counterService.GetAllValuesAsync(memberId);
|
|
});
|
|
}
|
|
|
|
public RiskControlMode GetCurrentMode() => _failoverStrategy.GetCurrentMode();
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_disposed) return;
|
|
_disposed = true;
|
|
}
|
|
}
|
|
|
|
public class RiskControlClientHostedService : BackgroundService
|
|
{
|
|
private readonly IRiskRuleLoader _ruleLoader;
|
|
private readonly IFailoverStrategy _failoverStrategy;
|
|
private readonly ILogger<RiskControlClientHostedService> _logger;
|
|
|
|
public RiskControlClientHostedService(
|
|
IRiskRuleLoader ruleLoader,
|
|
IFailoverStrategy failoverStrategy,
|
|
ILogger<RiskControlClientHostedService> logger)
|
|
{
|
|
_ruleLoader = ruleLoader;
|
|
_failoverStrategy = failoverStrategy;
|
|
_logger = logger;
|
|
}
|
|
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
{
|
|
_logger.LogInformation("RiskControl Client Hosted Service starting...");
|
|
|
|
while (!stoppingToken.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
var isHealthy = await _failoverStrategy.IsHealthyAsync();
|
|
if (!isHealthy)
|
|
{
|
|
_logger.LogWarning("Redis is unhealthy, client cannot function properly");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error checking health");
|
|
}
|
|
|
|
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
|
|
}
|
|
}
|
|
}
|