IMPL-5: 实现 PendingConfigController API
All checks were successful
Build and Push Docker / build (push) Successful in 2m39s
All checks were successful
Build and Push Docker / build (push) Successful in 2m39s
- 创建 PendingConfigDtos.cs 包含所有 DTO 类 - 创建 PendingConfigController.cs 实现 CRUD 操作 - 支持列表查询(分页、筛选、搜索) - 支持确认配置(写入数据库 + 发送 NOTIFY) - 支持拒绝配置(从缓存移除) - 支持修改配置(更新缓存并标记状态) - 添加管理员权限控制 - 集成 Swagger XML 文档 - 在 Program.cs 中注册通知服务
This commit is contained in:
parent
0e04bb1690
commit
83085c6dea
@ -1,5 +1,6 @@
|
|||||||
global using OpenIddict.Validation.AspNetCore;
|
global using OpenIddict.Validation.AspNetCore;
|
||||||
global using Fengling.Console.Models.Dtos;
|
global using Fengling.Console.Models.Dtos;
|
||||||
global using Fengling.Console.Services;
|
global using Fengling.Console.Services;
|
||||||
|
global using Fengling.Platform.Infrastructure;
|
||||||
global using Microsoft.AspNetCore.Authorization;
|
global using Microsoft.AspNetCore.Authorization;
|
||||||
global using Microsoft.AspNetCore.Mvc;
|
global using Microsoft.AspNetCore.Mvc;
|
||||||
491
src/Controllers/PendingConfigController.cs
Normal file
491
src/Controllers/PendingConfigController.cs
Normal file
@ -0,0 +1,491 @@
|
|||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using Fengling.Console.Models.Dtos;
|
||||||
|
using Fengling.Console.Models.Entities;
|
||||||
|
using Fengling.Console.Models.K8s;
|
||||||
|
using Fengling.Platform.Domain.AggregatesModel.GatewayAggregate;
|
||||||
|
|
||||||
|
namespace Fengling.Console.Controllers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 待确认配置管理控制器
|
||||||
|
/// 提供从 K8s 服务发现或手动添加的待确认网关配置的确认、拒绝、修改等功能
|
||||||
|
/// </summary>
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/console/pending-configs")]
|
||||||
|
[Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)]
|
||||||
|
public class PendingConfigController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly PendingConfigCache _pendingConfigCache;
|
||||||
|
private readonly INotificationService _notificationService;
|
||||||
|
private readonly IClusterStore _clusterStore;
|
||||||
|
private readonly IRouteStore _routeStore;
|
||||||
|
private readonly ILogger<PendingConfigController> _logger;
|
||||||
|
|
||||||
|
public PendingConfigController(
|
||||||
|
PendingConfigCache pendingConfigCache,
|
||||||
|
INotificationService notificationService,
|
||||||
|
IClusterStore clusterStore,
|
||||||
|
IRouteStore routeStore,
|
||||||
|
ILogger<PendingConfigController> logger)
|
||||||
|
{
|
||||||
|
_pendingConfigCache = pendingConfigCache;
|
||||||
|
_notificationService = notificationService;
|
||||||
|
_clusterStore = clusterStore;
|
||||||
|
_routeStore = routeStore;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取待确认配置统计信息
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>待确认配置的统计数据,包括总数、新配置数、变更数</returns>
|
||||||
|
/// <response code="200">成功返回统计数据</response>
|
||||||
|
/// <response code="500">服务器内部错误</response>
|
||||||
|
[HttpGet("statistics")]
|
||||||
|
[Produces("application/json")]
|
||||||
|
[ProducesResponseType(typeof(PendingConfigStatisticsDto), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||||
|
public ActionResult<PendingConfigStatisticsDto> GetStatistics()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var allConfigs = _pendingConfigCache.GetAll().ToList();
|
||||||
|
var result = new PendingConfigStatisticsDto
|
||||||
|
{
|
||||||
|
TotalCount = allConfigs.Count,
|
||||||
|
NewCount = allConfigs.Count(c => c.IsNew),
|
||||||
|
ModifiedCount = allConfigs.Count(c => c.IsModified)
|
||||||
|
};
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error getting pending config statistics");
|
||||||
|
return StatusCode(500, new { message = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取待确认配置列表
|
||||||
|
/// 支持分页、按状态筛选和按服务名搜索
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">查询参数,包括分页、状态筛选和服务名搜索</param>
|
||||||
|
/// <returns>分页的待确认配置列表</returns>
|
||||||
|
/// <response code="200">成功返回配置列表</response>
|
||||||
|
/// <response code="500">服务器内部错误</response>
|
||||||
|
[HttpGet]
|
||||||
|
[Produces("application/json")]
|
||||||
|
[ProducesResponseType(typeof(PagedResultDto<PendingConfigDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||||
|
public ActionResult<PagedResultDto<PendingConfigDto>> GetList([FromQuery] PendingConfigListRequest request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var query = _pendingConfigCache.GetAll().AsEnumerable();
|
||||||
|
|
||||||
|
// 应用状态筛选
|
||||||
|
if (request.Status.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(c => c.Status == request.Status.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用服务名称搜索
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.ServiceName))
|
||||||
|
{
|
||||||
|
query = query.Where(c => c.ServiceName.Contains(request.ServiceName, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用租户编码筛选
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.TenantCode))
|
||||||
|
{
|
||||||
|
query = query.Where(c => c.TenantCode == request.TenantCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用来源筛选
|
||||||
|
if (request.Source.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(c => c.Source == request.Source.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var allItems = query.ToList();
|
||||||
|
var totalCount = allItems.Count;
|
||||||
|
|
||||||
|
// 应用分页
|
||||||
|
var items = allItems
|
||||||
|
.Skip((request.Page - 1) * request.PageSize)
|
||||||
|
.Take(request.PageSize)
|
||||||
|
.Select(MapToDto)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var result = new PagedResultDto<PendingConfigDto>
|
||||||
|
{
|
||||||
|
Items = items,
|
||||||
|
TotalCount = totalCount,
|
||||||
|
Page = request.Page,
|
||||||
|
PageSize = request.PageSize
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error getting pending config list");
|
||||||
|
return StatusCode(500, new { message = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取单个待确认配置详情
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">配置ID</param>
|
||||||
|
/// <returns>待确认配置的详细信息,包括 JSON 配置内容</returns>
|
||||||
|
/// <response code="200">成功返回配置详情</response>
|
||||||
|
/// <response code="404">配置不存在</response>
|
||||||
|
/// <response code="500">服务器内部错误</response>
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
[Produces("application/json")]
|
||||||
|
[ProducesResponseType(typeof(PendingConfigDto), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||||
|
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||||
|
public ActionResult<PendingConfigDto> GetDetail(string id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var config = FindConfigById(id);
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
return NotFound(new { message = $"Pending config {id} not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(MapToDto(config));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error getting pending config {ConfigId}", id);
|
||||||
|
return StatusCode(500, new { message = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 确认待确认配置
|
||||||
|
/// 将配置写入数据库(GwTenantRoute/GwCluster/GwDestination),发送 PostgreSQL NOTIFY 通知,并从缓存中移除
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">配置ID</param>
|
||||||
|
/// <param name="request">确认请求,包含可选的备注信息</param>
|
||||||
|
/// <returns>操作结果</returns>
|
||||||
|
/// <response code="200">成功确认配置</response>
|
||||||
|
/// <response code="404">配置不存在</response>
|
||||||
|
/// <response code="400">配置格式无效或保存失败</response>
|
||||||
|
/// <response code="500">服务器内部错误</response>
|
||||||
|
[HttpPost("{id}/confirm")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
[Produces("application/json")]
|
||||||
|
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||||
|
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||||
|
public async Task<ActionResult> Confirm(string id, [FromBody] ConfirmPendingConfigRequest? request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var config = FindConfigById(id);
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
return NotFound(new { message = $"Pending config {id} not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析配置 JSON
|
||||||
|
var configModel = JsonSerializer.Deserialize<GatewayConfigModel>(config.ConfigJson, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||||
|
});
|
||||||
|
|
||||||
|
if (configModel == null)
|
||||||
|
{
|
||||||
|
return BadRequest(new { message = "Invalid config format" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前用户
|
||||||
|
var confirmedBy = User.FindFirst(ClaimTypes.Name)?.Value ?? User.Identity?.Name ?? "system";
|
||||||
|
|
||||||
|
// 写入数据库
|
||||||
|
await SaveConfigToDatabaseAsync(configModel);
|
||||||
|
|
||||||
|
// 发送 PostgreSQL NOTIFY
|
||||||
|
await _notificationService.PublishConfigChangeAsync(
|
||||||
|
"route",
|
||||||
|
"create",
|
||||||
|
new { configId = id, serviceName = config.ServiceName, clusterId = config.ClusterId }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 从缓存中移除
|
||||||
|
_pendingConfigCache.Remove(config.SourceId);
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Pending config {ConfigId} confirmed by {ConfirmedBy}. Service: {ServiceName}",
|
||||||
|
id, confirmedBy, config.ServiceName);
|
||||||
|
|
||||||
|
return Ok(new { message = "Config confirmed successfully", configId = id });
|
||||||
|
}
|
||||||
|
catch (JsonException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error parsing config JSON for {ConfigId}", id);
|
||||||
|
return BadRequest(new { message = "Invalid config JSON format" });
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Validation error confirming config {ConfigId}", id);
|
||||||
|
return BadRequest(new { message = ex.Message });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error confirming pending config {ConfigId}", id);
|
||||||
|
return StatusCode(500, new { message = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 拒绝待确认配置
|
||||||
|
/// 从缓存中移除配置,并可选择记录拒绝原因
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">配置ID</param>
|
||||||
|
/// <param name="request">拒绝请求,包含可选的拒绝原因</param>
|
||||||
|
/// <returns>操作结果</returns>
|
||||||
|
/// <response code="200">成功拒绝配置</response>
|
||||||
|
/// <response code="404">配置不存在</response>
|
||||||
|
/// <response code="500">服务器内部错误</response>
|
||||||
|
[HttpPost("{id}/reject")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
[Produces("application/json")]
|
||||||
|
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||||
|
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||||
|
public ActionResult Reject(string id, [FromBody] RejectPendingConfigRequest? request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var config = FindConfigById(id);
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
return NotFound(new { message = $"Pending config {id} not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从缓存中移除
|
||||||
|
var removed = _pendingConfigCache.Remove(config.SourceId);
|
||||||
|
if (!removed)
|
||||||
|
{
|
||||||
|
return NotFound(new { message = $"Pending config {id} not found in cache" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录拒绝原因(可选)
|
||||||
|
var reason = request?.Reason ?? "No reason provided";
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Pending config {ConfigId} rejected. Reason: {Reason}. Service: {ServiceName}",
|
||||||
|
id, reason, config.ServiceName);
|
||||||
|
|
||||||
|
return Ok(new { message = "Config rejected successfully", configId = id, reason });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error rejecting pending config {ConfigId}", id);
|
||||||
|
return StatusCode(500, new { message = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 修改待确认配置
|
||||||
|
/// 允许用户调整配置内容,更新缓存中的配置,并标记为 Modified 状态
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">配置ID</param>
|
||||||
|
/// <param name="request">修改请求,包含新的配置内容和修改原因</param>
|
||||||
|
/// <returns>更新后的配置详情</returns>
|
||||||
|
/// <response code="200">成功修改配置</response>
|
||||||
|
/// <response code="404">配置不存在</response>
|
||||||
|
/// <response code="400">配置格式无效</response>
|
||||||
|
/// <response code="500">服务器内部错误</response>
|
||||||
|
[HttpPut("{id}/modify")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
[Produces("application/json")]
|
||||||
|
[ProducesResponseType(typeof(PendingConfigDto), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||||
|
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||||
|
public ActionResult<PendingConfigDto> Modify(string id, [FromBody] ModifyPendingConfigRequest request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (request?.Config == null)
|
||||||
|
{
|
||||||
|
return BadRequest(new { message = "Config content is required" });
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = FindConfigById(id);
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
return NotFound(new { message = $"Pending config {id} not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将新的配置内容序列化为 JSON
|
||||||
|
var newConfigJson = request.Config.ToJsonString(new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新配置
|
||||||
|
config.ConfigJson = newConfigJson;
|
||||||
|
config.Status = PendingConfigStatus.Modified;
|
||||||
|
config.IsModified = true;
|
||||||
|
|
||||||
|
// 尝试解析配置以更新相关字段
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var configModel = JsonSerializer.Deserialize<GatewayConfigModel>(newConfigJson, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||||
|
});
|
||||||
|
|
||||||
|
if (configModel != null)
|
||||||
|
{
|
||||||
|
config.ServiceName = configModel.ServiceName;
|
||||||
|
config.TenantCode = configModel.TenantCode;
|
||||||
|
config.ClusterId = configModel.ClusterId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (JsonException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Failed to parse modified config for {ConfigId}", id);
|
||||||
|
// 即使解析失败,仍然保存 JSON 内容
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新缓存
|
||||||
|
_pendingConfigCache.AddOrUpdate(config);
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Pending config {ConfigId} modified. Reason: {Reason}. Service: {ServiceName}",
|
||||||
|
id, request.Reason ?? "No reason provided", config.ServiceName);
|
||||||
|
|
||||||
|
return Ok(MapToDto(config));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error modifying pending config {ConfigId}", id);
|
||||||
|
return StatusCode(500, new { message = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据 ID 查找配置
|
||||||
|
/// </summary>
|
||||||
|
private PendingConfig? FindConfigById(string id)
|
||||||
|
{
|
||||||
|
return _pendingConfigCache.GetAll().FirstOrDefault(c => c.Id == id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将 PendingConfig 映射为 PendingConfigDto
|
||||||
|
/// </summary>
|
||||||
|
private static PendingConfigDto MapToDto(PendingConfig config)
|
||||||
|
{
|
||||||
|
JsonNode? configNode = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(config.ConfigJson))
|
||||||
|
{
|
||||||
|
configNode = JsonNode.Parse(config.ConfigJson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
// 忽略解析错误,返回 null
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PendingConfigDto
|
||||||
|
{
|
||||||
|
Id = config.Id,
|
||||||
|
Type = config.Type.ToString(),
|
||||||
|
Source = config.Source.ToString(),
|
||||||
|
SourceId = config.SourceId,
|
||||||
|
Config = configNode,
|
||||||
|
ServiceName = config.ServiceName,
|
||||||
|
TenantCode = config.TenantCode,
|
||||||
|
ClusterId = config.ClusterId,
|
||||||
|
Status = config.Status.ToString(),
|
||||||
|
CreatedAt = config.CreatedAt,
|
||||||
|
ConfirmedAt = config.ConfirmedAt,
|
||||||
|
ConfirmedBy = config.ConfirmedBy,
|
||||||
|
IsNew = config.IsNew,
|
||||||
|
IsModified = config.IsModified
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将配置保存到数据库
|
||||||
|
/// </summary>
|
||||||
|
private async Task SaveConfigToDatabaseAsync(GatewayConfigModel config)
|
||||||
|
{
|
||||||
|
// 1. 创建或更新 Cluster
|
||||||
|
var cluster = await _clusterStore.FindByClusterIdAsync(config.ClusterId);
|
||||||
|
if (cluster == null)
|
||||||
|
{
|
||||||
|
cluster = new GwCluster
|
||||||
|
{
|
||||||
|
Id = Guid.CreateVersion7().ToString("N"),
|
||||||
|
ClusterId = config.ClusterId,
|
||||||
|
Name = $"{config.ServiceName} Cluster",
|
||||||
|
Destinations = new List<GwDestination>()
|
||||||
|
};
|
||||||
|
await _clusterStore.CreateAsync(cluster);
|
||||||
|
_logger.LogInformation("Created new cluster: {ClusterId}", config.ClusterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 添加 Destination 到 Cluster
|
||||||
|
var serviceAddress = config.ServiceAddress ?? $"http://{config.ServiceName}";
|
||||||
|
var port = config.Port ?? 80;
|
||||||
|
var destinationAddress = $"{serviceAddress}:{port}";
|
||||||
|
|
||||||
|
var destination = new GwDestination
|
||||||
|
{
|
||||||
|
DestinationId = config.DestinationId,
|
||||||
|
Address = destinationAddress,
|
||||||
|
Weight = config.Weight,
|
||||||
|
HealthStatus = 1, // Healthy
|
||||||
|
Status = 1, // Active
|
||||||
|
TenantCode = config.TenantCode ?? ""
|
||||||
|
};
|
||||||
|
|
||||||
|
await _clusterStore.AddDestinationAsync(config.ClusterId, destination);
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Added destination {DestinationId} to cluster {ClusterId}: {Address}",
|
||||||
|
config.DestinationId, config.ClusterId, destinationAddress);
|
||||||
|
|
||||||
|
// 3. 创建 Route
|
||||||
|
var pathPattern = string.IsNullOrEmpty(config.PathPrefix)
|
||||||
|
? $"/{config.ServiceName}/{{**path}}"
|
||||||
|
: config.PathPrefix.TrimEnd('/') + "/{**path}";
|
||||||
|
|
||||||
|
var routeId = Guid.CreateVersion7().ToString("N");
|
||||||
|
var route = new GwTenantRoute
|
||||||
|
{
|
||||||
|
Id = routeId,
|
||||||
|
TenantCode = config.IsGlobal ? "" : (config.TenantCode ?? ""),
|
||||||
|
ServiceName = config.ServiceName,
|
||||||
|
ClusterId = config.ClusterId,
|
||||||
|
Match = new GwRouteMatch
|
||||||
|
{
|
||||||
|
Path = pathPattern,
|
||||||
|
Hosts = !string.IsNullOrEmpty(config.Host) ? new List<string> { config.Host } : null
|
||||||
|
},
|
||||||
|
Priority = config.IsGlobal ? 0 : 10,
|
||||||
|
Status = (int)RouteStatus.Active,
|
||||||
|
IsGlobal = config.IsGlobal,
|
||||||
|
CreatedTime = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
|
||||||
|
await _routeStore.CreateAsync(route);
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Created new route: {RouteId} for service {ServiceName} with path {PathPattern}",
|
||||||
|
routeId, config.ServiceName, pathPattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
176
src/Models/Dtos/PendingConfigDtos.cs
Normal file
176
src/Models/Dtos/PendingConfigDtos.cs
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using Fengling.Console.Models.Entities;
|
||||||
|
|
||||||
|
namespace Fengling.Console.Models.Dtos;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 待确认配置 DTO
|
||||||
|
/// 返回给前端的配置详情
|
||||||
|
/// </summary>
|
||||||
|
public class PendingConfigDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 唯一标识
|
||||||
|
/// </summary>
|
||||||
|
public string Id { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置类型
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置来源
|
||||||
|
/// </summary>
|
||||||
|
public string Source { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 来源标识(如 K8s Service UID)
|
||||||
|
/// </summary>
|
||||||
|
public string SourceId { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置内容(JSON 对象)
|
||||||
|
/// </summary>
|
||||||
|
public JsonNode? Config { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 服务名称
|
||||||
|
/// </summary>
|
||||||
|
public string ServiceName { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 租户编码
|
||||||
|
/// </summary>
|
||||||
|
public string? TenantCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 集群ID
|
||||||
|
/// </summary>
|
||||||
|
public string ClusterId { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置状态
|
||||||
|
/// </summary>
|
||||||
|
public string Status { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 确认时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? ConfirmedAt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 确认人
|
||||||
|
/// </summary>
|
||||||
|
public string? ConfirmedBy { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否为新配置
|
||||||
|
/// </summary>
|
||||||
|
public bool IsNew { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否已修改
|
||||||
|
/// </summary>
|
||||||
|
public bool IsModified { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 待确认配置列表查询请求
|
||||||
|
/// </summary>
|
||||||
|
public class PendingConfigListRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 页码,从 1 开始
|
||||||
|
/// </summary>
|
||||||
|
public int Page { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 每页大小,默认 10
|
||||||
|
/// </summary>
|
||||||
|
public int PageSize { get; set; } = 10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 按状态筛选(Pending, Confirmed, Rejected, Modified)
|
||||||
|
/// </summary>
|
||||||
|
public PendingConfigStatus? Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 按服务名称搜索(支持模糊匹配)
|
||||||
|
/// </summary>
|
||||||
|
public string? ServiceName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 按租户编码筛选
|
||||||
|
/// </summary>
|
||||||
|
public string? TenantCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 按配置来源筛选(K8sDiscovery, Manual)
|
||||||
|
/// </summary>
|
||||||
|
public PendingConfigSource? Source { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 确认待确认配置请求
|
||||||
|
/// </summary>
|
||||||
|
public class ConfirmPendingConfigRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 确认备注信息
|
||||||
|
/// </summary>
|
||||||
|
public string? Remark { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 拒绝待确认配置请求
|
||||||
|
/// </summary>
|
||||||
|
public class RejectPendingConfigRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 拒绝原因
|
||||||
|
/// </summary>
|
||||||
|
public string? Reason { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 修改待确认配置请求
|
||||||
|
/// </summary>
|
||||||
|
public class ModifyPendingConfigRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 修改后的配置内容(JSON 对象)
|
||||||
|
/// </summary>
|
||||||
|
public JsonNode? Config { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 修改原因
|
||||||
|
/// </summary>
|
||||||
|
public string? Reason { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 待确认配置统计信息
|
||||||
|
/// </summary>
|
||||||
|
public class PendingConfigStatisticsDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 待确认总数
|
||||||
|
/// </summary>
|
||||||
|
public int TotalCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 新配置数量
|
||||||
|
/// </summary>
|
||||||
|
public int NewCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 变更配置数量
|
||||||
|
/// </summary>
|
||||||
|
public int ModifiedCount { get; set; }
|
||||||
|
}
|
||||||
@ -96,6 +96,9 @@ builder.Services.AddSwaggerGen(c =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// 添加通知服务
|
||||||
|
builder.Services.AddNotificationService();
|
||||||
|
|
||||||
// 添加 K8s 服务监视
|
// 添加 K8s 服务监视
|
||||||
builder.Services.AddServiceLabelParser();
|
builder.Services.AddServiceLabelParser();
|
||||||
builder.Services.AddPendingConfigCache();
|
builder.Services.AddPendingConfigCache();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user