fix: rename typings.d.ts to typings.ts for proper module resolution

This commit is contained in:
Sam 2026-02-06 00:38:28 +08:00
parent 293209b1dc
commit 78ef001abc
30 changed files with 0 additions and 1605 deletions

View File

@ -1,35 +0,0 @@
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
});
}
}

View File

@ -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<LotteryCompletedEvent>
{
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);
}
}

View File

@ -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<Fengling.Member.Domain.Events.Member.MemberRegisteredEvent>
{
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<Fengling.Member.Domain.Events.Points.PointsChangedEvent>
{
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);
}
}
}

View File

@ -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<RiskAlertTriggeredEvent>
{
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);
}
}

View File

@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MediatR" />
<PackageReference Include="FluentValidation.AspNetCore" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Fengling.RiskControl.Domain\Fengling.RiskControl.Domain.csproj" />
<ProjectReference Include="..\Fengling.RiskControl.Infrastructure\Fengling.RiskControl.Infrastructure.csproj" />
<ProjectReference Include="..\..\Fengling.Member\src\Fengling.Member.Domain\Fengling.Member.Domain.csproj" />
</ItemGroup>
</Project>

View File

@ -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<LotteryParticipationResult> ParticipateAsync(LotteryParticipationRequest request);
Task<LotteryActivity?> GetActivityAsync(long activityId);
Task<IEnumerable<LotteryActivity>> GetMemberActivitiesAsync(long memberId);
}

View File

@ -1,25 +0,0 @@
namespace Fengling.RiskControl.Application.Services;
public interface IMemberIntegrationService
{
Task<int> GetMemberPointsBalanceAsync(long memberId);
Task<bool> DeductPointsAsync(long memberId, int points, string reason);
Task<bool> AddPointsAsync(long memberId, int points, string reason);
Task<MemberInfo?> 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; }
}

View File

@ -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<OrderDiscountValidationResult> ValidateDiscountAsync(OrderDiscountValidationRequest request);
}

View File

@ -1,14 +0,0 @@
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();
}

View File

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

View File

@ -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<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() < WIN_PROBABILITY;
var winAmount = isWin ? stakePoints * random.Next(MIN_WIN_MULTIPLIER, MAX_WIN_MULTIPLIER + 1) : 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);
}
}

View File

@ -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<MemberIntegrationService> _logger;
public MemberIntegrationService(HttpClient httpClient, ILogger<MemberIntegrationService> logger)
{
_httpClient = httpClient;
_logger = logger;
}
public async Task<int> 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<PointsBalanceResponse>();
return result?.AvailableBalance ?? 0;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting points balance for member {MemberId}", memberId);
return 0;
}
}
public async Task<bool> 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<bool> 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<MemberInfo?> GetMemberInfoAsync(long memberId)
{
try
{
var response = await _httpClient.GetAsync($"/api/v1/members/{memberId}");
if (!response.IsSuccessStatusCode)
return null;
return await response.Content.ReadFromJsonAsync<MemberInfo>();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting member info for {MemberId}", memberId);
return null;
}
}
}

View File

@ -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<OrderDiscountValidationResult> 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<string, object>
{
["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
};
}
}

View File

@ -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<RiskAlert> 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<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();
}
}

View File

@ -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<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)
{
// Frequency limit checking requires additional repository method
// Implement when ILotteryActivityRepository.GetLotteryCountByMemberAndTypeAsync is available
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 = 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<long> MemberIds { get; set; } = new();
}

View File

@ -1,15 +0,0 @@
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);
}
}

View File

@ -1,23 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Logging;
namespace Fengling.RiskControl.Infrastructure;
public class DesignTimeRiskControlDbContextFactory : IDesignTimeDbContextFactory<RiskControlDbContext>
{
private readonly ILoggerFactory _loggerFactory;
public DesignTimeRiskControlDbContextFactory(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
}
public RiskControlDbContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<RiskControlDbContext>();
optionsBuilder.UseNpgsql("Host=localhost;Database=RiskControl;Username=postgres;Password=postgres");
optionsBuilder.UseLoggerFactory(_loggerFactory);
return new RiskControlDbContext(optionsBuilder.Options);
}
}

View File

@ -1,16 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" />
<PackageReference Include="Microsoft.EntityFrameworkCore" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
<PackageReference Include="NetCorePal.Extensions.Repository.EntityFrameworkCore" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Fengling.RiskControl.Domain\Fengling.RiskControl.Domain.csproj" />
</ItemGroup>
</Project>

View File

@ -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<LotteryActivity?> GetByIdAsync(long id)
{
return await _context.LotteryActivities.FindAsync(id);
}
public async Task<IEnumerable<LotteryActivity>> GetByMemberIdAsync(long memberId)
{
return await _context.LotteryActivities
.Where(l => l.MemberId == memberId)
.OrderByDescending(l => l.CreatedAt)
.ToListAsync();
}
public async Task<IEnumerable<LotteryActivity>> 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;
}
}

View File

@ -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<RiskAlert?> GetByIdAsync(long id)
{
return await _context.RiskAlerts.FindAsync(id);
}
public async Task<IEnumerable<RiskAlert>> GetByMemberIdAsync(long memberId)
{
return await _context.RiskAlerts
.Where(a => a.MemberId == memberId)
.ToListAsync();
}
public async Task<IEnumerable<RiskAlert>> GetPendingAlertsAsync()
{
return await _context.RiskAlerts
.Where(a => a.Status == RiskAlertStatus.Pending)
.OrderByDescending(a => a.Priority)
.ToListAsync();
}
public async Task<IEnumerable<RiskAlert>> 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;
}
}

View File

@ -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<RiskRule?> GetByIdAsync(long id)
{
return await _context.RiskRules.FindAsync(id);
}
public async Task<IEnumerable<RiskRule>> GetActiveRulesAsync()
{
return await _context.RiskRules
.Where(r => r.IsActive)
.OrderByDescending(r => r.Priority)
.ToListAsync();
}
public async Task<IEnumerable<RiskRule>> GetRulesByTypeAsync(RiskRuleType type)
{
return await _context.RiskRules
.Where(r => r.RuleType == type && r.IsActive)
.ToListAsync();
}
public async Task<IEnumerable<RiskRule>> 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;
}
}

View File

@ -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<RiskScore?> GetByIdAsync(long id)
{
return await _context.RiskScores.FindAsync(id);
}
public async Task<RiskScore?> 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<RiskScore?> 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<IEnumerable<RiskScore>> 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;
}
}

View File

@ -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<RiskRule> RiskRules => Set<RiskRule>();
public DbSet<RiskScore> RiskScores => Set<RiskScore>();
public DbSet<RiskAlert> RiskAlerts => Set<RiskAlert>();
public DbSet<LotteryActivity> LotteryActivities => Set<LotteryActivity>();
private RiskControlDbContext() { }
public RiskControlDbContext(DbContextOptions<RiskControlDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<RiskRule>(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<RiskScore>(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<RiskAlert>(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<LotteryActivity>(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();
}
}
}

View File

@ -1,57 +0,0 @@
using Fengling.RiskControl.Domain.Aggregates.RiskRules;
namespace Fengling.RiskControl.Infrastructure.SeedData;
public static class RiskControlSeedData
{
public static IEnumerable<RiskRule> 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);
}
}

View File

@ -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<ParticipateLotteryRequest, LotteryParticipationResult>
{
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<GetLotteryHistoryRequest, IEnumerable<LotteryActivityResponse>>
{
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<IEnumerable<LotteryActivityResponse>>
{
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; }
}

View File

@ -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<EmptyRequest, IEnumerable<RiskAlertResponse>>
{
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<IEnumerable<RiskAlertResponse>>
{
}
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<ResolveAlertRequest, RiskAlertResponse>
{
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<RiskAlertResponse>
{
public long AlertId { get; init; }
public string Notes { get; init; } = string.Empty;
}

View File

@ -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<EvaluateRiskRequest, RiskEvaluationResult>
{
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<string, object>? Context { get; set; }
}

View File

@ -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<CreateRiskRuleRequest, RiskRuleResponse>
{
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<RiskRule>
{
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; }
}

View File

@ -1,19 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FastEndpoints" />
<PackageReference Include="FastEndpoints.Swagger" />
<PackageReference Include="Swashbuckle.AspNetCore" />
<PackageReference Include="DotNetCore.CAP" />
<PackageReference Include="DotNetCore.CAP.RabbitMQ" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Fengling.RiskControl.Domain\Fengling.RiskControl.Domain.csproj" />
<ProjectReference Include="..\Fengling.RiskControl.Infrastructure\Fengling.RiskControl.Infrastructure.csproj" />
<ProjectReference Include="..\Fengling.RiskControl.Application\Fengling.RiskControl.Application.csproj" />
</ItemGroup>
</Project>

View File

@ -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<RiskControlDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("RiskControl")));
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
builder.Services.AddValidatorsFromAssemblyContaining<Program>(lifetime: ServiceLifetime.Scoped);
builder.Services.AddScoped<IRiskRuleRepository, RiskRuleRepository>();
builder.Services.AddScoped<IRiskScoreRepository, RiskScoreRepository>();
builder.Services.AddScoped<IRiskAlertRepository, RiskAlertRepository>();
builder.Services.AddScoped<ILotteryActivityRepository, LotteryActivityRepository>();
builder.Services.AddScoped<IRiskEvaluationService, RiskEvaluationService>();
builder.Services.AddScoped<ILotteryService, LotteryService>();
builder.Services.AddScoped<IRiskAlertService, RiskAlertService>();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseFastEndpoints();
app.UseHttpsRedirection();
app.Run();