refactor: major project restructuring and cleanup

Changes:

- Remove deprecated Fengling.Activity and YarpGateway.Admin projects

- Add points processing services with distributed lock support

- Update Vben frontend with gateway management pages

- Add gateway config controller and database listener

- Update routing to use header-mixed-nav layout

- Add comprehensive test suites for Member services

- Add YarpGateway integration tests

- Update package versions in Directory.Packages.props

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
sam 2026-02-15 10:34:07 +08:00
parent f14bf019f1
commit 9516e1cd93
2 changed files with 31 additions and 51 deletions

View File

@ -39,6 +39,7 @@ builder.Services.AddScoped<IRoleRepository, RoleRepository>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<ITenantService, TenantService>();
builder.Services.AddScoped<IRoleService, RoleService>();
builder.Services.AddScoped<IGatewayService, GatewayService>();
builder.Services.AddOpenIddict()
.AddCore(options =>
@ -79,6 +80,7 @@ builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new() { Title = "Fengling.Console API", Version = "v1" });
c.CustomSchemaIds(type => type.FullName); // Use full name to avoid conflicts with YarpGateway DTOs
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
if (File.Exists(xmlPath))

View File

@ -24,21 +24,19 @@ public interface IGatewayService
public class GatewayService : IGatewayService
{
private readonly IDbContextFactory<GatewayDbContext> _dbContextFactory;
private readonly GatewayDbContext _dbContext;
private readonly ILogger<GatewayService> _logger;
public GatewayService(IDbContextFactory<GatewayDbContext> dbContextFactory, ILogger<GatewayService> logger)
public GatewayService(GatewayDbContext dbContext, ILogger<GatewayService> logger)
{
_dbContextFactory = dbContextFactory;
_dbContext = dbContext;
_logger = logger;
}
public async Task<GatewayStatisticsDto> GetStatisticsAsync()
{
await using var db = await _dbContextFactory.CreateDbContextAsync();
var routes = await db.TenantRoutes.Where(r => !r.IsDeleted).ToListAsync();
var instances = await db.ServiceInstances.Where(i => !i.IsDeleted).ToListAsync();
var routes = await _dbContext.TenantRoutes.Where(r => !r.IsDeleted).ToListAsync();
var instances = await _dbContext.ServiceInstances.Where(i => !i.IsDeleted).ToListAsync();
return new GatewayStatisticsDto
{
@ -57,9 +55,7 @@ public class GatewayService : IGatewayService
public async Task<List<GatewayServiceDto>> GetServicesAsync(bool globalOnly = false, string? tenantCode = null)
{
await using var db = await _dbContextFactory.CreateDbContextAsync();
var query = db.TenantRoutes.Where(r => !r.IsDeleted);
var query = _dbContext.TenantRoutes.Where(r => !r.IsDeleted);
if (globalOnly)
query = query.Where(r => r.IsGlobal);
@ -69,7 +65,7 @@ public class GatewayService : IGatewayService
var routes = await query.OrderByDescending(r => r.CreatedTime).ToListAsync();
var clusters = routes.Select(r => r.ClusterId).Distinct().ToList();
var instances = await db.ServiceInstances
var instances = await _dbContext.ServiceInstances
.Where(i => clusters.Contains(i.ClusterId) && !i.IsDeleted)
.GroupBy(i => i.ClusterId)
.ToDictionaryAsync(g => g.Key, g => g.Count());
@ -79,9 +75,7 @@ public class GatewayService : IGatewayService
public async Task<GatewayServiceDto?> GetServiceAsync(string serviceName, string? tenantCode = null)
{
await using var db = await _dbContextFactory.CreateDbContextAsync();
var route = await db.TenantRoutes
var route = await _dbContext.TenantRoutes
.FirstOrDefaultAsync(r =>
r.ServiceName == serviceName &&
r.IsDeleted == false &&
@ -89,7 +83,7 @@ public class GatewayService : IGatewayService
if (route == null) return null;
var instances = await db.ServiceInstances
var instances = await _dbContext.ServiceInstances
.CountAsync(i => i.ClusterId == route.ClusterId && !i.IsDeleted);
return MapToServiceDto(route, instances);
@ -97,8 +91,6 @@ public class GatewayService : IGatewayService
public async Task<GatewayServiceDto> RegisterServiceAsync(CreateGatewayServiceDto dto)
{
await using var db = await _dbContextFactory.CreateDbContextAsync();
var clusterId = $"{dto.ServicePrefix}-service";
var pathPattern = $"/{dto.ServicePrefix}/{dto.Version}/{{**path}}";
var destinationId = string.IsNullOrEmpty(dto.DestinationId)
@ -106,7 +98,7 @@ public class GatewayService : IGatewayService
: dto.DestinationId;
// Check if route already exists
var existingRoute = await db.TenantRoutes
var existingRoute = await _dbContext.TenantRoutes
.FirstOrDefaultAsync(r =>
r.ServiceName == dto.ServicePrefix &&
r.IsGlobal == dto.IsGlobal &&
@ -130,7 +122,7 @@ public class GatewayService : IGatewayService
Status = 1,
CreatedTime = DateTime.UtcNow
};
await db.ServiceInstances.AddAsync(instance);
await _dbContext.ServiceInstances.AddAsync(instance);
// Add route
var routeId = instanceId + 1;
@ -146,9 +138,9 @@ public class GatewayService : IGatewayService
IsGlobal = dto.IsGlobal,
CreatedTime = DateTime.UtcNow
};
await db.TenantRoutes.AddAsync(route);
await _dbContext.TenantRoutes.AddAsync(route);
await db.SaveChangesAsync();
await _dbContext.SaveChangesAsync();
_logger.LogInformation("Registered service {Service} at {Address}", dto.ServicePrefix, dto.ServiceAddress);
@ -157,9 +149,7 @@ public class GatewayService : IGatewayService
public async Task<bool> UnregisterServiceAsync(string serviceName, string? tenantCode = null)
{
await using var db = await _dbContextFactory.CreateDbContextAsync();
var route = await db.TenantRoutes
var route = await _dbContext.TenantRoutes
.FirstOrDefaultAsync(r =>
r.ServiceName == serviceName &&
r.IsDeleted == false &&
@ -172,7 +162,7 @@ public class GatewayService : IGatewayService
route.UpdatedTime = DateTime.UtcNow;
// Soft delete instances
var instances = await db.ServiceInstances
var instances = await _dbContext.ServiceInstances
.Where(i => i.ClusterId == route.ClusterId && !i.IsDeleted)
.ToListAsync();
@ -182,7 +172,7 @@ public class GatewayService : IGatewayService
instance.UpdatedTime = DateTime.UtcNow;
}
await db.SaveChangesAsync();
await _dbContext.SaveChangesAsync();
_logger.LogInformation("Unregistered service {Service}", serviceName);
@ -191,9 +181,7 @@ public class GatewayService : IGatewayService
public async Task<List<GatewayRouteDto>> GetRoutesAsync(bool globalOnly = false)
{
await using var db = await _dbContextFactory.CreateDbContextAsync();
var query = db.TenantRoutes.Where(r => !r.IsDeleted);
var query = _dbContext.TenantRoutes.Where(r => !r.IsDeleted);
if (globalOnly)
query = query.Where(r => r.IsGlobal);
@ -201,7 +189,7 @@ public class GatewayService : IGatewayService
var routes = await query.OrderByDescending(r => r.Priority).ToListAsync();
var clusters = routes.Select(r => r.ClusterId).Distinct().ToList();
var instances = await db.ServiceInstances
var instances = await _dbContext.ServiceInstances
.Where(i => clusters.Contains(i.ClusterId) && !i.IsDeleted)
.GroupBy(i => i.ClusterId)
.ToDictionaryAsync(g => g.Key, g => g.Count());
@ -222,9 +210,7 @@ public class GatewayService : IGatewayService
public async Task<GatewayRouteDto> CreateRouteAsync(CreateGatewayRouteDto dto)
{
await using var db = await _dbContextFactory.CreateDbContextAsync();
var existing = await db.TenantRoutes
var existing = await _dbContext.TenantRoutes
.FirstOrDefaultAsync(r =>
r.ServiceName == dto.ServiceName &&
r.IsGlobal == dto.IsGlobal &&
@ -248,8 +234,8 @@ public class GatewayService : IGatewayService
CreatedTime = DateTime.UtcNow
};
await db.TenantRoutes.AddAsync(route);
await db.SaveChangesAsync();
await _dbContext.TenantRoutes.AddAsync(route);
await _dbContext.SaveChangesAsync();
return new GatewayRouteDto
{
@ -267,9 +253,7 @@ public class GatewayService : IGatewayService
public async Task<List<GatewayInstanceDto>> GetInstancesAsync(string clusterId)
{
await using var db = await _dbContextFactory.CreateDbContextAsync();
var instances = await db.ServiceInstances
var instances = await _dbContext.ServiceInstances
.Where(i => i.ClusterId == clusterId && !i.IsDeleted)
.OrderByDescending(i => i.Weight)
.ToListAsync();
@ -289,9 +273,7 @@ public class GatewayService : IGatewayService
public async Task<GatewayInstanceDto> AddInstanceAsync(CreateGatewayInstanceDto dto)
{
await using var db = await _dbContextFactory.CreateDbContextAsync();
var existing = await db.ServiceInstances
var existing = await _dbContext.ServiceInstances
.FirstOrDefaultAsync(i =>
i.ClusterId == dto.ClusterId &&
i.DestinationId == dto.DestinationId &&
@ -314,8 +296,8 @@ public class GatewayService : IGatewayService
CreatedTime = DateTime.UtcNow
};
await db.ServiceInstances.AddAsync(instance);
await db.SaveChangesAsync();
await _dbContext.ServiceInstances.AddAsync(instance);
await _dbContext.SaveChangesAsync();
return new GatewayInstanceDto
{
@ -332,29 +314,25 @@ public class GatewayService : IGatewayService
public async Task<bool> RemoveInstanceAsync(long instanceId)
{
await using var db = await _dbContextFactory.CreateDbContextAsync();
var instance = await db.ServiceInstances.FindAsync(instanceId);
var instance = await _dbContext.ServiceInstances.FindAsync(instanceId);
if (instance == null) return false;
instance.IsDeleted = true;
instance.UpdatedTime = DateTime.UtcNow;
await db.SaveChangesAsync();
await _dbContext.SaveChangesAsync();
return true;
}
public async Task<bool> UpdateInstanceWeightAsync(long instanceId, int weight)
{
await using var db = await _dbContextFactory.CreateDbContextAsync();
var instance = await db.ServiceInstances.FindAsync(instanceId);
var instance = await _dbContext.ServiceInstances.FindAsync(instanceId);
if (instance == null) return false;
instance.Weight = weight;
instance.UpdatedTime = DateTime.UtcNow;
await db.SaveChangesAsync();
await _dbContext.SaveChangesAsync();
return true;
}