- 配置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
362 lines
15 KiB
C#
362 lines
15 KiB
C#
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 });
|
||
}
|
||
}
|
||
}
|