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 EvaluateRiskAsync(RiskEvaluationRequest request); Task IsAllowedAsync(RiskEvaluationRequest request); Task IncrementMetricAsync(string memberId, string metric, int value = 1); Task> 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 _logger; private bool _disposed; public RiskControlClient( IRiskEvaluator evaluator, IRiskCounterService counterService, IRiskEventPublisher eventPublisher, ISamplingService samplingService, IFailoverStrategy failoverStrategy, RiskControlClientOptions options, ILogger logger) { _evaluator = evaluator; _counterService = counterService; _eventPublisher = eventPublisher; _samplingService = samplingService; _failoverStrategy = failoverStrategy; _options = options; _logger = logger; } public async Task 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 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> 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 _logger; public RiskControlClientHostedService( IRiskRuleLoader ruleLoader, IFailoverStrategy failoverStrategy, ILogger 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); } } }