fengling-risk-control/Fengling.RiskControl.Client/RiskControlClient.cs
Sam 293209b1dc feat: add Fengling.RiskControl.Client SDK
- 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
2026-02-06 00:16:53 +08:00

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