- 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
121 lines
4.1 KiB
C#
121 lines
4.1 KiB
C#
using DotNetCore.CAP;
|
|
using Fengling.RiskControl.Configuration;
|
|
using Fengling.RiskControl.Evaluation;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Fengling.RiskControl.Events;
|
|
|
|
public interface IRiskEventPublisher
|
|
{
|
|
Task PublishRiskAssessmentAsync(RiskEvaluationRequest request, RiskEvaluationResult result);
|
|
Task PublishRiskAlertAsync(RiskEvaluationRequest request, RiskEvaluationResult result);
|
|
}
|
|
|
|
public class CapEventPublisher : IRiskEventPublisher
|
|
{
|
|
private readonly ICapPublisher _capPublisher;
|
|
private readonly RiskControlClientOptions _options;
|
|
private readonly ILogger<CapEventPublisher> _logger;
|
|
|
|
public CapEventPublisher(
|
|
ICapPublisher capPublisher,
|
|
RiskControlClientOptions options,
|
|
ILogger<CapEventPublisher> logger)
|
|
{
|
|
_capPublisher = capPublisher;
|
|
_options = options;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task PublishRiskAssessmentAsync(RiskEvaluationRequest request, RiskEvaluationResult result)
|
|
{
|
|
if (!_options.Cap.PublisherEnabled)
|
|
{
|
|
_logger.LogDebug("CAP publisher is disabled, skipping event publish");
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
var @event = new RiskAssessmentEvent
|
|
{
|
|
MemberId = request.MemberId,
|
|
EventType = request.EventType,
|
|
Amount = request.Amount,
|
|
DeviceFingerprint = request.DeviceFingerprint,
|
|
IpAddress = request.IpAddress,
|
|
TotalScore = result.TotalScore,
|
|
RiskLevel = result.RiskLevel.ToString(),
|
|
Blocked = result.Blocked,
|
|
Factors = result.Factors,
|
|
EvaluatedAt = result.EvaluatedAt
|
|
};
|
|
|
|
await _capPublisher.PublishAsync("fengling.risk.assessed", @event);
|
|
_logger.LogDebug("Published RiskAssessmentEvent for member {MemberId}", request.MemberId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to publish RiskAssessmentEvent for member {MemberId}", request.MemberId);
|
|
}
|
|
}
|
|
|
|
public async Task PublishRiskAlertAsync(RiskEvaluationRequest request, RiskEvaluationResult result)
|
|
{
|
|
if (!_options.Cap.PublisherEnabled)
|
|
return;
|
|
|
|
try
|
|
{
|
|
var @event = new RiskAlertEvent
|
|
{
|
|
MemberId = request.MemberId,
|
|
EventType = request.EventType,
|
|
RiskLevel = result.RiskLevel.ToString(),
|
|
TotalScore = result.TotalScore,
|
|
Blocked = result.Blocked,
|
|
AlertTime = DateTime.UtcNow,
|
|
Priority = result.RiskLevel switch
|
|
{
|
|
Evaluation.RiskLevel.High => "P0",
|
|
Evaluation.RiskLevel.Medium => "P1",
|
|
_ => "P2"
|
|
}
|
|
};
|
|
|
|
await _capPublisher.PublishAsync("fengling.risk.alert", @event);
|
|
_logger.LogWarning("Published RiskAlertEvent for member {MemberId}, level={Level}",
|
|
request.MemberId, result.RiskLevel);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to publish RiskAlertEvent for member {MemberId}", request.MemberId);
|
|
}
|
|
}
|
|
}
|
|
|
|
public class RiskAssessmentEvent
|
|
{
|
|
public string MemberId { get; set; } = string.Empty;
|
|
public string EventType { get; set; } = string.Empty;
|
|
public int? Amount { get; set; }
|
|
public string? DeviceFingerprint { get; set; }
|
|
public string? IpAddress { get; set; }
|
|
public int TotalScore { get; set; }
|
|
public string RiskLevel { get; set; } = string.Empty;
|
|
public bool Blocked { get; set; }
|
|
public List<RiskFactorResult> Factors { get; set; } = new();
|
|
public DateTime EvaluatedAt { get; set; }
|
|
}
|
|
|
|
public class RiskAlertEvent
|
|
{
|
|
public string MemberId { get; set; } = string.Empty;
|
|
public string EventType { get; set; } = string.Empty;
|
|
public string RiskLevel { get; set; } = string.Empty;
|
|
public int TotalScore { get; set; }
|
|
public bool Blocked { get; set; }
|
|
public DateTime AlertTime { get; set; }
|
|
public string Priority { get; set; } = string.Empty;
|
|
}
|