diff --git a/Fengling.RiskControl.Application/Commands/EvaluateRiskCommand.cs b/Fengling.RiskControl.Application/Commands/EvaluateRiskCommand.cs deleted file mode 100644 index 6ded6fe..0000000 --- a/Fengling.RiskControl.Application/Commands/EvaluateRiskCommand.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Fengling.RiskControl.Application.Services; -using MediatR; - -namespace Fengling.RiskControl.Application.Commands; - -public record EvaluateRiskCommand : IRequest -{ - 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 Context { get; init; } = new(); -} - -public class EvaluateRiskCommandHandler : IRequestHandler -{ - private readonly IRiskEvaluationService _riskService; - - public EvaluateRiskCommandHandler(IRiskEvaluationService riskService) - { - _riskService = riskService; - } - - public async Task 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 - }); - } -} diff --git a/Fengling.RiskControl.Application/Events/LotteryCompletedEventHandler.cs b/Fengling.RiskControl.Application/Events/LotteryCompletedEventHandler.cs deleted file mode 100644 index 773f5cc..0000000 --- a/Fengling.RiskControl.Application/Events/LotteryCompletedEventHandler.cs +++ /dev/null @@ -1,34 +0,0 @@ -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 -{ - private const int BIG_WIN_MULTIPLIER = 5; - - 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 * BIG_WIN_MULTIPLIER) - { - score.AddRiskFactor("big_win", 20, "赢得超过投入5倍"); - } - - await _scoreRepository.AddAsync(score); - } -} diff --git a/Fengling.RiskControl.Application/Events/MemberEventSubscriptions.cs b/Fengling.RiskControl.Application/Events/MemberEventSubscriptions.cs deleted file mode 100644 index d0569e5..0000000 --- a/Fengling.RiskControl.Application/Events/MemberEventSubscriptions.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Fengling.RiskControl.Domain.Aggregates.RiskScores; -using Fengling.RiskControl.Domain.Repositories; -using MediatR; - -namespace Fengling.RiskControl.Application.Events; - -public class MemberRegisteredEventHandler : INotificationHandler -{ - private readonly IRiskScoreRepository _scoreRepository; - - public MemberRegisteredEventHandler(IRiskScoreRepository scoreRepository) - { - _scoreRepository = scoreRepository; - } - - public async Task Handle(Fengling.Member.Domain.Events.Member.MemberRegisteredEvent notification, CancellationToken cancellationToken) - { - var baselineScore = RiskScore.Create( - notification.MemberId, - "member_registration", - $"reg_{notification.MemberId}", - expiresAt: DateTime.UtcNow.AddDays(30)); - - await _scoreRepository.AddAsync(baselineScore); - } -} - -public class PointsChangedEventHandler : INotificationHandler -{ - private const int LARGE_POINT_THRESHOLD = 1000; - - private readonly IRiskScoreRepository _scoreRepository; - - public PointsChangedEventHandler(IRiskScoreRepository scoreRepository) - { - _scoreRepository = scoreRepository; - } - - public async Task Handle(Fengling.Member.Domain.Events.Points.PointsChangedEvent notification, CancellationToken cancellationToken) - { - if (notification.ChangedPoints > LARGE_POINT_THRESHOLD) - { - var score = RiskScore.Create( - notification.MemberId, - "large_point_change", - notification.AccountId.ToString()); - - score.AddRiskFactor("large_point_change", 25, $"大额积分变动: {notification.ChangedPoints}"); - await _scoreRepository.AddAsync(score); - } - } -} diff --git a/Fengling.RiskControl.Application/Events/RiskAlertTriggeredEventHandler.cs b/Fengling.RiskControl.Application/Events/RiskAlertTriggeredEventHandler.cs deleted file mode 100644 index 0f0c785..0000000 --- a/Fengling.RiskControl.Application/Events/RiskAlertTriggeredEventHandler.cs +++ /dev/null @@ -1,38 +0,0 @@ -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 -{ - private const int ALERT_TRIGGER_THRESHOLD = 30; - - private readonly IRiskAlertService _alertService; - - public RiskAlertTriggeredEventHandler(IRiskAlertService alertService) - { - _alertService = alertService; - } - - public async Task Handle(RiskAlertTriggeredEvent notification, CancellationToken cancellationToken) - { - if (notification.RiskScore < ALERT_TRIGGER_THRESHOLD) - 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); - } -} diff --git a/Fengling.RiskControl.Application/Fengling.RiskControl.Application.csproj b/Fengling.RiskControl.Application/Fengling.RiskControl.Application.csproj deleted file mode 100644 index 61e33da..0000000 --- a/Fengling.RiskControl.Application/Fengling.RiskControl.Application.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - net10.0 - enable - enable - - - - - - - - - - - - diff --git a/Fengling.RiskControl.Application/Services/ILotteryService.cs b/Fengling.RiskControl.Application/Services/ILotteryService.cs deleted file mode 100644 index 4f36e35..0000000 --- a/Fengling.RiskControl.Application/Services/ILotteryService.cs +++ /dev/null @@ -1,37 +0,0 @@ -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 ParticipateAsync(LotteryParticipationRequest request); - Task GetActivityAsync(long activityId); - Task> GetMemberActivitiesAsync(long memberId); -} diff --git a/Fengling.RiskControl.Application/Services/IMemberIntegrationService.cs b/Fengling.RiskControl.Application/Services/IMemberIntegrationService.cs deleted file mode 100644 index 8282c3d..0000000 --- a/Fengling.RiskControl.Application/Services/IMemberIntegrationService.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Fengling.RiskControl.Application.Services; - -public interface IMemberIntegrationService -{ - Task GetMemberPointsBalanceAsync(long memberId); - Task DeductPointsAsync(long memberId, int points, string reason); - Task AddPointsAsync(long memberId, int points, string reason); - Task GetMemberInfoAsync(long memberId); -} - -public record MemberInfo -{ - public long Id { get; init; } - public long TenantId { get; init; } - public string? PhoneNumber { get; init; } - public int PointsBalance { get; init; } - public DateTime RegisteredAt { get; init; } -} - -public record PointsBalanceResponse -{ - public int AvailableBalance { get; init; } - public int FrozenBalance { get; init; } - public int TotalBalance { get; init; } -} diff --git a/Fengling.RiskControl.Application/Services/IOrderRiskValidationService.cs b/Fengling.RiskControl.Application/Services/IOrderRiskValidationService.cs deleted file mode 100644 index 86a0d7e..0000000 --- a/Fengling.RiskControl.Application/Services/IOrderRiskValidationService.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Fengling.RiskControl.Application.Services; - -public record OrderDiscountValidationRequest -{ - public long MemberId { get; init; } - public string OrderId { get; init; } = string.Empty; - public int DiscountAmount { get; init; } - public int OriginalAmount { get; init; } - public string DiscountType { get; init; } = string.Empty; -} - -public record OrderDiscountValidationResult -{ - public bool IsAllowed { get; init; } - public string Reason { get; init; } = string.Empty; - public int MaxDiscountAllowed { get; init; } - public RiskEvaluationResult? RiskDetails { get; init; } -} - -public interface IOrderRiskValidationService -{ - Task ValidateDiscountAsync(OrderDiscountValidationRequest request); -} diff --git a/Fengling.RiskControl.Application/Services/IRiskAlertService.cs b/Fengling.RiskControl.Application/Services/IRiskAlertService.cs deleted file mode 100644 index f2b2977..0000000 --- a/Fengling.RiskControl.Application/Services/IRiskAlertService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Fengling.RiskControl.Domain.Aggregates.RiskAlerts; - -namespace Fengling.RiskControl.Application.Services; - -public interface IRiskAlertService -{ - Task CreateAlertAsync(long memberId, string alertType, string description, - RiskAlertPriority priority = RiskAlertPriority.Medium); - Task ResolveAlertAsync(long alertId, string notes); - Task DismissAlertAsync(long alertId, string notes); - Task EscalateAlertAsync(long alertId); - Task> GetMemberAlertsAsync(long memberId); - Task> GetPendingAlertsAsync(); -} diff --git a/Fengling.RiskControl.Application/Services/IRiskEvaluationService.cs b/Fengling.RiskControl.Application/Services/IRiskEvaluationService.cs deleted file mode 100644 index 88defd5..0000000 --- a/Fengling.RiskControl.Application/Services/IRiskEvaluationService.cs +++ /dev/null @@ -1,37 +0,0 @@ -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 Context { get; init; } = new(); -} - -public record RiskEvaluationResult -{ - public int TotalScore { get; init; } - public RiskLevel RiskLevel { get; init; } - public RiskRuleAction RecommendedAction { get; init; } - public List 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 EvaluateRiskAsync(RiskEvaluationRequest request); - Task IsAllowedAsync(RiskEvaluationRequest request); -} diff --git a/Fengling.RiskControl.Application/Services/LotteryService.cs b/Fengling.RiskControl.Application/Services/LotteryService.cs deleted file mode 100644 index f52c6e0..0000000 --- a/Fengling.RiskControl.Application/Services/LotteryService.cs +++ /dev/null @@ -1,96 +0,0 @@ -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 const double WIN_PROBABILITY = 0.3; - private const int MIN_WIN_MULTIPLIER = 2; - private const int MAX_WIN_MULTIPLIER = 10; - - 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 ParticipateAsync(LotteryParticipationRequest request) - { - var riskResult = await _riskService.EvaluateRiskAsync(new RiskEvaluationRequest - { - MemberId = request.MemberId, - EntityType = "lottery", - EntityId = request.ActivityType, - ActionType = "execute", - Context = new Dictionary - { - ["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() < WIN_PROBABILITY; - var winAmount = isWin ? stakePoints * random.Next(MIN_WIN_MULTIPLIER, MAX_WIN_MULTIPLIER + 1) : 0; - return (winAmount, isWin); - } - - public Task GetActivityAsync(long activityId) - { - return _activityRepository.GetByIdAsync(activityId); - } - - public Task> GetMemberActivitiesAsync(long memberId) - { - return _activityRepository.GetByMemberIdAsync(memberId); - } -} diff --git a/Fengling.RiskControl.Application/Services/MemberIntegrationService.cs b/Fengling.RiskControl.Application/Services/MemberIntegrationService.cs deleted file mode 100644 index 4203b2d..0000000 --- a/Fengling.RiskControl.Application/Services/MemberIntegrationService.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Net.Http.Json; -using Microsoft.Extensions.Logging; - -namespace Fengling.RiskControl.Application.Services; - -public class MemberIntegrationService : IMemberIntegrationService -{ - private readonly HttpClient _httpClient; - private readonly ILogger _logger; - - public MemberIntegrationService(HttpClient httpClient, ILogger logger) - { - _httpClient = httpClient; - _logger = logger; - } - - public async Task GetMemberPointsBalanceAsync(long memberId) - { - try - { - var response = await _httpClient.GetAsync($"/api/v1/members/{memberId}/points/balance"); - if (!response.IsSuccessStatusCode) - { - _logger.LogWarning("Failed to get points balance for member {MemberId}: {StatusCode}", - memberId, response.StatusCode); - return 0; - } - - var result = await response.Content.ReadFromJsonAsync(); - return result?.AvailableBalance ?? 0; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting points balance for member {MemberId}", memberId); - return 0; - } - } - - public async Task DeductPointsAsync(long memberId, int points, string reason) - { - try - { - var response = await _httpClient.PostAsJsonAsync($"/api/v1/members/{memberId}/points/deduct", new - { - Points = points, - Reason = reason - }); - - return response.IsSuccessStatusCode; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error deducting points for member {MemberId}", memberId); - return false; - } - } - - public async Task AddPointsAsync(long memberId, int points, string reason) - { - try - { - var response = await _httpClient.PostAsJsonAsync($"/api/v1/members/{memberId}/points/add", new - { - Points = points, - Reason = reason - }); - - return response.IsSuccessStatusCode; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error adding points for member {MemberId}", memberId); - return false; - } - } - - public async Task GetMemberInfoAsync(long memberId) - { - try - { - var response = await _httpClient.GetAsync($"/api/v1/members/{memberId}"); - if (!response.IsSuccessStatusCode) - return null; - - return await response.Content.ReadFromJsonAsync(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting member info for {MemberId}", memberId); - return null; - } - } -} diff --git a/Fengling.RiskControl.Application/Services/OrderRiskValidationService.cs b/Fengling.RiskControl.Application/Services/OrderRiskValidationService.cs deleted file mode 100644 index a067ebb..0000000 --- a/Fengling.RiskControl.Application/Services/OrderRiskValidationService.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Fengling.RiskControl.Domain.Aggregates.RiskScores; - -namespace Fengling.RiskControl.Application.Services; - -public class OrderRiskValidationService : IOrderRiskValidationService -{ - private readonly IRiskEvaluationService _riskService; - private readonly IMemberIntegrationService _memberService; - - public OrderRiskValidationService( - IRiskEvaluationService riskService, - IMemberIntegrationService memberService) - { - _riskService = riskService; - _memberService = memberService; - } - - public async Task ValidateDiscountAsync(OrderDiscountValidationRequest request) - { - if (request.OriginalAmount <= 0) - { - return new OrderDiscountValidationResult - { - IsAllowed = false, - Reason = "订单金额必须大于0", - MaxDiscountAllowed = 0 - }; - } - - var discountRate = (double)request.DiscountAmount / request.OriginalAmount; - - var riskResult = await _riskService.EvaluateRiskAsync(new RiskEvaluationRequest - { - MemberId = request.MemberId, - EntityType = "order_discount", - EntityId = request.OrderId, - ActionType = "apply_discount", - Context = new Dictionary - { - ["discountAmount"] = request.DiscountAmount, - ["originalAmount"] = request.OriginalAmount, - ["discountRate"] = discountRate, - ["discountType"] = request.DiscountType - } - }); - - if (riskResult.Blocked) - { - return new OrderDiscountValidationResult - { - IsAllowed = false, - Reason = riskResult.Message, - MaxDiscountAllowed = 0, - RiskDetails = riskResult - }; - } - - var maxDiscount = riskResult.RiskLevel switch - { - RiskLevel.Low => request.OriginalAmount / 2, - RiskLevel.Medium => request.OriginalAmount / 5, - RiskLevel.High => 0, - _ => 0 - }; - - return new OrderDiscountValidationResult - { - IsAllowed = request.DiscountAmount <= maxDiscount, - Reason = request.DiscountAmount <= maxDiscount ? "折扣申请通过" : $"超过最大允许折扣: {maxDiscount}", - MaxDiscountAllowed = maxDiscount, - RiskDetails = riskResult - }; - } -} diff --git a/Fengling.RiskControl.Application/Services/RiskAlertService.cs b/Fengling.RiskControl.Application/Services/RiskAlertService.cs deleted file mode 100644 index fc3031a..0000000 --- a/Fengling.RiskControl.Application/Services/RiskAlertService.cs +++ /dev/null @@ -1,73 +0,0 @@ -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 CreateAlertAsync(long memberId, string alertType, string description, - RiskAlertPriority priority = RiskAlertPriority.Medium) - { - var alert = RiskAlert.Create(memberId, alertType, description, priority); - await _alertRepository.AddAsync(alert); - - return alert; - } - - public async Task 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 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 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> GetMemberAlertsAsync(long memberId) - { - return _alertRepository.GetByMemberIdAsync(memberId); - } - - public Task> GetPendingAlertsAsync() - { - return _alertRepository.GetPendingAlertsAsync(); - } -} diff --git a/Fengling.RiskControl.Application/Services/RiskEvaluationService.cs b/Fengling.RiskControl.Application/Services/RiskEvaluationService.cs deleted file mode 100644 index 8457aa4..0000000 --- a/Fengling.RiskControl.Application/Services/RiskEvaluationService.cs +++ /dev/null @@ -1,153 +0,0 @@ -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 const int HIGH_THRESHOLD = 70; - private const int MEDIUM_THRESHOLD = 30; - private const int BLACKLIST_POINTS = 100; - - 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 EvaluateRiskAsync(RiskEvaluationRequest request) - { - var rules = await _ruleRepository.GetActiveRulesAsync(); - var factors = new List(); - 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 IsAllowedAsync(RiskEvaluationRequest request) - { - var result = await EvaluateRiskAsync(request); - return !result.Blocked; - } - - private async Task 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 EvaluateFrequencyLimitAsync(RiskRule rule, RiskEvaluationRequest request) - { - // Frequency limit checking requires additional repository method - // Implement when ILotteryActivityRepository.GetLotteryCountByMemberAndTypeAsync is available - return null; - } - - private async Task EvaluateAmountLimitAsync(RiskRule rule, RiskEvaluationRequest request) - { - var config = rule.GetConfig(); - 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(); - if (blacklist.MemberIds.Contains(request.MemberId)) - { - return new RiskFactorResult - { - FactorType = "blacklist", - Points = BLACKLIST_POINTS, - Description = "用户在黑名单中", - RuleName = rule.Name - }; - } - return null; - } - - private RiskLevel DetermineRiskLevel(int score) - { - return score >= HIGH_THRESHOLD ? RiskLevel.High : - score >= MEDIUM_THRESHOLD ? 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 MemberIds { get; set; } = new(); -} diff --git a/Fengling.RiskControl.Application/Validators/EvaluateRiskCommandValidator.cs b/Fengling.RiskControl.Application/Validators/EvaluateRiskCommandValidator.cs deleted file mode 100644 index 6230099..0000000 --- a/Fengling.RiskControl.Application/Validators/EvaluateRiskCommandValidator.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Fengling.RiskControl.Application.Commands; -using FluentValidation; - -namespace Fengling.RiskControl.Application.Validators; - -public class EvaluateRiskCommandValidator : AbstractValidator -{ - 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); - } -} diff --git a/Fengling.RiskControl.Infrastructure/DesignTimeRiskControlDbContextFactory.cs b/Fengling.RiskControl.Infrastructure/DesignTimeRiskControlDbContextFactory.cs deleted file mode 100644 index c293da8..0000000 --- a/Fengling.RiskControl.Infrastructure/DesignTimeRiskControlDbContextFactory.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Design; -using Microsoft.Extensions.Logging; - -namespace Fengling.RiskControl.Infrastructure; - -public class DesignTimeRiskControlDbContextFactory : IDesignTimeDbContextFactory -{ - private readonly ILoggerFactory _loggerFactory; - - public DesignTimeRiskControlDbContextFactory(ILoggerFactory loggerFactory) - { - _loggerFactory = loggerFactory; - } - - public RiskControlDbContext CreateDbContext(string[] args) - { - var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseNpgsql("Host=localhost;Database=RiskControl;Username=postgres;Password=postgres"); - optionsBuilder.UseLoggerFactory(_loggerFactory); - return new RiskControlDbContext(optionsBuilder.Options); - } -} diff --git a/Fengling.RiskControl.Infrastructure/Fengling.RiskControl.Infrastructure.csproj b/Fengling.RiskControl.Infrastructure/Fengling.RiskControl.Infrastructure.csproj deleted file mode 100644 index 27b96ce..0000000 --- a/Fengling.RiskControl.Infrastructure/Fengling.RiskControl.Infrastructure.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - net10.0 - enable - enable - - - - - - - - - - - diff --git a/Fengling.RiskControl.Infrastructure/Repositories/LotteryActivityRepository.cs b/Fengling.RiskControl.Infrastructure/Repositories/LotteryActivityRepository.cs deleted file mode 100644 index 3b9a4ef..0000000 --- a/Fengling.RiskControl.Infrastructure/Repositories/LotteryActivityRepository.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Fengling.RiskControl.Domain.Aggregates.LotteryActivities; -using Fengling.RiskControl.Domain.Repositories; - -namespace Fengling.RiskControl.Infrastructure.Repositories; - -public class LotteryActivityRepository : ILotteryActivityRepository -{ - private readonly RiskControlDbContext _context; - - public LotteryActivityRepository(RiskControlDbContext context) - { - _context = context; - } - - public async Task GetByIdAsync(long id) - { - return await _context.LotteryActivities.FindAsync(id); - } - - public async Task> GetByMemberIdAsync(long memberId) - { - return await _context.LotteryActivities - .Where(l => l.MemberId == memberId) - .OrderByDescending(l => l.CreatedAt) - .ToListAsync(); - } - - public async Task> GetRecentByMemberIdAsync(long memberId, int count) - { - return await _context.LotteryActivities - .Where(l => l.MemberId == memberId) - .OrderByDescending(l => l.CreatedAt) - .Take(count) - .ToListAsync(); - } - - public async Task AddAsync(LotteryActivity activity) - { - await _context.LotteryActivities.AddAsync(activity); - } - - public async Task UpdateAsync(LotteryActivity activity) - { - _context.LotteryActivities.Update(activity); - await Task.CompletedTask; - } - - public async Task DeleteAsync(LotteryActivity activity) - { - _context.LotteryActivities.Remove(activity); - await Task.CompletedTask; - } -} diff --git a/Fengling.RiskControl.Infrastructure/Repositories/RiskAlertRepository.cs b/Fengling.RiskControl.Infrastructure/Repositories/RiskAlertRepository.cs deleted file mode 100644 index 559a917..0000000 --- a/Fengling.RiskControl.Infrastructure/Repositories/RiskAlertRepository.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Fengling.RiskControl.Domain.Aggregates.RiskAlerts; -using Fengling.RiskControl.Domain.Repositories; - -namespace Fengling.RiskControl.Infrastructure.Repositories; - -public class RiskAlertRepository : IRiskAlertRepository -{ - private readonly RiskControlDbContext _context; - - public RiskAlertRepository(RiskControlDbContext context) - { - _context = context; - } - - public async Task GetByIdAsync(long id) - { - return await _context.RiskAlerts.FindAsync(id); - } - - public async Task> GetByMemberIdAsync(long memberId) - { - return await _context.RiskAlerts - .Where(a => a.MemberId == memberId) - .ToListAsync(); - } - - public async Task> GetPendingAlertsAsync() - { - return await _context.RiskAlerts - .Where(a => a.Status == RiskAlertStatus.Pending) - .OrderByDescending(a => a.Priority) - .ToListAsync(); - } - - public async Task> GetAlertsByPriorityAsync(RiskAlertPriority priority) - { - return await _context.RiskAlerts - .Where(a => a.Priority == priority) - .ToListAsync(); - } - - public async Task AddAsync(RiskAlert alert) - { - await _context.RiskAlerts.AddAsync(alert); - } - - public async Task UpdateAsync(RiskAlert alert) - { - _context.RiskAlerts.Update(alert); - await Task.CompletedTask; - } - - public async Task DeleteAsync(RiskAlert alert) - { - _context.RiskAlerts.Remove(alert); - await Task.CompletedTask; - } -} diff --git a/Fengling.RiskControl.Infrastructure/Repositories/RiskRuleRepository.cs b/Fengling.RiskControl.Infrastructure/Repositories/RiskRuleRepository.cs deleted file mode 100644 index cb96f37..0000000 --- a/Fengling.RiskControl.Infrastructure/Repositories/RiskRuleRepository.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Fengling.RiskControl.Domain.Aggregates.RiskRules; -using Fengling.RiskControl.Domain.Repositories; - -namespace Fengling.RiskControl.Infrastructure.Repositories; - -public class RiskRuleRepository : IRiskRuleRepository -{ - private readonly RiskControlDbContext _context; - - public RiskRuleRepository(RiskControlDbContext context) - { - _context = context; - } - - public async Task GetByIdAsync(long id) - { - return await _context.RiskRules.FindAsync(id); - } - - public async Task> GetActiveRulesAsync() - { - return await _context.RiskRules - .Where(r => r.IsActive) - .OrderByDescending(r => r.Priority) - .ToListAsync(); - } - - public async Task> GetRulesByTypeAsync(RiskRuleType type) - { - return await _context.RiskRules - .Where(r => r.RuleType == type && r.IsActive) - .ToListAsync(); - } - - public async Task> GetRulesForEvaluationAsync(string entityType, string actionType) - { - return await _context.RiskRules - .Where(r => r.IsActive) - .ToListAsync(); - } - - public async Task AddAsync(RiskRule rule) - { - await _context.RiskRules.AddAsync(rule); - } - - public async Task UpdateAsync(RiskRule rule) - { - _context.RiskRules.Update(rule); - await Task.CompletedTask; - } - - public async Task DeleteAsync(RiskRule rule) - { - _context.RiskRules.Remove(rule); - await Task.CompletedTask; - } -} diff --git a/Fengling.RiskControl.Infrastructure/Repositories/RiskScoreRepository.cs b/Fengling.RiskControl.Infrastructure/Repositories/RiskScoreRepository.cs deleted file mode 100644 index ed8c2aa..0000000 --- a/Fengling.RiskControl.Infrastructure/Repositories/RiskScoreRepository.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Fengling.RiskControl.Domain.Aggregates.RiskScores; -using Fengling.RiskControl.Domain.Repositories; - -namespace Fengling.RiskControl.Infrastructure.Repositories; - -public class RiskScoreRepository : IRiskScoreRepository -{ - private readonly RiskControlDbContext _context; - - public RiskScoreRepository(RiskControlDbContext context) - { - _context = context; - } - - public async Task GetByIdAsync(long id) - { - return await _context.RiskScores.FindAsync(id); - } - - public async Task GetByMemberAndEntityAsync(long memberId, string entityType, string entityId) - { - return await _context.RiskScores - .FirstOrDefaultAsync(s => s.MemberId == memberId && s.EntityType == entityType && s.EntityId == entityId); - } - - public async Task GetActiveByMemberAndEntityTypeAsync(long memberId, string entityType) - { - return await _context.RiskScores - .Where(s => s.MemberId == memberId && s.EntityType == entityType) - .OrderByDescending(s => s.CreatedAt) - .FirstOrDefaultAsync(); - } - - public async Task> GetByMemberIdAsync(long memberId) - { - return await _context.RiskScores - .Where(s => s.MemberId == memberId) - .ToListAsync(); - } - - public async Task AddAsync(RiskScore score) - { - await _context.RiskScores.AddAsync(score); - } - - public async Task UpdateAsync(RiskScore score) - { - _context.RiskScores.Update(score); - await Task.CompletedTask; - } - - public async Task DeleteAsync(RiskScore score) - { - _context.RiskScores.Remove(score); - await Task.CompletedTask; - } -} diff --git a/Fengling.RiskControl.Infrastructure/RiskControlDbContext.cs b/Fengling.RiskControl.Infrastructure/RiskControlDbContext.cs deleted file mode 100644 index 167f291..0000000 --- a/Fengling.RiskControl.Infrastructure/RiskControlDbContext.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using NetCorePal.Extensions.Domain; -using Fengling.RiskControl.Domain.Aggregates.RiskRules; -using Fengling.RiskControl.Domain.Aggregates.RiskScores; -using Fengling.RiskControl.Domain.Aggregates.RiskAlerts; -using Fengling.RiskControl.Domain.Aggregates.LotteryActivities; -using Fengling.RiskControl.Infrastructure.SeedData; - -namespace Fengling.RiskControl.Infrastructure; - -public class RiskControlDbContext : DbContext -{ - public DbSet RiskRules => Set(); - public DbSet RiskScores => Set(); - public DbSet RiskAlerts => Set(); - public DbSet LotteryActivities => Set(); - - private RiskControlDbContext() { } - - public RiskControlDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity(builder => - { - builder.ToTable("rc_risk_rules"); - builder.HasKey(r => r.Id); - builder.Property(r => r.Name).HasMaxLength(100).IsRequired(); - builder.Property(r => r.Description).HasMaxLength(500); - builder.Property(r => r.ConfigJson).HasColumnName("config_json"); - builder.Property(r => r.IsActive).HasDefaultValue(true); - }); - - modelBuilder.Entity(builder => - { - builder.ToTable("rc_risk_scores"); - builder.HasKey(s => s.Id); - builder.Property(s => s.MemberId).IsRequired(); - builder.Property(s => s.EntityType).HasMaxLength(50).IsRequired(); - builder.Property(s => s.EntityId).HasMaxLength(100).IsRequired(); - }); - - modelBuilder.Entity(builder => - { - builder.ToTable("rc_risk_alerts"); - builder.HasKey(a => a.Id); - builder.Property(a => a.MemberId).IsRequired(); - builder.Property(a => a.AlertType).HasMaxLength(50).IsRequired(); - builder.Property(a => a.Description).HasMaxLength(500); - }); - - modelBuilder.Entity(builder => - { - builder.ToTable("rc_lottery_activities"); - builder.HasKey(l => l.Id); - builder.Property(l => l.MemberId).IsRequired(); - builder.Property(l => l.ActivityType).HasMaxLength(50).IsRequired(); - builder.Property(l => l.IpAddress).HasMaxLength(50); - builder.Property(l => l.DeviceId).HasMaxLength(100); - }); - } - - public void SeedData() - { - if (!RiskRules.Any()) - { - RiskRules.AddRange(RiskControlSeedData.GetDefaultRules()); - SaveChanges(); - } - } -} diff --git a/Fengling.RiskControl.Infrastructure/SeedData/RiskControlSeedData.cs b/Fengling.RiskControl.Infrastructure/SeedData/RiskControlSeedData.cs deleted file mode 100644 index 3bc4809..0000000 --- a/Fengling.RiskControl.Infrastructure/SeedData/RiskControlSeedData.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Fengling.RiskControl.Domain.Aggregates.RiskRules; - -namespace Fengling.RiskControl.Infrastructure.SeedData; - -public static class RiskControlSeedData -{ - public static IEnumerable GetDefaultRules() - { - yield return RiskRule.Create( - "高频抽奖限制", - "限制单日抽奖次数,防止沉迷", - RiskRuleType.FrequencyLimit, - RiskRuleAction.Block, - """{"maxCount": 10, "windowMinutes": 1440, "points": 30}""", - priority: 10); - - yield return RiskRule.Create( - "单次大额抽奖限制", - "单次抽奖投入不能超过1000积分", - RiskRuleType.AmountLimit, - RiskRuleAction.Block, - """{"maxAmount": 1000, "points": 40}""", - priority: 9); - - yield return RiskRule.Create( - "设备异常检测", - "同一设备频繁切换账号", - RiskRuleType.DeviceFingerprint, - RiskRuleAction.RequireVerification, - """{"maxAccountsPerDevice": 3, "windowMinutes": 60, "points": 50}""", - priority: 8); - - yield return RiskRule.Create( - "IP地址异常", - "同一IP短时间内大量请求", - RiskRuleType.VelocityCheck, - RiskRuleAction.RateLimit, - """{"maxRequests": 100, "windowMinutes": 1, "points": 20}""", - priority: 7); - - yield return RiskRule.Create( - "行为模式异常", - "检测非正常用户行为模式", - RiskRuleType.BehaviorPattern, - RiskRuleAction.FlagForReview, - """{"patterns": ["rapid_clicks", "pattern_sequences"]}""", - priority: 5); - - yield return RiskRule.Create( - "VIP会员白名单", - "VIP会员享受较低风控等级", - RiskRuleType.Whitelist, - RiskRuleAction.Allow, - """{"vipLevels": [3, 4, 5]}""", - priority: 100); - } -} diff --git a/Fengling.RiskControl.Web/Endpoints/LotteryEndpoints.cs b/Fengling.RiskControl.Web/Endpoints/LotteryEndpoints.cs deleted file mode 100644 index 93f02f6..0000000 --- a/Fengling.RiskControl.Web/Endpoints/LotteryEndpoints.cs +++ /dev/null @@ -1,97 +0,0 @@ -using FastEndpoints; -using MediatR; -using Microsoft.AspNetCore.Mvc; -using Fengling.RiskControl.Application.Services; -using Fengling.RiskControl.Domain.Aggregates.LotteryActivities; - -namespace Fengling.RiskControl.Web.Endpoints; - -public class ParticipateLotteryEndpoint : Endpoint -{ - private readonly IMediator _mediator; - - public ParticipateLotteryEndpoint(IMediator mediator) - { - _mediator = mediator; - } - - public override void Configure() - { - Post("/api/v1/lottery/participate"); - Summary(s => { - s.Summary = "参与抽奖"; - s.Description = "会员参与抽奖活动,系统自动进行风险评估"; - }); - } - - public override async Task HandleAsync(ParticipateLotteryRequest req, CancellationToken ct) - { - var result = await _mediator.Send(new LotteryParticipationRequest - { - MemberId = req.MemberId, - ActivityType = req.ActivityType, - StakePoints = req.StakePoints, - IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown", - DeviceId = HttpContext.Request.Headers["X-Device-ID"].FirstOrDefault() - }, ct); - - Response = (LotteryParticipationResult)result!; - } -} - -public class ParticipateLotteryRequest -{ - public long MemberId { get; set; } - public string ActivityType { get; set; } = string.Empty; - public int StakePoints { get; set; } -} - -public class GetLotteryHistoryEndpoint : Endpoint> -{ - private readonly IMediator _mediator; - - public GetLotteryHistoryEndpoint(IMediator mediator) - { - _mediator = mediator; - } - - public override void Configure() - { - Get("/api/v1/lottery/history/{MemberId}"); - } - - public override async Task HandleAsync(GetLotteryHistoryRequest req, CancellationToken ct) - { - var activities = await _mediator.Send(new GetMemberLotteriesQuery { MemberId = req.MemberId }, ct); - Response = activities.Select(a => new LotteryActivityResponse - { - Id = a.Id, - ActivityType = a.ActivityType, - StakePoints = a.StakePoints, - WinAmount = a.WinAmount, - Status = a.Status.ToString(), - CreatedAt = a.CreatedAt - }); - } -} - -public class GetLotteryHistoryRequest -{ - [Microsoft.AspNetCore.Mvc.FromRoute] - public long MemberId { get; set; } -} - -public record GetMemberLotteriesQuery : IRequest> -{ - public long MemberId { get; init; } -} - -public record LotteryActivityResponse -{ - public long Id { get; init; } - public string ActivityType { get; init; } = string.Empty; - public int StakePoints { get; init; } - public int? WinAmount { get; init; } - public string Status { get; init; } = string.Empty; - public DateTime CreatedAt { get; init; } -} diff --git a/Fengling.RiskControl.Web/Endpoints/RiskAlertEndpoints.cs b/Fengling.RiskControl.Web/Endpoints/RiskAlertEndpoints.cs deleted file mode 100644 index 65cebbe..0000000 --- a/Fengling.RiskControl.Web/Endpoints/RiskAlertEndpoints.cs +++ /dev/null @@ -1,92 +0,0 @@ -using FastEndpoints; -using MediatR; -using Microsoft.AspNetCore.Mvc; -using Fengling.RiskControl.Domain.Aggregates.RiskAlerts; - -namespace Fengling.RiskControl.Web.Endpoints; - -public class GetPendingAlertsEndpoint : Endpoint> -{ - private readonly IMediator _mediator; - - public GetPendingAlertsEndpoint(IMediator mediator) - { - _mediator = mediator; - } - - public override void Configure() - { - Get("/api/v1/risk/alerts/pending"); - Roles("Admin"); - } - - public override async Task HandleAsync(EmptyRequest req, CancellationToken ct) - { - var alerts = await _mediator.Send(new GetPendingAlertsQuery(), ct); - Response = alerts.Select(a => new RiskAlertResponse - { - Id = a.Id, - MemberId = a.MemberId, - AlertType = a.AlertType, - Description = a.Description, - Priority = a.Priority.ToString(), - Status = a.Status.ToString(), - CreatedAt = a.CreatedAt - }); - } -} - -public class GetPendingAlertsQuery : IRequest> -{ -} - -public record RiskAlertResponse -{ - public long Id { get; init; } - public long MemberId { get; init; } - public string AlertType { get; init; } = string.Empty; - public string Description { get; init; } = string.Empty; - public string Priority { get; init; } = string.Empty; - public string Status { get; init; } = string.Empty; - public DateTime CreatedAt { get; init; } -} - -public class ResolveAlertEndpoint : Endpoint -{ - private readonly IMediator _mediator; - - public ResolveAlertEndpoint(IMediator mediator) - { - _mediator = mediator; - } - - public override void Configure() - { - Post("/api/v1/risk/alerts/{AlertId}/resolve"); - Roles("Admin"); - } - - public override async Task HandleAsync(ResolveAlertRequest req, CancellationToken ct) - { - var alert = await _mediator.Send(new ResolveAlertCommand - { - AlertId = req.AlertId, - Notes = req.Notes - }, ct); - - Response = alert; - } -} - -public class ResolveAlertRequest -{ - [Microsoft.AspNetCore.Mvc.FromRoute] - public long AlertId { get; set; } - public string Notes { get; set; } = string.Empty; -} - -public record ResolveAlertCommand : IRequest -{ - public long AlertId { get; init; } - public string Notes { get; init; } = string.Empty; -} diff --git a/Fengling.RiskControl.Web/Endpoints/RiskAssessmentEndpoints.cs b/Fengling.RiskControl.Web/Endpoints/RiskAssessmentEndpoints.cs deleted file mode 100644 index cb780b9..0000000 --- a/Fengling.RiskControl.Web/Endpoints/RiskAssessmentEndpoints.cs +++ /dev/null @@ -1,49 +0,0 @@ -using FastEndpoints; -using MediatR; -using Microsoft.AspNetCore.Mvc; -using Fengling.RiskControl.Application.Services; -using Fengling.RiskControl.Application.Commands; - -namespace Fengling.RiskControl.Web.Endpoints; - -public class EvaluateRiskEndpoint : Endpoint -{ - private readonly IMediator _mediator; - - public EvaluateRiskEndpoint(IMediator mediator) - { - _mediator = mediator; - } - - public override void Configure() - { - Post("/api/v1/risk/evaluate"); - Summary(s => { - s.Summary = "风险评估"; - s.Description = "对会员进行实时风险评估"; - }); - } - - public override async Task HandleAsync(EvaluateRiskRequest req, CancellationToken ct) - { - var result = await _mediator.Send(new EvaluateRiskCommand - { - MemberId = req.MemberId, - EntityType = req.EntityType, - EntityId = req.EntityId, - ActionType = req.ActionType, - Context = req.Context ?? new() - }, ct); - - Response = result; - } -} - -public class EvaluateRiskRequest -{ - public long MemberId { get; set; } - public string EntityType { get; set; } = string.Empty; - public string EntityId { get; set; } = string.Empty; - public string ActionType { get; set; } = string.Empty; - public Dictionary? Context { get; set; } -} diff --git a/Fengling.RiskControl.Web/Endpoints/RiskRuleEndpoints.cs b/Fengling.RiskControl.Web/Endpoints/RiskRuleEndpoints.cs deleted file mode 100644 index 9e8afce..0000000 --- a/Fengling.RiskControl.Web/Endpoints/RiskRuleEndpoints.cs +++ /dev/null @@ -1,86 +0,0 @@ -using FastEndpoints; -using MediatR; -using Microsoft.AspNetCore.Mvc; -using Fengling.RiskControl.Domain.Aggregates.RiskRules; - -namespace Fengling.RiskControl.Web.Endpoints; - -public class CreateRiskRuleEndpoint : Endpoint -{ - private readonly IMediator _mediator; - - public CreateRiskRuleEndpoint(IMediator mediator) - { - _mediator = mediator; - } - - public override void Configure() - { - Post("/api/v1/risk/rules"); - Roles("Admin"); - } - - public override async Task HandleAsync(CreateRiskRuleRequest req, CancellationToken ct) - { - if (!Enum.IsDefined(typeof(RiskRuleType), req.RuleType)) - { - ThrowError($"无效的规则类型: {req.RuleType}"); - } - if (!Enum.IsDefined(typeof(RiskRuleAction), req.Action)) - { - ThrowError($"无效的规则动作: {req.Action}"); - } - - var rule = await _mediator.Send(new CreateRiskRuleCommand - { - Name = req.Name, - Description = req.Description, - RuleType = (RiskRuleType)req.RuleType, - Action = (RiskRuleAction)req.Action, - ConfigJson = req.ConfigJson, - Priority = req.Priority - }, ct); - - Response = new RiskRuleResponse - { - Id = rule.Id, - Name = rule.Name, - Description = rule.Description, - RuleType = ((int)rule.RuleType).ToString(), - Action = ((int)rule.Action).ToString(), - IsActive = rule.IsActive, - CreatedAt = rule.CreatedAt - }; - } -} - -public class CreateRiskRuleRequest -{ - public string Name { get; set; } = string.Empty; - public string Description { get; set; } = string.Empty; - public int RuleType { get; set; } - public int Action { get; set; } - public string ConfigJson { get; set; } = string.Empty; - public int Priority { get; set; } -} - -public record CreateRiskRuleCommand : IRequest -{ - public string Name { get; init; } = string.Empty; - public string Description { get; init; } = string.Empty; - public RiskRuleType RuleType { get; init; } - public RiskRuleAction Action { get; init; } - public string ConfigJson { get; init; } = string.Empty; - public int Priority { get; init; } -} - -public record RiskRuleResponse -{ - public long Id { get; init; } - public string Name { get; init; } = string.Empty; - public string Description { get; init; } = string.Empty; - public string RuleType { get; init; } = string.Empty; - public string Action { get; init; } = string.Empty; - public bool IsActive { get; init; } - public DateTime CreatedAt { get; init; } -} diff --git a/Fengling.RiskControl.Web/Fengling.RiskControl.Web.csproj b/Fengling.RiskControl.Web/Fengling.RiskControl.Web.csproj deleted file mode 100644 index 695bffb..0000000 --- a/Fengling.RiskControl.Web/Fengling.RiskControl.Web.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - net10.0 - enable - enable - - - - - - - - - - - - - - diff --git a/Fengling.RiskControl.Web/Program.cs b/Fengling.RiskControl.Web/Program.cs deleted file mode 100644 index 8bf7123..0000000 --- a/Fengling.RiskControl.Web/Program.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Fengling.RiskControl.Infrastructure; -using Fengling.RiskControl.Infrastructure.Repositories; -using Fengling.RiskControl.Domain.Repositories; -using Fengling.RiskControl.Domain.Aggregates.RiskRules; -using Fengling.RiskControl.Domain.Aggregates.RiskScores; -using Fengling.RiskControl.Domain.Aggregates.RiskAlerts; -using Fengling.RiskControl.Domain.Aggregates.LotteryActivities; -using Fengling.RiskControl.Application.Services; -using FastEndpoints; -using FluentValidation; -using MediatR; -using Microsoft.EntityFrameworkCore; - -var builder = WebApplication.CreateBuilder(args); - -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddFastEndpoints(); -builder.Services.AddSwaggerGen(); - -builder.Services.AddDbContext(options => - options.UseNpgsql(builder.Configuration.GetConnectionString("RiskControl"))); - -builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly)); -builder.Services.AddValidatorsFromAssemblyContaining(lifetime: ServiceLifetime.Scoped); - -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); - -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); - -var app = builder.Build(); - -if (app.Environment.IsDevelopment()) -{ - app.UseSwagger(); - app.UseSwaggerUI(); -} - -app.UseFastEndpoints(); -app.UseHttpsRedirection(); - -app.Run();