using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using YarpGateway.Data; using YarpGateway.Models; namespace YarpGateway.Controllers; [ApiController] [Route("api/gateway/pending-services")] [Authorize] // 要求所有管理 API 都需要认证 public class PendingServicesController : ControllerBase { private readonly IDbContextFactory _dbContextFactory; private readonly ILogger _logger; public PendingServicesController( IDbContextFactory dbContextFactory, ILogger logger) { _dbContextFactory = dbContextFactory; _logger = logger; } [HttpGet] public async Task GetPendingServices( [FromQuery] int page = 1, [FromQuery] int pageSize = 10, [FromQuery] int? status = null) { await using var db = _dbContextFactory.CreateDbContext(); var query = db.PendingServiceDiscoveries.Where(p => !p.IsDeleted); if (status.HasValue) { query = query.Where(p => p.Status == status.Value); } var total = await query.CountAsync(); var items = await query .OrderByDescending(p => p.DiscoveredAt) .Skip((page - 1) * pageSize) .Take(pageSize) .Select(p => new { p.Id, p.K8sServiceName, p.K8sNamespace, p.K8sClusterIP, DiscoveredPorts = System.Text.Json.JsonSerializer.Deserialize>(p.DiscoveredPorts) ?? new List(), Labels = System.Text.Json.JsonSerializer.Deserialize>(p.Labels) ?? new Dictionary(), p.PodCount, Status = (PendingServiceStatus)p.Status, p.AssignedClusterId, p.AssignedBy, p.AssignedAt, p.DiscoveredAt }) .ToListAsync(); return Ok(new { items, total, page, pageSize }); } [HttpGet("{id}")] public async Task GetPendingService(long id) { await using var db = _dbContextFactory.CreateDbContext(); var service = await db.PendingServiceDiscoveries.FindAsync(id); if (service == null || service.IsDeleted) { return NotFound(new { message = "Pending service not found" }); } return Ok(new { service.Id, service.K8sServiceName, service.K8sNamespace, service.K8sClusterIP, DiscoveredPorts = System.Text.Json.JsonSerializer.Deserialize>(service.DiscoveredPorts) ?? new List(), Labels = System.Text.Json.JsonSerializer.Deserialize>(service.Labels) ?? new Dictionary(), service.PodCount, Status = (PendingServiceStatus)service.Status, service.AssignedClusterId, service.AssignedBy, service.AssignedAt, service.DiscoveredAt }); } [HttpPost("{id}/assign")] public async Task AssignService(long id, [FromBody] AssignServiceRequest request) { await using var db = _dbContextFactory.CreateDbContext(); var pendingService = await db.PendingServiceDiscoveries.FindAsync(id); if (pendingService == null || pendingService.IsDeleted) { return NotFound(new { message = "Pending service not found" }); } if (pendingService.Status != (int)PendingServiceStatus.Pending) { return BadRequest(new { message = $"Service is already {((PendingServiceStatus)pendingService.Status)}, cannot assign" }); } if (string.IsNullOrEmpty(request.ClusterId)) { return BadRequest(new { message = "ClusterId is required" }); } var existingCluster = await db.ServiceInstances .AnyAsync(i => i.ClusterId == request.ClusterId && !i.IsDeleted); if (!existingCluster) { return BadRequest(new { message = $"Cluster '{request.ClusterId}' does not exist. Please create the cluster first." }); } var discoveredPorts = System.Text.Json.JsonSerializer.Deserialize>(pendingService.DiscoveredPorts) ?? new List(); var primaryPort = discoveredPorts.FirstOrDefault() > 0 ? discoveredPorts.First() : 80; var instanceNumber = await db.ServiceInstances .CountAsync(i => i.ClusterId == request.ClusterId && !i.IsDeleted); var newInstance = new GwServiceInstance { ClusterId = request.ClusterId, DestinationId = $"{pendingService.K8sServiceName}-{instanceNumber + 1}", Address = $"http://{pendingService.K8sClusterIP}:{primaryPort}", Health = 1, Weight = 100, Status = 1, CreatedTime = DateTime.UtcNow, Version = 1 }; db.ServiceInstances.Add(newInstance); pendingService.Status = (int)PendingServiceStatus.Approved; pendingService.AssignedClusterId = request.ClusterId; pendingService.AssignedBy = "admin"; pendingService.AssignedAt = DateTime.UtcNow; pendingService.Version++; await db.SaveChangesAsync(); _logger.LogInformation("Service {ServiceName} assigned to cluster {ClusterId} by admin", pendingService.K8sServiceName, request.ClusterId); return Ok(new { success = true, message = $"Service '{pendingService.K8sServiceName}' assigned to cluster '{request.ClusterId}'", instanceId = newInstance.Id }); } [HttpPost("{id}/reject")] public async Task RejectService(long id) { await using var db = _dbContextFactory.CreateDbContext(); var pendingService = await db.PendingServiceDiscoveries.FindAsync(id); if (pendingService == null || pendingService.IsDeleted) { return NotFound(new { message = "Pending service not found" }); } if (pendingService.Status != (int)PendingServiceStatus.Pending) { return BadRequest(new { message = $"Service is already {((PendingServiceStatus)pendingService.Status)}, cannot reject" }); } pendingService.Status = (int)PendingServiceStatus.Rejected; pendingService.AssignedBy = "admin"; pendingService.AssignedAt = DateTime.UtcNow; pendingService.Version++; await db.SaveChangesAsync(); _logger.LogInformation("Service {ServiceName} rejected by admin", pendingService.K8sServiceName); return Ok(new { success = true, message = $"Service '{pendingService.K8sServiceName}' rejected" }); } [HttpGet("clusters")] public async Task GetClusters() { await using var db = _dbContextFactory.CreateDbContext(); var clusters = await db.ServiceInstances .Where(i => !i.IsDeleted) .GroupBy(i => i.ClusterId) .Select(g => new { ClusterId = g.Key, InstanceCount = g.Count(), HealthyCount = g.Count(i => i.Health == 1) }) .ToListAsync(); return Ok(clusters); } } public class AssignServiceRequest { public string ClusterId { get; set; } = string.Empty; }