feat(risk-control): add application services
This commit is contained in:
parent
d6f5c00554
commit
352291c68b
@ -0,0 +1,35 @@
|
|||||||
|
using Fengling.RiskControl.Application.Services;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace Fengling.RiskControl.Application.Commands;
|
||||||
|
|
||||||
|
public record EvaluateRiskCommand : IRequest<RiskEvaluationResult>
|
||||||
|
{
|
||||||
|
public long MemberId { get; init; }
|
||||||
|
public string EntityType { get; init; } = string.Empty;
|
||||||
|
public string EntityId { get; init; } = string.Empty;
|
||||||
|
public string ActionType { get; init; } = string.Empty;
|
||||||
|
public Dictionary<string, object> Context { get; init; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EvaluateRiskCommandHandler : IRequestHandler<EvaluateRiskCommand, RiskEvaluationResult>
|
||||||
|
{
|
||||||
|
private readonly IRiskEvaluationService _riskService;
|
||||||
|
|
||||||
|
public EvaluateRiskCommandHandler(IRiskEvaluationService riskService)
|
||||||
|
{
|
||||||
|
_riskService = riskService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RiskEvaluationResult> Handle(EvaluateRiskCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return await _riskService.EvaluateRiskAsync(new RiskEvaluationRequest
|
||||||
|
{
|
||||||
|
MemberId = request.MemberId,
|
||||||
|
EntityType = request.EntityType,
|
||||||
|
EntityId = request.EntityId,
|
||||||
|
ActionType = request.ActionType,
|
||||||
|
Context = request.Context
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
using Fengling.RiskControl.Domain.Aggregates.RiskScores;
|
||||||
|
using Fengling.RiskControl.Domain.Events;
|
||||||
|
using Fengling.RiskControl.Domain.Repositories;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace Fengling.RiskControl.Application.Events;
|
||||||
|
|
||||||
|
public class LotteryCompletedEventHandler : INotificationHandler<LotteryCompletedEvent>
|
||||||
|
{
|
||||||
|
private readonly IRiskScoreRepository _scoreRepository;
|
||||||
|
|
||||||
|
public LotteryCompletedEventHandler(IRiskScoreRepository scoreRepository)
|
||||||
|
{
|
||||||
|
_scoreRepository = scoreRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(LotteryCompletedEvent notification, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var score = RiskScore.Create(
|
||||||
|
notification.MemberId,
|
||||||
|
"lottery_result",
|
||||||
|
notification.ActivityId.ToString(),
|
||||||
|
expiresAt: DateTime.UtcNow.AddDays(1));
|
||||||
|
|
||||||
|
if (notification.IsWin && notification.WinAmount > notification.StakePoints * 5)
|
||||||
|
{
|
||||||
|
score.AddRiskFactor("big_win", 20, "赢得超过投入5倍");
|
||||||
|
}
|
||||||
|
|
||||||
|
await _scoreRepository.AddAsync(score);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
using Fengling.RiskControl.Application.Services;
|
||||||
|
using Fengling.RiskControl.Domain.Aggregates.RiskAlerts;
|
||||||
|
using Fengling.RiskControl.Domain.Events;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace Fengling.RiskControl.Application.Events;
|
||||||
|
|
||||||
|
public class RiskAlertTriggeredEventHandler : INotificationHandler<RiskAlertTriggeredEvent>
|
||||||
|
{
|
||||||
|
private readonly IRiskAlertService _alertService;
|
||||||
|
|
||||||
|
public RiskAlertTriggeredEventHandler(IRiskAlertService alertService)
|
||||||
|
{
|
||||||
|
_alertService = alertService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(RiskAlertTriggeredEvent notification, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (notification.RiskScore < 30)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var priority = notification.RiskScore switch
|
||||||
|
{
|
||||||
|
>= 80 => RiskAlertPriority.Critical,
|
||||||
|
>= 60 => RiskAlertPriority.High,
|
||||||
|
>= 40 => RiskAlertPriority.Medium,
|
||||||
|
_ => RiskAlertPriority.Low
|
||||||
|
};
|
||||||
|
|
||||||
|
await _alertService.CreateAlertAsync(
|
||||||
|
notification.MemberId,
|
||||||
|
notification.AlertType,
|
||||||
|
notification.Reason,
|
||||||
|
priority);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
Fengling.RiskControl.Application/Services/ILotteryService.cs
Normal file
37
Fengling.RiskControl.Application/Services/ILotteryService.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
using Fengling.RiskControl.Domain.Aggregates.LotteryActivities;
|
||||||
|
|
||||||
|
namespace Fengling.RiskControl.Application.Services;
|
||||||
|
|
||||||
|
public record LotteryParticipationRequest
|
||||||
|
{
|
||||||
|
public long MemberId { get; init; }
|
||||||
|
public string ActivityType { get; init; } = string.Empty;
|
||||||
|
public int StakePoints { get; init; }
|
||||||
|
public string? IpAddress { get; init; }
|
||||||
|
public string? DeviceId { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record LotteryParticipationResult
|
||||||
|
{
|
||||||
|
public long ActivityId { get; init; }
|
||||||
|
public LotteryParticipationStatus Status { get; init; }
|
||||||
|
public int? WinAmount { get; init; }
|
||||||
|
public string Message { get; init; } = string.Empty;
|
||||||
|
public RiskEvaluationResult? RiskResult { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum LotteryParticipationStatus
|
||||||
|
{
|
||||||
|
Allowed = 0,
|
||||||
|
Blocked = 1,
|
||||||
|
PendingVerification = 2,
|
||||||
|
InsufficientPoints = 3,
|
||||||
|
Failed = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ILotteryService
|
||||||
|
{
|
||||||
|
Task<LotteryParticipationResult> ParticipateAsync(LotteryParticipationRequest request);
|
||||||
|
Task<LotteryActivity?> GetActivityAsync(long activityId);
|
||||||
|
Task<IEnumerable<LotteryActivity>> GetMemberActivitiesAsync(long memberId);
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
using Fengling.RiskControl.Domain.Aggregates.RiskAlerts;
|
||||||
|
|
||||||
|
namespace Fengling.RiskControl.Application.Services;
|
||||||
|
|
||||||
|
public interface IRiskAlertService
|
||||||
|
{
|
||||||
|
Task<RiskAlert> CreateAlertAsync(long memberId, string alertType, string description,
|
||||||
|
RiskAlertPriority priority = RiskAlertPriority.Medium);
|
||||||
|
Task<RiskAlert> ResolveAlertAsync(long alertId, string notes);
|
||||||
|
Task<RiskAlert> DismissAlertAsync(long alertId, string notes);
|
||||||
|
Task<RiskAlert> EscalateAlertAsync(long alertId);
|
||||||
|
Task<IEnumerable<RiskAlert>> GetMemberAlertsAsync(long memberId);
|
||||||
|
Task<IEnumerable<RiskAlert>> GetPendingAlertsAsync();
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
using Fengling.RiskControl.Domain.Aggregates.RiskRules;
|
||||||
|
using Fengling.RiskControl.Domain.Aggregates.RiskScores;
|
||||||
|
|
||||||
|
namespace Fengling.RiskControl.Application.Services;
|
||||||
|
|
||||||
|
public record RiskEvaluationRequest
|
||||||
|
{
|
||||||
|
public long MemberId { get; init; }
|
||||||
|
public string EntityType { get; init; } = string.Empty;
|
||||||
|
public string EntityId { get; init; } = string.Empty;
|
||||||
|
public string ActionType { get; init; } = string.Empty;
|
||||||
|
public Dictionary<string, object> Context { get; init; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public record RiskEvaluationResult
|
||||||
|
{
|
||||||
|
public int TotalScore { get; init; }
|
||||||
|
public RiskLevel RiskLevel { get; init; }
|
||||||
|
public RiskRuleAction RecommendedAction { get; init; }
|
||||||
|
public List<RiskFactorResult> Factors { get; init; } = new();
|
||||||
|
public bool Blocked { get; init; }
|
||||||
|
public string Message { get; init; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record RiskFactorResult
|
||||||
|
{
|
||||||
|
public string FactorType { get; init; } = string.Empty;
|
||||||
|
public int Points { get; init; }
|
||||||
|
public string Description { get; init; } = string.Empty;
|
||||||
|
public string RuleName { get; init; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IRiskEvaluationService
|
||||||
|
{
|
||||||
|
Task<RiskEvaluationResult> EvaluateRiskAsync(RiskEvaluationRequest request);
|
||||||
|
Task<bool> IsAllowedAsync(RiskEvaluationRequest request);
|
||||||
|
}
|
||||||
92
Fengling.RiskControl.Application/Services/LotteryService.cs
Normal file
92
Fengling.RiskControl.Application/Services/LotteryService.cs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
using Fengling.RiskControl.Domain.Aggregates.LotteryActivities;
|
||||||
|
using Fengling.RiskControl.Domain.Events;
|
||||||
|
using Fengling.RiskControl.Domain.Repositories;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace Fengling.RiskControl.Application.Services;
|
||||||
|
|
||||||
|
public class LotteryService : ILotteryService
|
||||||
|
{
|
||||||
|
private readonly ILotteryActivityRepository _activityRepository;
|
||||||
|
private readonly IRiskEvaluationService _riskService;
|
||||||
|
private readonly IMediator _mediator;
|
||||||
|
|
||||||
|
public LotteryService(
|
||||||
|
ILotteryActivityRepository activityRepository,
|
||||||
|
IRiskEvaluationService riskService,
|
||||||
|
IMediator mediator)
|
||||||
|
{
|
||||||
|
_activityRepository = activityRepository;
|
||||||
|
_riskService = riskService;
|
||||||
|
_mediator = mediator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<LotteryParticipationResult> ParticipateAsync(LotteryParticipationRequest request)
|
||||||
|
{
|
||||||
|
var riskResult = await _riskService.EvaluateRiskAsync(new RiskEvaluationRequest
|
||||||
|
{
|
||||||
|
MemberId = request.MemberId,
|
||||||
|
EntityType = "lottery",
|
||||||
|
EntityId = request.ActivityType,
|
||||||
|
ActionType = "execute",
|
||||||
|
Context = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["stakePoints"] = request.StakePoints,
|
||||||
|
["activityType"] = request.ActivityType
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (riskResult.Blocked)
|
||||||
|
{
|
||||||
|
return new LotteryParticipationResult
|
||||||
|
{
|
||||||
|
Status = LotteryParticipationStatus.Blocked,
|
||||||
|
Message = riskResult.Message,
|
||||||
|
RiskResult = riskResult
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var activity = LotteryActivity.Create(
|
||||||
|
request.MemberId,
|
||||||
|
request.ActivityType,
|
||||||
|
request.StakePoints,
|
||||||
|
request.IpAddress,
|
||||||
|
request.DeviceId);
|
||||||
|
|
||||||
|
activity.MarkProcessing();
|
||||||
|
await _activityRepository.AddAsync(activity);
|
||||||
|
|
||||||
|
var (winAmount, isWin) = SimulateLotteryOutcome(request.StakePoints);
|
||||||
|
activity.Complete(winAmount, isWin);
|
||||||
|
|
||||||
|
await _mediator.Publish(new LotteryCompletedEvent(
|
||||||
|
activity.Id, request.MemberId, request.StakePoints, winAmount, isWin, riskResult.TotalScore));
|
||||||
|
|
||||||
|
return new LotteryParticipationResult
|
||||||
|
{
|
||||||
|
ActivityId = activity.Id,
|
||||||
|
Status = LotteryParticipationStatus.Allowed,
|
||||||
|
WinAmount = winAmount,
|
||||||
|
Message = isWin ? $"恭喜中奖! 获得 {winAmount} 积分" : "未中奖",
|
||||||
|
RiskResult = riskResult
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private (int winAmount, bool isWin) SimulateLotteryOutcome(int stakePoints)
|
||||||
|
{
|
||||||
|
var random = new Random();
|
||||||
|
var isWin = random.NextDouble() < 0.3;
|
||||||
|
var winAmount = isWin ? stakePoints * random.Next(2, 10) : 0;
|
||||||
|
return (winAmount, isWin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<LotteryActivity?> GetActivityAsync(long activityId)
|
||||||
|
{
|
||||||
|
return _activityRepository.GetByIdAsync(activityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IEnumerable<LotteryActivity>> GetMemberActivitiesAsync(long memberId)
|
||||||
|
{
|
||||||
|
return _activityRepository.GetByMemberIdAsync(memberId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
using Fengling.RiskControl.Domain.Aggregates.RiskAlerts;
|
||||||
|
using Fengling.RiskControl.Domain.Events;
|
||||||
|
using Fengling.RiskControl.Domain.Repositories;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace Fengling.RiskControl.Application.Services;
|
||||||
|
|
||||||
|
public class RiskAlertService : IRiskAlertService
|
||||||
|
{
|
||||||
|
private readonly IRiskAlertRepository _alertRepository;
|
||||||
|
private readonly IMediator _mediator;
|
||||||
|
|
||||||
|
public RiskAlertService(IRiskAlertRepository alertRepository, IMediator mediator)
|
||||||
|
{
|
||||||
|
_alertRepository = alertRepository;
|
||||||
|
_mediator = mediator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RiskAlert> CreateAlertAsync(long memberId, string alertType, string description,
|
||||||
|
RiskAlertPriority priority = RiskAlertPriority.Medium)
|
||||||
|
{
|
||||||
|
var alert = RiskAlert.Create(memberId, alertType, description, priority);
|
||||||
|
await _alertRepository.AddAsync(alert);
|
||||||
|
|
||||||
|
await _mediator.Publish(new RiskAlertTriggeredEvent(
|
||||||
|
alert.Id, memberId, alertType, 0, description));
|
||||||
|
|
||||||
|
return alert;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RiskAlert> ResolveAlertAsync(long alertId, string notes)
|
||||||
|
{
|
||||||
|
var alert = await _alertRepository.GetByIdAsync(alertId);
|
||||||
|
if (alert == null)
|
||||||
|
throw new KeyNotFoundException($"Alert not found: {alertId}");
|
||||||
|
|
||||||
|
alert.Resolve(notes);
|
||||||
|
await _alertRepository.UpdateAsync(alert);
|
||||||
|
|
||||||
|
return alert;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RiskAlert> DismissAlertAsync(long alertId, string notes)
|
||||||
|
{
|
||||||
|
var alert = await _alertRepository.GetByIdAsync(alertId);
|
||||||
|
if (alert == null)
|
||||||
|
throw new KeyNotFoundException($"Alert not found: {alertId}");
|
||||||
|
|
||||||
|
alert.Dismiss(notes);
|
||||||
|
await _alertRepository.UpdateAsync(alert);
|
||||||
|
|
||||||
|
return alert;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RiskAlert> EscalateAlertAsync(long alertId)
|
||||||
|
{
|
||||||
|
var alert = await _alertRepository.GetByIdAsync(alertId);
|
||||||
|
if (alert == null)
|
||||||
|
throw new KeyNotFoundException($"Alert not found: {alertId}");
|
||||||
|
|
||||||
|
alert.Escalate();
|
||||||
|
await _alertRepository.UpdateAsync(alert);
|
||||||
|
|
||||||
|
return alert;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IEnumerable<RiskAlert>> GetMemberAlertsAsync(long memberId)
|
||||||
|
{
|
||||||
|
return _alertRepository.GetByMemberIdAsync(memberId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IEnumerable<RiskAlert>> GetPendingAlertsAsync()
|
||||||
|
{
|
||||||
|
return _alertRepository.GetPendingAlertsAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,159 @@
|
|||||||
|
using Fengling.RiskControl.Domain.Aggregates.RiskRules;
|
||||||
|
using Fengling.RiskControl.Domain.Aggregates.RiskScores;
|
||||||
|
using Fengling.RiskControl.Domain.Repositories;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace Fengling.RiskControl.Application.Services;
|
||||||
|
|
||||||
|
public class RiskEvaluationService : IRiskEvaluationService
|
||||||
|
{
|
||||||
|
private readonly IRiskRuleRepository _ruleRepository;
|
||||||
|
private readonly IRiskScoreRepository _scoreRepository;
|
||||||
|
private readonly IMediator _mediator;
|
||||||
|
|
||||||
|
public RiskEvaluationService(
|
||||||
|
IRiskRuleRepository ruleRepository,
|
||||||
|
IRiskScoreRepository scoreRepository,
|
||||||
|
IMediator mediator)
|
||||||
|
{
|
||||||
|
_ruleRepository = ruleRepository;
|
||||||
|
_scoreRepository = scoreRepository;
|
||||||
|
_mediator = mediator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RiskEvaluationResult> EvaluateRiskAsync(RiskEvaluationRequest request)
|
||||||
|
{
|
||||||
|
var rules = await _ruleRepository.GetActiveRulesAsync();
|
||||||
|
var factors = new List<RiskFactorResult>();
|
||||||
|
var totalScore = 0;
|
||||||
|
|
||||||
|
foreach (var rule in rules)
|
||||||
|
{
|
||||||
|
var factor = await EvaluateRuleAsync(rule, request);
|
||||||
|
if (factor != null)
|
||||||
|
{
|
||||||
|
factors.Add(factor);
|
||||||
|
totalScore += factor.Points;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var riskLevel = DetermineRiskLevel(totalScore);
|
||||||
|
var recommendedAction = DetermineAction(riskLevel);
|
||||||
|
var blocked = recommendedAction == RiskRuleAction.Block;
|
||||||
|
|
||||||
|
return new RiskEvaluationResult
|
||||||
|
{
|
||||||
|
TotalScore = totalScore,
|
||||||
|
Factors = factors,
|
||||||
|
RiskLevel = riskLevel,
|
||||||
|
RecommendedAction = recommendedAction,
|
||||||
|
Blocked = blocked,
|
||||||
|
Message = blocked ? "操作被风险控制系统拒绝" : "操作已通过风险评估"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsAllowedAsync(RiskEvaluationRequest request)
|
||||||
|
{
|
||||||
|
var result = await EvaluateRiskAsync(request);
|
||||||
|
return !result.Blocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<RiskFactorResult?> EvaluateRuleAsync(RiskRule rule, RiskEvaluationRequest request)
|
||||||
|
{
|
||||||
|
return rule.RuleType switch
|
||||||
|
{
|
||||||
|
RiskRuleType.FrequencyLimit => await EvaluateFrequencyLimitAsync(rule, request),
|
||||||
|
RiskRuleType.AmountLimit => await EvaluateAmountLimitAsync(rule, request),
|
||||||
|
RiskRuleType.Blacklist => EvaluateBlacklist(rule, request),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<RiskFactorResult?> EvaluateFrequencyLimitAsync(RiskRule rule, RiskEvaluationRequest request)
|
||||||
|
{
|
||||||
|
var config = rule.GetConfig<FrequencyLimitConfig>();
|
||||||
|
var recentCount = 0;
|
||||||
|
if (recentCount >= config.MaxCount)
|
||||||
|
{
|
||||||
|
return new RiskFactorResult
|
||||||
|
{
|
||||||
|
FactorType = "frequency_limit",
|
||||||
|
Points = config.Points,
|
||||||
|
Description = $"超过频率限制: {recentCount}/{config.MaxCount}",
|
||||||
|
RuleName = rule.Name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<RiskFactorResult?> EvaluateAmountLimitAsync(RiskRule rule, RiskEvaluationRequest request)
|
||||||
|
{
|
||||||
|
var config = rule.GetConfig<AmountLimitConfig>();
|
||||||
|
if (request.Context.TryGetValue("amount", out var amountObj) && amountObj is int amount)
|
||||||
|
{
|
||||||
|
if (amount > config.MaxAmount)
|
||||||
|
{
|
||||||
|
return new RiskFactorResult
|
||||||
|
{
|
||||||
|
FactorType = "amount_limit",
|
||||||
|
Points = config.Points,
|
||||||
|
Description = $"超过金额限制: {amount}/{config.MaxAmount}",
|
||||||
|
RuleName = rule.Name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RiskFactorResult? EvaluateBlacklist(RiskRule rule, RiskEvaluationRequest request)
|
||||||
|
{
|
||||||
|
var blacklist = rule.GetConfig<BlacklistConfig>();
|
||||||
|
if (blacklist.MemberIds.Contains(request.MemberId))
|
||||||
|
{
|
||||||
|
return new RiskFactorResult
|
||||||
|
{
|
||||||
|
FactorType = "blacklist",
|
||||||
|
Points = 100,
|
||||||
|
Description = "用户在黑名单中",
|
||||||
|
RuleName = rule.Name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RiskLevel DetermineRiskLevel(int score)
|
||||||
|
{
|
||||||
|
return score >= 70 ? RiskLevel.High :
|
||||||
|
score >= 30 ? RiskLevel.Medium :
|
||||||
|
RiskLevel.Low;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RiskRuleAction DetermineAction(RiskLevel level)
|
||||||
|
{
|
||||||
|
return level switch
|
||||||
|
{
|
||||||
|
RiskLevel.Critical => RiskRuleAction.Block,
|
||||||
|
RiskLevel.High => RiskRuleAction.RequireVerification,
|
||||||
|
RiskLevel.Medium => RiskRuleAction.FlagForReview,
|
||||||
|
_ => RiskRuleAction.Allow
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FrequencyLimitConfig
|
||||||
|
{
|
||||||
|
public int MaxCount { get; set; }
|
||||||
|
public int WindowMinutes { get; set; }
|
||||||
|
public int Points { get; set; } = 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AmountLimitConfig
|
||||||
|
{
|
||||||
|
public int MaxAmount { get; set; }
|
||||||
|
public int Points { get; set; } = 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BlacklistConfig
|
||||||
|
{
|
||||||
|
public HashSet<long> MemberIds { get; set; } = new();
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
using Fengling.RiskControl.Application.Commands;
|
||||||
|
using FluentValidation;
|
||||||
|
|
||||||
|
namespace Fengling.RiskControl.Application.Validators;
|
||||||
|
|
||||||
|
public class EvaluateRiskCommandValidator : AbstractValidator<EvaluateRiskCommand>
|
||||||
|
{
|
||||||
|
public EvaluateRiskCommandValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.MemberId).GreaterThan(0);
|
||||||
|
RuleFor(x => x.EntityType).NotEmpty().MaximumLength(50);
|
||||||
|
RuleFor(x => x.EntityId).NotEmpty().MaximumLength(100);
|
||||||
|
RuleFor(x => x.ActionType).NotEmpty().MaximumLength(50);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user