feat(risk-control): add FastEndpoints APIs

This commit is contained in:
Sam 2026-02-05 15:14:29 +08:00
parent 2b85e2d115
commit 1ac028643f
5 changed files with 379 additions and 1 deletions

View File

@ -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<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(),
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<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

@ -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<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<RiskAlert>>
{
}
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 = 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<RiskAlert>
{
public long AlertId { get; init; }
public string Notes { get; init; } = string.Empty;
}

View File

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

View File

@ -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<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)
{
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<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,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<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();
app.MapGet("/", () => "Fengling RiskControl Service");
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseFastEndpoints();
app.UseHttpsRedirection();
app.Run();