fengling-console/Controllers/GatewayController.cs
Sam c8cb7c06bc feat: 添加Console API认证和OpenIddict集成
- 配置AuthService使用OpenIddict reference tokens
- 添加fengling-api客户端用于introspection验证
- 配置Console API通过OpenIddict验证reference tokens
- 实现Tenant/Users/Roles/OAuthClients CRUD API
- 添加GatewayController服务注册API
- 重构Repository和Service层支持多租户

BREAKING CHANGE: API认证现在使用OpenIddict reference tokens
2026-02-08 19:01:25 +08:00

362 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

namespace Fengling.Console.Controllers;
/// <summary>
/// 网关管理控制器
/// 提供网关服务、路由、集群实例等配置管理功能
/// </summary>
[ApiController]
[Route("api/console/[controller]")]
[Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)]
public class GatewayController(IGatewayService gatewayService, ILogger<GatewayController> logger)
: ControllerBase
{
/// <summary>
/// 获取网关统计数据
/// </summary>
/// <returns>网关的整体统计信息,包括请求量、响应时间等指标</returns>
/// <response code="200">成功返回网关统计数据</response>
/// <response code="500">服务器内部错误</response>
[HttpGet("statistics")]
[Produces("application/json")]
[ProducesResponseType(typeof(GatewayStatisticsDto), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<GatewayStatisticsDto>> GetStatistics()
{
try
{
var statistics = await gatewayService.GetStatisticsAsync();
return Ok(statistics);
}
catch (Exception ex)
{
logger.LogError(ex, "Error getting gateway statistics");
return StatusCode(500, new { message = ex.Message });
}
}
/// <summary>
/// 获取网关服务列表
/// </summary>
/// <param name="globalOnly">是否只返回全局服务默认为false</param>
/// <param name="tenantCode">租户编码,用于筛选特定租户的服务</param>
/// <returns>网关服务列表,包含服务名称、地址、健康状态等信息</returns>
/// <response code="200">成功返回网关服务列表</response>
/// <response code="500">服务器内部错误</response>
[HttpGet("services")]
[Produces("application/json")]
[ProducesResponseType(typeof(List<GatewayServiceDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<List<GatewayServiceDto>>> GetServices(
[FromQuery] bool globalOnly = false,
[FromQuery] string? tenantCode = null)
{
try
{
var services = await gatewayService.GetServicesAsync(globalOnly, tenantCode);
return Ok(services);
}
catch (Exception ex)
{
logger.LogError(ex, "Error getting gateway services");
return StatusCode(500, new { message = ex.Message });
}
}
/// <summary>
/// 获取单个网关服务详情
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <param name="tenantCode">租户编码,用于筛选特定租户的服务</param>
/// <returns>网关服务的详细信息</returns>
/// <response code="200">成功返回服务详情</response>
/// <response code="404">服务不存在</response>
/// <response code="500">服务器内部错误</response>
[HttpGet("services/{serviceName}")]
[Produces("application/json")]
[ProducesResponseType(typeof(GatewayServiceDto), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<GatewayServiceDto>> GetService(
string serviceName,
[FromQuery] string? tenantCode = null)
{
try
{
var service = await gatewayService.GetServiceAsync(serviceName, tenantCode);
if (service == null)
{
return NotFound(new { message = $"Service {serviceName} not found" });
}
return Ok(service);
}
catch (Exception ex)
{
logger.LogError(ex, "Error getting service {ServiceName}", serviceName);
return StatusCode(500, new { message = ex.Message });
}
}
/// <summary>
/// 注册新的网关服务
/// </summary>
/// <param name="dto">创建服务所需的配置信息</param>
/// <returns>创建的服务详情</returns>
/// <response code="201">成功创建服务</response>
/// <response code="400">请求参数无效或服务已存在</response>
/// <response code="500">服务器内部错误</response>
[HttpPost("services")]
[Produces("application/json")]
[ProducesResponseType(typeof(GatewayServiceDto), StatusCodes.Status201Created)]
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<GatewayServiceDto>> RegisterService([FromBody] CreateGatewayServiceDto dto)
{
try
{
var service = await gatewayService.RegisterServiceAsync(dto);
return CreatedAtAction(nameof(GetService), new { serviceName = service.ServicePrefix }, service);
}
catch (InvalidOperationException ex)
{
logger.LogWarning(ex, "Validation error registering service");
return BadRequest(new { message = ex.Message });
}
catch (Exception ex)
{
logger.LogError(ex, "Error registering service");
return StatusCode(500, new { message = ex.Message });
}
}
/// <summary>
/// 注销网关服务
/// </summary>
/// <param name="serviceName">要注销的服务名称</param>
/// <param name="tenantCode">租户编码,用于筛选特定租户的服务</param>
/// <returns>无内容响应</returns>
/// <response code="200">成功注销服务</response>
/// <response code="404">服务不存在</response>
/// <response code="500">服务器内部错误</response>
[HttpDelete("services/{serviceName}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult> UnregisterService(
string serviceName,
[FromQuery] string? tenantCode = null)
{
try
{
var result = await gatewayService.UnregisterServiceAsync(serviceName, tenantCode);
if (!result)
{
return NotFound(new { message = $"Service {serviceName} not found" });
}
return Ok(new { message = "Service unregistered successfully" });
}
catch (Exception ex)
{
logger.LogError(ex, "Error unregistering service {ServiceName}", serviceName);
return StatusCode(500, new { message = ex.Message });
}
}
/// <summary>
/// 获取网关路由列表
/// </summary>
/// <param name="globalOnly">是否只返回全局路由默认为false</param>
/// <returns>网关路由列表,包含路径匹配规则、转发目标等信息</returns>
/// <response code="200">成功返回网关路由列表</response>
/// <response code="500">服务器内部错误</response>
[HttpGet("routes")]
[Produces("application/json")]
[ProducesResponseType(typeof(List<GatewayRouteDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<List<GatewayRouteDto>>> GetRoutes([FromQuery] bool globalOnly = false)
{
try
{
var routes = await gatewayService.GetRoutesAsync(globalOnly);
return Ok(routes);
}
catch (Exception ex)
{
logger.LogError(ex, "Error getting gateway routes");
return StatusCode(500, new { message = ex.Message });
}
}
/// <summary>
/// 创建新的网关路由
/// </summary>
/// <param name="dto">创建路由所需的配置信息</param>
/// <returns>创建的路由详情</returns>
/// <response code="201">成功创建路由</response>
/// <response code="400">请求参数无效</response>
/// <response code="500">服务器内部错误</response>
[HttpPost("routes")]
[Produces("application/json")]
[ProducesResponseType(typeof(GatewayRouteDto), StatusCodes.Status201Created)]
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<GatewayRouteDto>> CreateRoute([FromBody] CreateGatewayRouteDto dto)
{
try
{
var route = await gatewayService.CreateRouteAsync(dto);
return Ok(route);
}
catch (InvalidOperationException ex)
{
return BadRequest(new { message = ex.Message });
}
catch (Exception ex)
{
logger.LogError(ex, "Error creating gateway route");
return StatusCode(500, new { message = ex.Message });
}
}
/// <summary>
/// 获取集群实例列表
/// </summary>
/// <param name="clusterId">集群ID</param>
/// <returns>指定集群下的所有服务实例列表</returns>
/// <response code="200">成功返回实例列表</response>
/// <response code="500">服务器内部错误</response>
[HttpGet("clusters/{clusterId}/instances")]
[Produces("application/json")]
[ProducesResponseType(typeof(List<GatewayInstanceDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<List<GatewayInstanceDto>>> GetInstances(string clusterId)
{
try
{
var instances = await gatewayService.GetInstancesAsync(clusterId);
return Ok(instances);
}
catch (Exception ex)
{
logger.LogError(ex, "Error getting instances for cluster {ClusterId}", clusterId);
return StatusCode(500, new { message = ex.Message });
}
}
/// <summary>
/// 添加服务实例到集群
/// </summary>
/// <param name="dto">创建实例所需的配置信息</param>
/// <returns>创建的实例详情</returns>
/// <response code="201">成功添加实例</response>
/// <response code="400">请求参数无效</response>
/// <response code="500">服务器内部错误</response>
[HttpPost("instances")]
[Produces("application/json")]
[ProducesResponseType(typeof(GatewayInstanceDto), StatusCodes.Status201Created)]
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<GatewayInstanceDto>> AddInstance([FromBody] CreateGatewayInstanceDto dto)
{
try
{
var instance = await gatewayService.AddInstanceAsync(dto);
return CreatedAtAction(nameof(GetInstances), new { clusterId = dto.ClusterId }, instance);
}
catch (InvalidOperationException ex)
{
return BadRequest(new { message = ex.Message });
}
catch (Exception ex)
{
logger.LogError(ex, "Error adding instance to cluster {ClusterId}", dto.ClusterId);
return StatusCode(500, new { message = ex.Message });
}
}
/// <summary>
/// 移除服务实例
/// </summary>
/// <param name="instanceId">实例ID</param>
/// <returns>无内容响应</returns>
/// <response code="200">成功移除实例</response>
/// <response code="404">实例不存在</response>
/// <response code="500">服务器内部错误</response>
[HttpDelete("instances/{instanceId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult> RemoveInstance(long instanceId)
{
try
{
var result = await gatewayService.RemoveInstanceAsync(instanceId);
if (!result)
{
return NotFound(new { message = $"Instance {instanceId} not found" });
}
return Ok(new { message = "Instance removed successfully" });
}
catch (Exception ex)
{
logger.LogError(ex, "Error removing instance {InstanceId}", instanceId);
return StatusCode(500, new { message = ex.Message });
}
}
/// <summary>
/// 更新实例权重
/// 用于负载均衡策略中调整实例的请求分发权重
/// </summary>
/// <param name="instanceId">实例ID</param>
/// <param name="dto">包含新权重值的请求体</param>
/// <returns>无内容响应</returns>
/// <response code="200">成功更新权重</response>
/// <response code="404">实例不存在</response>
/// <response code="500">服务器内部错误</response>
[HttpPut("instances/{instanceId}/weight")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult> UpdateWeight(long instanceId, [FromBody] GatewayUpdateWeightDto dto)
{
try
{
var result = await gatewayService.UpdateInstanceWeightAsync(instanceId, dto.Weight);
if (!result)
{
return NotFound(new { message = $"Instance {instanceId} not found" });
}
return Ok(new { message = "Weight updated successfully" });
}
catch (Exception ex)
{
logger.LogError(ex, "Error updating weight for instance {InstanceId}", instanceId);
return StatusCode(500, new { message = ex.Message });
}
}
/// <summary>
/// 重新加载网关配置
/// 触发网关重新加载所有配置,包括路由、服务、集群等配置
/// </summary>
/// <returns>无内容响应</returns>
/// <response code="200">成功重新加载配置</response>
/// <response code="500">服务器内部错误</response>
[HttpPost("reload")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult> ReloadGateway()
{
try
{
await gatewayService.ReloadGatewayAsync();
return Ok(new { message = "Gateway configuration reloaded successfully" });
}
catch (Exception ex)
{
logger.LogError(ex, "Error reloading gateway configuration");
return StatusCode(500, new { message = ex.Message });
}
}
}