diff --git a/Fengling.RiskControl.Web/Endpoints/LotteryEndpoints.cs b/Fengling.RiskControl.Web/Endpoints/LotteryEndpoints.cs new file mode 100644 index 0000000..9bf683c --- /dev/null +++ b/Fengling.RiskControl.Web/Endpoints/LotteryEndpoints.cs @@ -0,0 +1,100 @@ +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(), + DeviceId = HttpContext.Request.Headers["X-Device-ID"].FirstOrDefault() + }, ct); + + Response = (LotteryParticipationResult)result!; + } +} + +public class ParticipateLotteryRequest +{ + [Microsoft.AspNetCore.Mvc.FromBody] + public long MemberId { get; set; } + [Microsoft.AspNetCore.Mvc.FromBody] + public string ActivityType { get; set; } = string.Empty; + [Microsoft.AspNetCore.Mvc.FromBody] + 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 new file mode 100644 index 0000000..17033ad --- /dev/null +++ b/Fengling.RiskControl.Web/Endpoints/RiskAlertEndpoints.cs @@ -0,0 +1,102 @@ +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 = new RiskAlertResponse + { + Id = alert.Id, + MemberId = alert.MemberId, + AlertType = alert.AlertType, + Description = alert.Description, + Priority = alert.Priority.ToString(), + Status = alert.Status.ToString(), + CreatedAt = alert.CreatedAt + }; + } +} + +public class ResolveAlertRequest +{ + [Microsoft.AspNetCore.Mvc.FromRoute] + public long AlertId { get; set; } + [Microsoft.AspNetCore.Mvc.FromBody] + 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 new file mode 100644 index 0000000..38663c3 --- /dev/null +++ b/Fengling.RiskControl.Web/Endpoints/RiskAssessmentEndpoints.cs @@ -0,0 +1,54 @@ +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 +{ + [Microsoft.AspNetCore.Mvc.FromBody] + public long MemberId { get; set; } + [Microsoft.AspNetCore.Mvc.FromBody] + public string EntityType { get; set; } = string.Empty; + [Microsoft.AspNetCore.Mvc.FromBody] + public string EntityId { get; set; } = string.Empty; + [Microsoft.AspNetCore.Mvc.FromBody] + public string ActionType { get; set; } = string.Empty; + [Microsoft.AspNetCore.Mvc.FromBody] + public Dictionary? Context { get; set; } +} diff --git a/Fengling.RiskControl.Web/Endpoints/RiskRuleEndpoints.cs b/Fengling.RiskControl.Web/Endpoints/RiskRuleEndpoints.cs new file mode 100644 index 0000000..dc224d1 --- /dev/null +++ b/Fengling.RiskControl.Web/Endpoints/RiskRuleEndpoints.cs @@ -0,0 +1,83 @@ +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) + { + 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 +{ + [Microsoft.AspNetCore.Mvc.FromBody] + public string Name { get; set; } = string.Empty; + [Microsoft.AspNetCore.Mvc.FromBody] + public string Description { get; set; } = string.Empty; + [Microsoft.AspNetCore.Mvc.FromBody] + public int RuleType { get; set; } + [Microsoft.AspNetCore.Mvc.FromBody] + public int Action { get; set; } + [Microsoft.AspNetCore.Mvc.FromBody] + public string ConfigJson { get; set; } = string.Empty; + [Microsoft.AspNetCore.Mvc.FromBody] + 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/Program.cs b/Fengling.RiskControl.Web/Program.cs index 9aff7e9..8bf7123 100644 --- a/Fengling.RiskControl.Web/Program.cs +++ b/Fengling.RiskControl.Web/Program.cs @@ -1,7 +1,46 @@ +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(); -app.MapGet("/", () => "Fengling RiskControl Service"); +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseFastEndpoints(); +app.UseHttpsRedirection(); app.Run();