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.Services;
|
||||
global using Fengling.Platform.Infrastructure;
|
||||
global using Microsoft.AspNetCore.Authorization;
|
||||
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 服务监视
|
||||
builder.Services.AddServiceLabelParser();
|
||||
builder.Services.AddPendingConfigCache();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user