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
This commit is contained in:
parent
61c3a27192
commit
c8cb7c06bc
361
Controllers/GatewayController.cs
Normal file
361
Controllers/GatewayController.cs
Normal file
@ -0,0 +1,361 @@
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
5
Controllers/GlobalUsing.cs
Normal file
5
Controllers/GlobalUsing.cs
Normal file
@ -0,0 +1,5 @@
|
||||
global using OpenIddict.Validation.AspNetCore;
|
||||
global using Fengling.Console.Models.Dtos;
|
||||
global using Fengling.Console.Services;
|
||||
global using Microsoft.AspNetCore.Authorization;
|
||||
global using Microsoft.AspNetCore.Mvc;
|
||||
@ -1,12 +1,11 @@
|
||||
using Fengling.Console.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Fengling.Console.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// OAuth客户端管理控制器
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
[Route("api/console/[controller]")]
|
||||
[Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)]
|
||||
public class OAuthClientsController : ControllerBase
|
||||
{
|
||||
private readonly IOAuthClientService _service;
|
||||
@ -20,25 +19,31 @@ public class OAuthClientsController : ControllerBase
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取OAuth客户端列表
|
||||
/// </summary>
|
||||
/// <param name="query">分页查询参数,支持按显示名称、客户端ID和状态筛选</param>
|
||||
/// <returns>分页的OAuth客户端列表,包含总数量、分页信息和客户端详情</returns>
|
||||
/// <response code="200">成功返回OAuth客户端分页列表</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<object>> GetClients(
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 10,
|
||||
[FromQuery] string? displayName = null,
|
||||
[FromQuery] string? clientId = null,
|
||||
[FromQuery] string? status = null)
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(typeof(OAuthClientListDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<OAuthClientListDto>> GetClients([FromQuery] OAuthClientQueryDto query)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (items, totalCount) = await _service.GetClientsAsync(page, pageSize, displayName, clientId, status);
|
||||
var (items, totalCount) = await _service.GetClientsAsync(query.Page, query.PageSize, query.DisplayName, query.ClientId, query.Status);
|
||||
|
||||
return Ok(new
|
||||
var result = new OAuthClientListDto
|
||||
{
|
||||
items,
|
||||
totalCount,
|
||||
page,
|
||||
pageSize
|
||||
});
|
||||
Items = items.ToList(),
|
||||
TotalCount = totalCount,
|
||||
Page = query.Page,
|
||||
PageSize = query.PageSize
|
||||
};
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -47,14 +52,33 @@ public class OAuthClientsController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取OAuth客户端选项
|
||||
/// </summary>
|
||||
/// <returns>包含客户端类型、授权类型、授权范围等可选值的配置选项</returns>
|
||||
/// <response code="200">成功返回客户端配置选项</response>
|
||||
[HttpGet("options")]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
|
||||
public ActionResult<object> GetClientOptions()
|
||||
{
|
||||
return Ok(_service.GetClientOptions());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取单个OAuth客户端详情
|
||||
/// </summary>
|
||||
/// <param name="id">客户端唯一标识符</param>
|
||||
/// <returns>OAuth客户端的完整配置信息</returns>
|
||||
/// <response code="200">成功返回客户端详情</response>
|
||||
/// <response code="404">客户端不存在</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<ActionResult<object>> GetClient(string id)
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(typeof(OAuthClientDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<OAuthClientDto>> GetClient(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -73,8 +97,20 @@ public class OAuthClientsController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建新的OAuth客户端
|
||||
/// </summary>
|
||||
/// <param name="dto">创建客户端所需的配置信息</param>
|
||||
/// <returns>创建的OAuth客户端详情</returns>
|
||||
/// <response code="201">成功创建客户端</response>
|
||||
/// <response code="400">请求参数无效或客户端ID已存在</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<object>> CreateClient([FromBody] CreateClientDto dto)
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(typeof(OAuthClientDto), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<OAuthClientDto>> CreateClient([FromBody] CreateClientDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -92,7 +128,19 @@ public class OAuthClientsController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为指定客户端生成新的密钥
|
||||
/// </summary>
|
||||
/// <param name="id">客户端唯一标识符</param>
|
||||
/// <returns>包含新生成的客户端密钥信息</returns>
|
||||
/// <response code="200">成功生成新密钥</response>
|
||||
/// <response code="404">客户端不存在</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpPost("{id}/generate-secret")]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult> GenerateSecret(string id)
|
||||
{
|
||||
try
|
||||
@ -111,7 +159,18 @@ public class OAuthClientsController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除指定的OAuth客户端
|
||||
/// </summary>
|
||||
/// <param name="id">客户端唯一标识符</param>
|
||||
/// <returns>无内容响应</returns>
|
||||
/// <response code="204">成功删除客户端</response>
|
||||
/// <response code="404">客户端不存在</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpDelete("{id}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> DeleteClient(string id)
|
||||
{
|
||||
try
|
||||
@ -130,7 +189,19 @@ public class OAuthClientsController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新指定的OAuth客户端
|
||||
/// </summary>
|
||||
/// <param name="id">客户端唯一标识符</param>
|
||||
/// <param name="dto">需要更新的客户端配置信息</param>
|
||||
/// <returns>无内容响应</returns>
|
||||
/// <response code="204">成功更新客户端</response>
|
||||
/// <response code="404">客户端不存在</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpPut("{id}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> UpdateClient(string id, [FromBody] UpdateClientDto dto)
|
||||
{
|
||||
try
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
using Fengling.Console.Models.Dtos;
|
||||
using Fengling.Console.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Fengling.Console.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 角色管理控制器
|
||||
/// 提供角色的增删改查以及用户角色关联管理功能
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
[Route("api/console/[controller]")]
|
||||
[Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)]
|
||||
public class RolesController : ControllerBase
|
||||
{
|
||||
private readonly IRoleService _roleService;
|
||||
@ -19,17 +18,30 @@ public class RolesController : ControllerBase
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取角色列表
|
||||
/// </summary>
|
||||
/// <param name="query">分页查询参数,支持按名称和租户ID筛选</param>
|
||||
/// <returns>分页的角色列表,包含角色基本信息和关联统计</returns>
|
||||
/// <response code="200">成功返回角色分页列表</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<object>> GetRoles(
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 10,
|
||||
[FromQuery] string? name = null,
|
||||
[FromQuery] string? tenantId = null)
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(typeof(PagedResultDto<RoleDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<PagedResultDto<RoleDto>>> GetRoles([FromQuery] RoleQueryDto query)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (items, totalCount) = await _roleService.GetRolesAsync(page, pageSize, name, tenantId);
|
||||
return Ok(new { items, totalCount, page, pageSize });
|
||||
var (items, totalCount) = await _roleService.GetRolesAsync(query.Page, query.PageSize, query.Name, query.TenantId);
|
||||
var result = new PagedResultDto<RoleDto>
|
||||
{
|
||||
Items = items.ToList(),
|
||||
TotalCount = totalCount,
|
||||
Page = query.Page,
|
||||
PageSize = query.PageSize
|
||||
};
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -38,7 +50,19 @@ public class RolesController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取单个角色详情
|
||||
/// </summary>
|
||||
/// <param name="id">角色ID</param>
|
||||
/// <returns>角色的详细信息,包括权限配置等</returns>
|
||||
/// <response code="200">成功返回角色详情</response>
|
||||
/// <response code="404">角色不存在</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpGet("{id}")]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(typeof(RoleDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<RoleDto>> GetRole(long id)
|
||||
{
|
||||
try
|
||||
@ -57,7 +81,19 @@ public class RolesController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定角色的用户列表
|
||||
/// </summary>
|
||||
/// <param name="id">角色ID</param>
|
||||
/// <returns>属于该角色的所有用户列表</returns>
|
||||
/// <response code="200">成功返回用户列表</response>
|
||||
/// <response code="404">角色不存在</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpGet("{id}/users")]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(typeof(IEnumerable<UserDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<IEnumerable<UserDto>>> GetRoleUsers(long id)
|
||||
{
|
||||
try
|
||||
@ -77,7 +113,19 @@ public class RolesController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建新角色
|
||||
/// </summary>
|
||||
/// <param name="dto">创建角色所需的配置信息</param>
|
||||
/// <returns>创建的角色详情</returns>
|
||||
/// <response code="201">成功创建角色</response>
|
||||
/// <response code="400">请求参数无效或角色名称已存在</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpPost]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(typeof(RoleDto), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<RoleDto>> CreateRole([FromBody] CreateRoleDto dto)
|
||||
{
|
||||
try
|
||||
@ -97,7 +145,21 @@ public class RolesController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新角色信息
|
||||
/// </summary>
|
||||
/// <param name="id">角色ID</param>
|
||||
/// <param name="dto">需要更新的角色配置信息</param>
|
||||
/// <returns>无内容响应</returns>
|
||||
/// <response code="204">成功更新角色</response>
|
||||
/// <response code="404">角色不存在</response>
|
||||
/// <response code="400">请求参数无效</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpPut("{id}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> UpdateRole(long id, [FromBody] UpdateRoleDto dto)
|
||||
{
|
||||
try
|
||||
@ -122,7 +184,20 @@ public class RolesController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除角色
|
||||
/// </summary>
|
||||
/// <param name="id">角色ID</param>
|
||||
/// <returns>无内容响应</returns>
|
||||
/// <response code="204">成功删除角色</response>
|
||||
/// <response code="404">角色不存在</response>
|
||||
/// <response code="400">请求参数无效(如角色下有关联用户)</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpDelete("{id}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> DeleteRole(long id)
|
||||
{
|
||||
try
|
||||
@ -147,7 +222,60 @@ public class RolesController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将用户添加到角色
|
||||
/// </summary>
|
||||
/// <param name="id">角色ID</param>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <returns>无内容响应</returns>
|
||||
/// <response code="204">成功添加用户到角色</response>
|
||||
/// <response code="404">角色或用户不存在</response>
|
||||
/// <response code="400">请求参数无效或用户已在角色中</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpPost("{id}/users/{userId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> AddUserToRole(long id, long userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _roleService.AddUserToRoleAsync(id, userId);
|
||||
return NoContent();
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Role or user not found: RoleId={RoleId}, UserId={UserId}", id, userId);
|
||||
return NotFound();
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Validation error adding user {UserId} to role {RoleId}", userId, id);
|
||||
return BadRequest(new { message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error adding user {UserId} to role {RoleId}", userId, id);
|
||||
return StatusCode(500, new { message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将用户从角色中移除
|
||||
/// </summary>
|
||||
/// <param name="id">角色ID</param>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <returns>无内容响应</returns>
|
||||
/// <response code="204">成功从角色中移除用户</response>
|
||||
/// <response code="404">角色或用户不存在</response>
|
||||
/// <response code="400">请求参数无效</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpDelete("{id}/users/{userId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> RemoveUserFromRole(long id, long userId)
|
||||
{
|
||||
try
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
using Fengling.Console.Models.Dtos;
|
||||
using Fengling.Console.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Fengling.Console.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 租户管理控制器
|
||||
/// 提供租户的增删改查以及租户用户、角色、配置管理功能
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
[Route("api/console/[controller]")]
|
||||
[Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)]
|
||||
public class TenantsController : ControllerBase
|
||||
{
|
||||
private readonly ITenantService _tenantService;
|
||||
@ -19,18 +18,31 @@ public class TenantsController : ControllerBase
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取租户列表
|
||||
/// </summary>
|
||||
/// <param name="query">分页查询参数,支持按名称、租户编码和状态筛选</param>
|
||||
/// <returns>分页的租户列表,包含租户基本信息和状态</returns>
|
||||
/// <response code="200">成功返回租户分页列表</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<object>> GetTenants(
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 10,
|
||||
[FromQuery] string? name = null,
|
||||
[FromQuery] string? tenantId = null,
|
||||
[FromQuery] string? status = null)
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(typeof(PagedResultDto<TenantDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<PagedResultDto<TenantDto>>> GetTenants([FromQuery] TenantQueryDto query)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (items, totalCount) = await _tenantService.GetTenantsAsync(page, pageSize, name, tenantId, status);
|
||||
return Ok(new { items, totalCount, page, pageSize });
|
||||
var (items, totalCount) = await _tenantService.GetTenantsAsync(query.Page, query.PageSize, query.Name,
|
||||
query.TenantId, query.Status);
|
||||
var result = new PagedResultDto<TenantDto>
|
||||
{
|
||||
Items = items.ToList(),
|
||||
TotalCount = totalCount,
|
||||
Page = query.Page,
|
||||
PageSize = query.PageSize
|
||||
};
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -39,7 +51,19 @@ public class TenantsController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取单个租户详情
|
||||
/// </summary>
|
||||
/// <param name="id">租户ID</param>
|
||||
/// <returns>租户的详细信息,包括配置、限额等信息</returns>
|
||||
/// <response code="200">成功返回租户详情</response>
|
||||
/// <response code="404">租户不存在</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpGet("{id}")]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(typeof(TenantDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<TenantDto>> GetTenant(long id)
|
||||
{
|
||||
try
|
||||
@ -49,6 +73,7 @@ public class TenantsController : ControllerBase
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(tenant);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -58,7 +83,19 @@ public class TenantsController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定租户的用户列表
|
||||
/// </summary>
|
||||
/// <param name="tenantId">租户ID</param>
|
||||
/// <returns>属于该租户的所有用户列表</returns>
|
||||
/// <response code="200">成功返回用户列表</response>
|
||||
/// <response code="404">租户不存在</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpGet("{tenantId}/users")]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(typeof(IEnumerable<UserDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<IEnumerable<UserDto>>> GetTenantUsers(long tenantId)
|
||||
{
|
||||
try
|
||||
@ -78,7 +115,19 @@ public class TenantsController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定租户的角色列表
|
||||
/// </summary>
|
||||
/// <param name="tenantId">租户ID</param>
|
||||
/// <returns>属于该租户的所有角色列表</returns>
|
||||
/// <response code="200">成功返回角色列表</response>
|
||||
/// <response code="404">租户不存在</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpGet("{tenantId}/roles")]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(typeof(IEnumerable<object>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<IEnumerable<object>>> GetTenantRoles(long tenantId)
|
||||
{
|
||||
try
|
||||
@ -98,47 +147,81 @@ public class TenantsController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{tenantId}/settings")]
|
||||
public async Task<ActionResult<TenantSettingsDto>> GetTenantSettings(string tenantId)
|
||||
/// <summary>
|
||||
/// 获取租户配置信息
|
||||
/// </summary>
|
||||
/// <param name="id">租户ID</param>
|
||||
/// <returns>租户的详细配置信息,包括功能开关、配额限制等</returns>
|
||||
/// <response code="200">成功返回租户配置</response>
|
||||
/// <response code="404">租户不存在</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpGet("{id}/settings")]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(typeof(TenantSettingsDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<TenantSettingsDto>> GetTenantSettings(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = await _tenantService.GetTenantSettingsAsync(tenantId);
|
||||
var settings = await _tenantService.GetTenantSettingsAsync(id);
|
||||
return Ok(settings);
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Tenant not found: {TenantId}", tenantId);
|
||||
_logger.LogWarning(ex, "Tenant not found: {TenantId}", id);
|
||||
return NotFound();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting settings for tenant {TenantId}", tenantId);
|
||||
_logger.LogError(ex, "Error getting settings for tenant {TenantId}", id);
|
||||
return StatusCode(500, new { message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{tenantId}/settings")]
|
||||
public async Task<IActionResult> UpdateTenantSettings(string tenantId, [FromBody] TenantSettingsDto settings)
|
||||
/// <summary>
|
||||
/// 更新租户配置
|
||||
/// </summary>
|
||||
/// <param name="id">租户ID</param>
|
||||
/// <param name="settings">需要更新的租户配置信息</param>
|
||||
/// <returns>无内容响应</returns>
|
||||
/// <response code="204">成功更新租户配置</response>
|
||||
/// <response code="404">租户不存在</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpPut("{id}/settings")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> UpdateTenantSettings(long id, [FromBody] TenantSettingsDto settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _tenantService.UpdateTenantSettingsAsync(tenantId, settings);
|
||||
await _tenantService.UpdateTenantSettingsAsync(id, settings);
|
||||
return NoContent();
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Tenant not found: {TenantId}", tenantId);
|
||||
_logger.LogWarning(ex, "Tenant not found: {TenantId}", id);
|
||||
return NotFound();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error updating settings for tenant {TenantId}", tenantId);
|
||||
_logger.LogError(ex, "Error updating settings for tenant {TenantId}", id);
|
||||
return StatusCode(500, new { message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建新租户
|
||||
/// </summary>
|
||||
/// <param name="dto">创建租户所需的配置信息</param>
|
||||
/// <returns>创建的租户详情</returns>
|
||||
/// <response code="201">成功创建租户</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpPost]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(typeof(TenantDto), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<TenantDto>> CreateTenant([FromBody] CreateTenantDto dto)
|
||||
{
|
||||
try
|
||||
@ -153,7 +236,19 @@ public class TenantsController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新租户信息
|
||||
/// </summary>
|
||||
/// <param name="id">租户ID</param>
|
||||
/// <param name="dto">需要更新的租户配置信息</param>
|
||||
/// <returns>无内容响应</returns>
|
||||
/// <response code="204">成功更新租户</response>
|
||||
/// <response code="404">租户不存在</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpPut("{id}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> UpdateTenant(long id, [FromBody] UpdateTenantDto dto)
|
||||
{
|
||||
try
|
||||
@ -173,7 +268,18 @@ public class TenantsController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除租户
|
||||
/// </summary>
|
||||
/// <param name="id">租户ID</param>
|
||||
/// <returns>无内容响应</returns>
|
||||
/// <response code="204">成功删除租户</response>
|
||||
/// <response code="404">租户不存在</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpDelete("{id}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> DeleteTenant(long id)
|
||||
{
|
||||
try
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
using Fengling.Console.Models.Dtos;
|
||||
using Fengling.Console.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
|
||||
namespace Fengling.Console.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 用户管理控制器
|
||||
/// 提供用户的增删改查以及密码重置等功能
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
[Route("api/console/[controller]")]
|
||||
[Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)]
|
||||
public class UsersController : ControllerBase
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
@ -19,18 +20,30 @@ public class UsersController : ControllerBase
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户列表
|
||||
/// </summary>
|
||||
/// <param name="query">分页查询参数,支持按用户名、邮箱和租户ID筛选</param>
|
||||
/// <returns>分页的用户列表,包含用户基本信息和状态</returns>
|
||||
/// <response code="200">成功返回用户分页列表</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<object>> GetUsers(
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 10,
|
||||
[FromQuery] string? userName = null,
|
||||
[FromQuery] string? email = null,
|
||||
[FromQuery] string? tenantId = null)
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(typeof(PagedResultDto<UserDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<PagedResultDto<UserDto>>> GetUsers([FromQuery] UserQueryDto query)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (items, totalCount) = await _userService.GetUsersAsync(page, pageSize, userName, email, tenantId);
|
||||
return Ok(new { items, totalCount, page, pageSize });
|
||||
var (items, totalCount) = await _userService.GetUsersAsync(query.Page, query.PageSize, query.UserName, query.Email, query.TenantId);
|
||||
var result = new PagedResultDto<UserDto>
|
||||
{
|
||||
Items = items.ToList(),
|
||||
TotalCount = totalCount,
|
||||
Page = query.Page,
|
||||
PageSize = query.PageSize
|
||||
};
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -39,7 +52,19 @@ public class UsersController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取单个用户详情
|
||||
/// </summary>
|
||||
/// <param name="id">用户ID</param>
|
||||
/// <returns>用户的详细信息,包括角色、租户等信息</returns>
|
||||
/// <response code="200">成功返回用户详情</response>
|
||||
/// <response code="404">用户不存在</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpGet("{id}")]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(typeof(UserDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<UserDto>> GetUser(long id)
|
||||
{
|
||||
try
|
||||
@ -58,7 +83,19 @@ public class UsersController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建新用户
|
||||
/// </summary>
|
||||
/// <param name="dto">创建用户所需的配置信息</param>
|
||||
/// <returns>创建的用户详情</returns>
|
||||
/// <response code="201">成功创建用户</response>
|
||||
/// <response code="400">请求参数无效或用户名/邮箱已存在</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpPost]
|
||||
[Produces("application/json")]
|
||||
[ProducesResponseType(typeof(UserDto), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<UserDto>> CreateUser([FromBody] CreateUserDto dto)
|
||||
{
|
||||
try
|
||||
@ -78,7 +115,19 @@ public class UsersController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新用户信息
|
||||
/// </summary>
|
||||
/// <param name="id">用户ID</param>
|
||||
/// <param name="dto">需要更新的用户配置信息</param>
|
||||
/// <returns>无内容响应</returns>
|
||||
/// <response code="204">成功更新用户</response>
|
||||
/// <response code="404">用户不存在</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpPut("{id}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> UpdateUser(long id, [FromBody] UpdateUserDto dto)
|
||||
{
|
||||
try
|
||||
@ -98,7 +147,21 @@ public class UsersController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置用户密码
|
||||
/// </summary>
|
||||
/// <param name="id">用户ID</param>
|
||||
/// <param name="dto">包含新密码的请求体</param>
|
||||
/// <returns>无内容响应</returns>
|
||||
/// <response code="204">成功重置密码</response>
|
||||
/// <response code="404">用户不存在</response>
|
||||
/// <response code="400">密码不符合复杂度要求</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpPut("{id}/password")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> ResetPassword(long id, [FromBody] ResetPasswordDto dto)
|
||||
{
|
||||
try
|
||||
@ -123,7 +186,18 @@ public class UsersController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除用户
|
||||
/// </summary>
|
||||
/// <param name="id">用户ID</param>
|
||||
/// <returns>无内容响应</returns>
|
||||
/// <response code="204">成功删除用户</response>
|
||||
/// <response code="404">用户不存在</response>
|
||||
/// <response code="500">服务器内部错误</response>
|
||||
[HttpDelete("{id}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> DeleteUser(long id)
|
||||
{
|
||||
try
|
||||
|
||||
85
Datas/ApplicationDbContext.cs
Normal file
85
Datas/ApplicationDbContext.cs
Normal file
@ -0,0 +1,85 @@
|
||||
using Fengling.Console.Models.Entities;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Fengling.Console.Datas;
|
||||
|
||||
public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
|
||||
: IdentityDbContext<ApplicationUser, ApplicationRole, long>(options)
|
||||
{
|
||||
public DbSet<Tenant> Tenants { get; set; }
|
||||
public DbSet<AccessLog> AccessLogs { get; set; }
|
||||
public DbSet<AuditLog> AuditLogs { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
|
||||
builder.Entity<ApplicationUser>(entity =>
|
||||
{
|
||||
entity.Property(e => e.RealName).HasMaxLength(100);
|
||||
entity.Property(e => e.Phone).HasMaxLength(20);
|
||||
entity.HasIndex(e => e.Phone).IsUnique();
|
||||
|
||||
entity.OwnsOne(e => e.TenantInfo, navigationBuilder =>
|
||||
{
|
||||
navigationBuilder.Property(e => e.Id).HasColumnName("TenantId");
|
||||
navigationBuilder.Property(e => e.TenantId).HasColumnName("TenantCode");
|
||||
navigationBuilder.Property(e => e.Name).HasColumnName("TenantName");
|
||||
navigationBuilder.WithOwner();
|
||||
});
|
||||
});
|
||||
|
||||
builder.Entity<ApplicationRole>(entity => { entity.Property(e => e.Description).HasMaxLength(200); });
|
||||
|
||||
builder.Entity<Tenant>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.HasIndex(e => e.TenantId).IsUnique();
|
||||
entity.Property(e => e.TenantId).HasMaxLength(50);
|
||||
entity.Property(e => e.Name).HasMaxLength(100);
|
||||
entity.Property(e => e.ContactName).HasMaxLength(50);
|
||||
entity.Property(e => e.ContactEmail).HasMaxLength(100);
|
||||
entity.Property(e => e.ContactPhone).HasMaxLength(20);
|
||||
entity.Property(e => e.Status).HasMaxLength(20);
|
||||
entity.Property(e => e.Description).HasMaxLength(500);
|
||||
});
|
||||
|
||||
builder.Entity<AccessLog>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.HasIndex(e => e.CreatedAt);
|
||||
entity.HasIndex(e => e.UserName);
|
||||
entity.HasIndex(e => e.TenantId);
|
||||
entity.HasIndex(e => e.Action);
|
||||
entity.HasIndex(e => e.Status);
|
||||
entity.Property(e => e.UserName).HasMaxLength(50);
|
||||
entity.Property(e => e.TenantId).HasMaxLength(50);
|
||||
entity.Property(e => e.Action).HasMaxLength(20);
|
||||
entity.Property(e => e.Resource).HasMaxLength(200);
|
||||
entity.Property(e => e.Method).HasMaxLength(10);
|
||||
entity.Property(e => e.IpAddress).HasMaxLength(50);
|
||||
entity.Property(e => e.UserAgent).HasMaxLength(500);
|
||||
entity.Property(e => e.Status).HasMaxLength(20);
|
||||
});
|
||||
|
||||
builder.Entity<AuditLog>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.HasIndex(e => e.CreatedAt);
|
||||
entity.HasIndex(e => e.Operator);
|
||||
entity.HasIndex(e => e.TenantId);
|
||||
entity.HasIndex(e => e.Operation);
|
||||
entity.HasIndex(e => e.Action);
|
||||
entity.Property(e => e.Operator).HasMaxLength(50);
|
||||
entity.Property(e => e.TenantId).HasMaxLength(50);
|
||||
entity.Property(e => e.Operation).HasMaxLength(20);
|
||||
entity.Property(e => e.Action).HasMaxLength(20);
|
||||
entity.Property(e => e.TargetType).HasMaxLength(50);
|
||||
entity.Property(e => e.TargetName).HasMaxLength(100);
|
||||
entity.Property(e => e.IpAddress).HasMaxLength(50);
|
||||
entity.Property(e => e.Description).HasMaxLength(500);
|
||||
entity.Property(e => e.Status).HasMaxLength(20);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -6,12 +6,21 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DocumentationFile>bin\Debug\net10.0\Fengling.Console.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DocumentationFile>bin\Release\net10.0\Fengling.Console.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
|
||||
<PackageReference Include="OpenIddict.Abstractions" />
|
||||
<PackageReference Include="OpenIddict.AspNetCore" />
|
||||
<PackageReference Include="OpenIddict.EntityFrameworkCore" />
|
||||
<PackageReference Include="OpenIddict.Server" />
|
||||
<PackageReference Include="OpenIddict.Server.AspNetCore" />
|
||||
@ -19,7 +28,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Fengling.AuthService\Fengling.AuthService.csproj" />
|
||||
<!-- <ProjectReference Include="..\Fengling.AuthService\Fengling.AuthService.csproj" />-->
|
||||
<ProjectReference Include="..\YarpGateway\YarpGateway.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
16
Models/Dtos/CreateClientDto.cs
Normal file
16
Models/Dtos/CreateClientDto.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace Fengling.Console.Models.Dtos;
|
||||
|
||||
public record CreateClientDto
|
||||
{
|
||||
public string ClientId { get; init; } = string.Empty;
|
||||
public string? ClientSecret { get; init; }
|
||||
public string DisplayName { get; init; } = string.Empty;
|
||||
public string[]? RedirectUris { get; init; }
|
||||
public string[]? PostLogoutRedirectUris { get; init; }
|
||||
public string[]? Scopes { get; init; }
|
||||
public string[]? GrantTypes { get; init; }
|
||||
public string? ClientType { get; init; }
|
||||
public string? ConsentType { get; init; }
|
||||
public string? Status { get; init; }
|
||||
public string? Description { get; init; }
|
||||
}
|
||||
89
Models/Dtos/GatewayDto.cs
Normal file
89
Models/Dtos/GatewayDto.cs
Normal file
@ -0,0 +1,89 @@
|
||||
namespace Fengling.Console.Models.Dtos;
|
||||
|
||||
public class GatewayServiceDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string ServicePrefix { get; set; } = "";
|
||||
public string ServiceName { get; set; } = "";
|
||||
public string Version { get; set; } = "v1";
|
||||
public string ClusterId { get; set; } = "";
|
||||
public string PathPattern { get; set; } = "";
|
||||
public string ServiceAddress { get; set; } = "";
|
||||
public string DestinationId { get; set; } = "";
|
||||
public int Weight { get; set; } = 1;
|
||||
public int InstanceCount { get; set; }
|
||||
public bool IsGlobal { get; set; }
|
||||
public string? TenantCode { get; set; }
|
||||
public int Status { get; set; } = 1;
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
public class CreateGatewayServiceDto
|
||||
{
|
||||
public string ServicePrefix { get; set; } = "";
|
||||
public string ServiceName { get; set; } = "";
|
||||
public string Version { get; set; } = "v1";
|
||||
public string ServiceAddress { get; set; } = "";
|
||||
public string DestinationId { get; set; } = "";
|
||||
public int Weight { get; set; } = 1;
|
||||
public bool IsGlobal { get; set; } = true;
|
||||
public string? TenantCode { get; set; }
|
||||
}
|
||||
|
||||
public class GatewayRouteDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string ServiceName { get; set; } = "";
|
||||
public string ClusterId { get; set; } = "";
|
||||
public string PathPattern { get; set; } = "";
|
||||
public int Priority { get; set; }
|
||||
public bool IsGlobal { get; set; }
|
||||
public string? TenantCode { get; set; }
|
||||
public int Status { get; set; }
|
||||
public int InstanceCount { get; set; }
|
||||
}
|
||||
|
||||
public class CreateGatewayRouteDto
|
||||
{
|
||||
public string ServiceName { get; set; } = "";
|
||||
public string ClusterId { get; set; } = "";
|
||||
public string PathPattern { get; set; } = "";
|
||||
public int Priority { get; set; } = 10;
|
||||
public bool IsGlobal { get; set; } = true;
|
||||
public string? TenantCode { get; set; }
|
||||
}
|
||||
|
||||
public class GatewayInstanceDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string ClusterId { get; set; } = "";
|
||||
public string DestinationId { get; set; } = "";
|
||||
public string Address { get; set; } = "";
|
||||
public int Weight { get; set; } = 1;
|
||||
public int Health { get; set; } = 1;
|
||||
public int Status { get; set; } = 1;
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
public class CreateGatewayInstanceDto
|
||||
{
|
||||
public string ClusterId { get; set; } = "";
|
||||
public string DestinationId { get; set; } = "";
|
||||
public string Address { get; set; } = "";
|
||||
public int Weight { get; set; } = 1;
|
||||
}
|
||||
|
||||
public class GatewayStatisticsDto
|
||||
{
|
||||
public int TotalServices { get; set; }
|
||||
public int GlobalRoutes { get; set; }
|
||||
public int TenantRoutes { get; set; }
|
||||
public int TotalInstances { get; set; }
|
||||
public int HealthyInstances { get; set; }
|
||||
public List<GatewayServiceDto> RecentServices { get; set; } = new();
|
||||
}
|
||||
|
||||
public class GatewayUpdateWeightDto
|
||||
{
|
||||
public int Weight { get; set; }
|
||||
}
|
||||
69
Models/Dtos/OAuthClientDto.cs
Normal file
69
Models/Dtos/OAuthClientDto.cs
Normal file
@ -0,0 +1,69 @@
|
||||
namespace Fengling.Console.Models.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// OAuth客户端详细信息DTO
|
||||
/// </summary>
|
||||
public class OAuthClientDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 客户端唯一标识符
|
||||
/// </summary>
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 客户端ID,用于OAuth授权流程中的标识
|
||||
/// </summary>
|
||||
public string ClientId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 客户端显示名称
|
||||
/// </summary>
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 客户端描述信息
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 回调地址列表,用于OAuth授权回调
|
||||
/// </summary>
|
||||
public string[] RedirectUris { get; set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 注销回调地址列表
|
||||
/// </summary>
|
||||
public string[] PostLogoutRedirectUris { get; set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 授权范围列表
|
||||
/// </summary>
|
||||
public string[] Scopes { get; set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 授权类型列表
|
||||
/// </summary>
|
||||
public string[] GrantTypes { get; set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 客户端类型:public(公开客户端)或 confidential(机密客户端)
|
||||
/// </summary>
|
||||
public string? ClientType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 授权同意类型:implicit、explicit或system
|
||||
/// </summary>
|
||||
public string? ConsentType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 客户端状态:active、inactive或suspended
|
||||
/// </summary>
|
||||
public string Status { get; set; } = "active";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OAuth客户端列表分页结果DTO
|
||||
/// </summary>
|
||||
public class OAuthClientListDto : PagedResultDto<OAuthClientDto>
|
||||
{
|
||||
}
|
||||
10
Models/Dtos/OAuthClientQueryDto.cs
Normal file
10
Models/Dtos/OAuthClientQueryDto.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Fengling.Console.Models.Dtos;
|
||||
|
||||
public class OAuthClientQueryDto : PaginationQueryDto
|
||||
{
|
||||
public string? DisplayName { get; set; }
|
||||
|
||||
public string? ClientId { get; set; }
|
||||
|
||||
public string? Status { get; set; }
|
||||
}
|
||||
29
Models/Dtos/PaginationDto.cs
Normal file
29
Models/Dtos/PaginationDto.cs
Normal file
@ -0,0 +1,29 @@
|
||||
namespace Fengling.Console.Models.Dtos;
|
||||
|
||||
public class PaginationQueryDto
|
||||
{
|
||||
public int Page { get; set; } = 1;
|
||||
|
||||
public int PageSize { get; set; } = 10;
|
||||
|
||||
public string? SortBy { get; set; }
|
||||
|
||||
public string? SortOrder { get; set; }
|
||||
}
|
||||
|
||||
public class PagedResultDto<T>
|
||||
{
|
||||
public List<T> Items { get; set; } = new();
|
||||
|
||||
public int TotalCount { get; set; }
|
||||
|
||||
public int Page { get; set; }
|
||||
|
||||
public int PageSize { get; set; }
|
||||
|
||||
public int TotalPages => PageSize > 0 ? (int)Math.Ceiling((double)TotalCount / PageSize) : 0;
|
||||
|
||||
public bool HasNextPage => Page < TotalPages;
|
||||
|
||||
public bool HasPreviousPage => Page > 1;
|
||||
}
|
||||
8
Models/Dtos/RoleQueryDto.cs
Normal file
8
Models/Dtos/RoleQueryDto.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Fengling.Console.Models.Dtos;
|
||||
|
||||
public class RoleQueryDto : PaginationQueryDto
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
|
||||
public string? TenantId { get; set; }
|
||||
}
|
||||
10
Models/Dtos/TenantQueryDto.cs
Normal file
10
Models/Dtos/TenantQueryDto.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Fengling.Console.Models.Dtos;
|
||||
|
||||
public class TenantQueryDto : PaginationQueryDto
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
|
||||
public string? TenantId { get; set; }
|
||||
|
||||
public string? Status { get; set; }
|
||||
}
|
||||
14
Models/Dtos/UpdateClientDto.cs
Normal file
14
Models/Dtos/UpdateClientDto.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace Fengling.Console.Models.Dtos;
|
||||
|
||||
public record UpdateClientDto
|
||||
{
|
||||
public string? DisplayName { get; init; }
|
||||
public string[]? RedirectUris { get; init; }
|
||||
public string[]? PostLogoutRedirectUris { get; init; }
|
||||
public string[]? Scopes { get; init; }
|
||||
public string[]? GrantTypes { get; init; }
|
||||
public string? ClientType { get; init; }
|
||||
public string? ConsentType { get; init; }
|
||||
public string? Status { get; init; }
|
||||
public string? Description { get; init; }
|
||||
}
|
||||
10
Models/Dtos/UserQueryDto.cs
Normal file
10
Models/Dtos/UserQueryDto.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Fengling.Console.Models.Dtos;
|
||||
|
||||
public class UserQueryDto : PaginationQueryDto
|
||||
{
|
||||
public string? UserName { get; set; }
|
||||
|
||||
public string? Email { get; set; }
|
||||
|
||||
public string? TenantId { get; set; }
|
||||
}
|
||||
43
Models/Entities/AccessLog.cs
Normal file
43
Models/Entities/AccessLog.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Fengling.Console.Models.Entities;
|
||||
|
||||
public class AccessLog
|
||||
{
|
||||
[Key]
|
||||
public long Id { get; set; }
|
||||
|
||||
[MaxLength(50)]
|
||||
public string? UserName { get; set; }
|
||||
|
||||
[MaxLength(50)]
|
||||
public string? TenantId { get; set; }
|
||||
|
||||
[MaxLength(20)]
|
||||
public string Action { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(200)]
|
||||
public string? Resource { get; set; }
|
||||
|
||||
[MaxLength(10)]
|
||||
public string? Method { get; set; }
|
||||
|
||||
[MaxLength(50)]
|
||||
public string? IpAddress { get; set; }
|
||||
|
||||
[MaxLength(500)]
|
||||
public string? UserAgent { get; set; }
|
||||
|
||||
[MaxLength(20)]
|
||||
public string Status { get; set; } = "success";
|
||||
|
||||
public int Duration { get; set; }
|
||||
|
||||
public string? RequestData { get; set; }
|
||||
|
||||
public string? ResponseData { get; set; }
|
||||
|
||||
public string? ErrorMessage { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
13
Models/Entities/ApplicationRole.cs
Normal file
13
Models/Entities/ApplicationRole.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Fengling.Console.Models.Entities;
|
||||
|
||||
public class ApplicationRole : IdentityRole<long>
|
||||
{
|
||||
public string? Description { get; set; }
|
||||
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
|
||||
public long? TenantId { get; set; }
|
||||
public bool IsSystem { get; set; }
|
||||
public string? DisplayName { get; set; }
|
||||
public List<string>? Permissions { get; set; }
|
||||
}
|
||||
13
Models/Entities/ApplicationUser.cs
Normal file
13
Models/Entities/ApplicationUser.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Fengling.Console.Models.Entities;
|
||||
|
||||
public class ApplicationUser : IdentityUser<long>
|
||||
{
|
||||
public string? RealName { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public TenantInfo TenantInfo { get; set; } = null!;
|
||||
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? UpdatedTime { get; set; }
|
||||
public bool IsDeleted { get; set; }
|
||||
}
|
||||
47
Models/Entities/AuditLog.cs
Normal file
47
Models/Entities/AuditLog.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Fengling.Console.Models.Entities;
|
||||
|
||||
public class AuditLog
|
||||
{
|
||||
[Key]
|
||||
public long Id { get; set; }
|
||||
|
||||
[MaxLength(50)]
|
||||
[Required]
|
||||
public string Operator { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(50)]
|
||||
public string? TenantId { get; set; }
|
||||
|
||||
[MaxLength(20)]
|
||||
public string Operation { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(20)]
|
||||
public string Action { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(50)]
|
||||
public string? TargetType { get; set; }
|
||||
|
||||
public long? TargetId { get; set; }
|
||||
|
||||
[MaxLength(100)]
|
||||
public string? TargetName { get; set; }
|
||||
|
||||
[MaxLength(50)]
|
||||
public string IpAddress { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(500)]
|
||||
public string? Description { get; set; }
|
||||
|
||||
public string? OldValue { get; set; }
|
||||
|
||||
public string? NewValue { get; set; }
|
||||
|
||||
public string? ErrorMessage { get; set; }
|
||||
|
||||
[MaxLength(20)]
|
||||
public string Status { get; set; } = "success";
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
63
Models/Entities/Tenant.cs
Normal file
63
Models/Entities/Tenant.cs
Normal file
@ -0,0 +1,63 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Fengling.Console.Models.Entities;
|
||||
|
||||
public class Tenant
|
||||
{
|
||||
private long _id;
|
||||
private string _tenantId;
|
||||
private string _name;
|
||||
|
||||
[Key]
|
||||
public long Id
|
||||
{
|
||||
get => _id;
|
||||
set => _id = value;
|
||||
}
|
||||
|
||||
[MaxLength(50)]
|
||||
[Required]
|
||||
public string TenantId
|
||||
{
|
||||
get => _tenantId;
|
||||
set => _tenantId = value;
|
||||
}
|
||||
|
||||
[MaxLength(100)]
|
||||
[Required]
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => _name = value;
|
||||
}
|
||||
|
||||
[MaxLength(50)]
|
||||
[Required]
|
||||
public string ContactName { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(100)]
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string ContactEmail { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(20)]
|
||||
public string? ContactPhone { get; set; }
|
||||
|
||||
public int? MaxUsers { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
[MaxLength(500)]
|
||||
public string? Description { get; set; }
|
||||
|
||||
[MaxLength(20)]
|
||||
public string Status { get; set; } = "active";
|
||||
|
||||
public DateTime? ExpiresAt { get; set; }
|
||||
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
public bool IsDeleted { get; set; }
|
||||
|
||||
public TenantInfo Info => new(Id, TenantId, Name);
|
||||
}
|
||||
3
Models/Entities/TenantInfo.cs
Normal file
3
Models/Entities/TenantInfo.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace Fengling.Console.Models.Entities;
|
||||
|
||||
public record TenantInfo(long Id, string TenantId, string Name);
|
||||
43
Program.cs
43
Program.cs
@ -1,6 +1,4 @@
|
||||
using System.Reflection;
|
||||
using Fengling.AuthService.Data;
|
||||
using Fengling.AuthService.Models;
|
||||
using Fengling.Console.Repositories;
|
||||
using Fengling.Console.Services;
|
||||
using OpenIddict.Abstractions;
|
||||
@ -9,6 +7,10 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Text;
|
||||
using Fengling.Console.Datas;
|
||||
using Fengling.Console.Models.Entities;
|
||||
using OpenIddict.Validation.AspNetCore;
|
||||
using YarpGateway.Data;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@ -19,6 +21,9 @@ builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
||||
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"));
|
||||
});
|
||||
|
||||
builder.Services.AddDbContext<GatewayDbContext>(options =>
|
||||
options.UseNpgsql(builder.Configuration.GetConnectionString("GatewayConnection")));
|
||||
|
||||
builder.Services.AddIdentity<ApplicationUser, ApplicationRole>()
|
||||
.AddEntityFrameworkStores<ApplicationDbContext>()
|
||||
.AddDefaultTokenProviders();
|
||||
@ -39,27 +44,25 @@ builder.Services.AddOpenIddict()
|
||||
.AddCore(options =>
|
||||
{
|
||||
options.UseEntityFrameworkCore().UseDbContext<ApplicationDbContext>();
|
||||
});
|
||||
|
||||
var jwtKey = builder.Configuration["Jwt:Key"];
|
||||
var jwtIssuer = builder.Configuration["Jwt:Issuer"];
|
||||
var jwtAudience = builder.Configuration["Jwt:Audience"];
|
||||
|
||||
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddJwtBearer(options =>
|
||||
})
|
||||
.AddValidation(options =>
|
||||
{
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidateAudience = true,
|
||||
ValidateLifetime = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
ValidIssuer = jwtIssuer,
|
||||
ValidAudience = jwtAudience,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey ?? throw new InvalidOperationException("JWT Key is not configured")))
|
||||
};
|
||||
options.SetIssuer("http://localhost:5132/");
|
||||
|
||||
options.UseIntrospection()
|
||||
.SetClientId("fengling-api")
|
||||
.SetClientSecret("fengling-api-secret");
|
||||
|
||||
options.UseSystemNetHttp();
|
||||
|
||||
options.UseAspNetCore();
|
||||
});
|
||||
|
||||
builder.Services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
|
||||
});
|
||||
|
||||
builder.Services.AddAuthorization();
|
||||
|
||||
builder.Services.AddCors(options =>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using Fengling.AuthService.Models;
|
||||
using Fengling.Console.Models.Entities;
|
||||
|
||||
namespace Fengling.Console.Repositories;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using Fengling.AuthService.Models;
|
||||
using Fengling.Console.Models.Entities;
|
||||
|
||||
namespace Fengling.Console.Repositories;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using Fengling.AuthService.Models;
|
||||
using Fengling.Console.Models.Entities;
|
||||
|
||||
namespace Fengling.Console.Repositories;
|
||||
|
||||
|
||||
@ -1,36 +1,30 @@
|
||||
using Fengling.AuthService.Data;
|
||||
using Fengling.AuthService.Models;
|
||||
using Fengling.Console.Datas;
|
||||
using Fengling.Console.Models.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Fengling.Console.Repositories;
|
||||
|
||||
public class RoleRepository : IRoleRepository
|
||||
public class RoleRepository(ApplicationDbContext context) : IRoleRepository
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public RoleRepository(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<ApplicationRole?> GetByIdAsync(long id)
|
||||
{
|
||||
return await _context.Roles.FindAsync(id);
|
||||
return await context.Roles.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<ApplicationRole?> GetByNameAsync(string name)
|
||||
{
|
||||
return await _context.Roles.FirstOrDefaultAsync(r => r.Name == name);
|
||||
return await context.Roles.FirstOrDefaultAsync(r => r.Name == name);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ApplicationRole>> GetAllAsync()
|
||||
{
|
||||
return await _context.Roles.ToListAsync();
|
||||
return await context.Roles.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ApplicationRole>> GetPagedAsync(int page, int pageSize, string? name = null, string? tenantId = null)
|
||||
public async Task<IEnumerable<ApplicationRole>> GetPagedAsync(int page, int pageSize, string? name = null,
|
||||
string? tenantId = null)
|
||||
{
|
||||
var query = _context.Roles.AsQueryable();
|
||||
var query = context.Roles.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
@ -51,7 +45,7 @@ public class RoleRepository : IRoleRepository
|
||||
|
||||
public async Task<int> CountAsync(string? name = null, string? tenantId = null)
|
||||
{
|
||||
var query = _context.Roles.AsQueryable();
|
||||
var query = context.Roles.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
@ -68,19 +62,19 @@ public class RoleRepository : IRoleRepository
|
||||
|
||||
public async Task AddAsync(ApplicationRole role)
|
||||
{
|
||||
_context.Roles.Add(role);
|
||||
await _context.SaveChangesAsync();
|
||||
context.Roles.Add(role);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(ApplicationRole role)
|
||||
{
|
||||
_context.Roles.Update(role);
|
||||
await _context.SaveChangesAsync();
|
||||
context.Roles.Update(role);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(ApplicationRole role)
|
||||
{
|
||||
_context.Roles.Remove(role);
|
||||
await _context.SaveChangesAsync();
|
||||
context.Roles.Remove(role);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
@ -1,36 +1,30 @@
|
||||
using Fengling.AuthService.Data;
|
||||
using Fengling.AuthService.Models;
|
||||
using Fengling.Console.Datas;
|
||||
using Fengling.Console.Models.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Fengling.Console.Repositories;
|
||||
|
||||
public class TenantRepository : ITenantRepository
|
||||
public class TenantRepository(ApplicationDbContext context) : ITenantRepository
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public TenantRepository(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Tenant?> GetByIdAsync(long id)
|
||||
{
|
||||
return await _context.Tenants.FindAsync(id);
|
||||
return await context.Tenants.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<Tenant?> GetByTenantIdAsync(string tenantId)
|
||||
{
|
||||
return await _context.Tenants.FirstOrDefaultAsync(t => t.TenantId == tenantId);
|
||||
return await context.Tenants.FirstOrDefaultAsync(t => t.TenantId == tenantId);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Tenant>> GetAllAsync()
|
||||
{
|
||||
return await _context.Tenants.ToListAsync();
|
||||
return await context.Tenants.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Tenant>> GetPagedAsync(int page, int pageSize, string? name = null, string? tenantId = null, string? status = null)
|
||||
public async Task<IEnumerable<Tenant>> GetPagedAsync(int page, int pageSize, string? name = null,
|
||||
string? tenantId = null, string? status = null)
|
||||
{
|
||||
var query = _context.Tenants.AsQueryable();
|
||||
var query = context.Tenants.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
@ -56,7 +50,7 @@ public class TenantRepository : ITenantRepository
|
||||
|
||||
public async Task<int> CountAsync(string? name = null, string? tenantId = null, string? status = null)
|
||||
{
|
||||
var query = _context.Tenants.AsQueryable();
|
||||
var query = context.Tenants.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
@ -78,24 +72,24 @@ public class TenantRepository : ITenantRepository
|
||||
|
||||
public async Task AddAsync(Tenant tenant)
|
||||
{
|
||||
_context.Tenants.Add(tenant);
|
||||
await _context.SaveChangesAsync();
|
||||
context.Tenants.Add(tenant);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Tenant tenant)
|
||||
{
|
||||
_context.Tenants.Update(tenant);
|
||||
await _context.SaveChangesAsync();
|
||||
context.Tenants.Update(tenant);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(Tenant tenant)
|
||||
{
|
||||
_context.Tenants.Remove(tenant);
|
||||
await _context.SaveChangesAsync();
|
||||
context.Tenants.Remove(tenant);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<int> GetUserCountAsync(long tenantId)
|
||||
{
|
||||
return await _context.Users.CountAsync(u => u.TenantInfo.Id == tenantId && !u.IsDeleted);
|
||||
return await context.Users.CountAsync(u => u.TenantInfo.Id == tenantId && !u.IsDeleted);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
using Fengling.AuthService.Data;
|
||||
using Fengling.AuthService.Models;
|
||||
using Fengling.Console.Datas;
|
||||
using Fengling.Console.Models.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Fengling.Console.Repositories;
|
||||
|
||||
386
Services/GatewayService.cs
Normal file
386
Services/GatewayService.cs
Normal file
@ -0,0 +1,386 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using YarpGateway.Data;
|
||||
using YarpGateway.Models;
|
||||
using Fengling.Console.Models.Dtos;
|
||||
|
||||
namespace Fengling.Console.Services;
|
||||
|
||||
public interface IGatewayService
|
||||
{
|
||||
Task<GatewayStatisticsDto> GetStatisticsAsync();
|
||||
Task<List<GatewayServiceDto>> GetServicesAsync(bool globalOnly = false, string? tenantCode = null);
|
||||
Task<GatewayServiceDto?> GetServiceAsync(string serviceName, string? tenantCode = null);
|
||||
Task<GatewayServiceDto> RegisterServiceAsync(CreateGatewayServiceDto dto);
|
||||
Task<bool> UnregisterServiceAsync(string serviceName, string? tenantCode = null);
|
||||
Task<List<GatewayRouteDto>> GetRoutesAsync(bool globalOnly = false);
|
||||
Task<GatewayRouteDto> CreateRouteAsync(CreateGatewayRouteDto dto);
|
||||
Task<List<GatewayInstanceDto>> GetInstancesAsync(string clusterId);
|
||||
Task<GatewayInstanceDto> AddInstanceAsync(CreateGatewayInstanceDto dto);
|
||||
Task<bool> RemoveInstanceAsync(long instanceId);
|
||||
Task<bool> UpdateInstanceWeightAsync(long instanceId, int weight);
|
||||
Task ReloadGatewayAsync();
|
||||
}
|
||||
|
||||
public class GatewayService : IGatewayService
|
||||
{
|
||||
private readonly IDbContextFactory<GatewayDbContext> _dbContextFactory;
|
||||
private readonly ILogger<GatewayService> _logger;
|
||||
|
||||
public GatewayService(IDbContextFactory<GatewayDbContext> dbContextFactory, ILogger<GatewayService> logger)
|
||||
{
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<GatewayStatisticsDto> GetStatisticsAsync()
|
||||
{
|
||||
await using var db = await _dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
var routes = await db.TenantRoutes.Where(r => !r.IsDeleted).ToListAsync();
|
||||
var instances = await db.ServiceInstances.Where(i => !i.IsDeleted).ToListAsync();
|
||||
|
||||
return new GatewayStatisticsDto
|
||||
{
|
||||
TotalServices = routes.Select(r => r.ServiceName).Distinct().Count(),
|
||||
GlobalRoutes = routes.Count(r => r.IsGlobal),
|
||||
TenantRoutes = routes.Count(r => !r.IsGlobal),
|
||||
TotalInstances = instances.Count,
|
||||
HealthyInstances = instances.Count(i => i.Health == 1),
|
||||
RecentServices = routes
|
||||
.OrderByDescending(r => r.CreatedTime)
|
||||
.Take(5)
|
||||
.Select(MapToServiceDto)
|
||||
.ToList()
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<List<GatewayServiceDto>> GetServicesAsync(bool globalOnly = false, string? tenantCode = null)
|
||||
{
|
||||
await using var db = await _dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
var query = db.TenantRoutes.Where(r => !r.IsDeleted);
|
||||
|
||||
if (globalOnly)
|
||||
query = query.Where(r => r.IsGlobal);
|
||||
else if (!string.IsNullOrEmpty(tenantCode))
|
||||
query = query.Where(r => r.TenantCode == tenantCode);
|
||||
|
||||
var routes = await query.OrderByDescending(r => r.CreatedTime).ToListAsync();
|
||||
var clusters = routes.Select(r => r.ClusterId).Distinct().ToList();
|
||||
|
||||
var instances = await db.ServiceInstances
|
||||
.Where(i => clusters.Contains(i.ClusterId) && !i.IsDeleted)
|
||||
.GroupBy(i => i.ClusterId)
|
||||
.ToDictionaryAsync(g => g.Key, g => g.Count());
|
||||
|
||||
return routes.Select(r => MapToServiceDto(r, instances.GetValueOrDefault(r.ClusterId, 0))).ToList();
|
||||
}
|
||||
|
||||
public async Task<GatewayServiceDto?> GetServiceAsync(string serviceName, string? tenantCode = null)
|
||||
{
|
||||
await using var db = await _dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
var route = await db.TenantRoutes
|
||||
.FirstOrDefaultAsync(r =>
|
||||
r.ServiceName == serviceName &&
|
||||
r.IsDeleted == false &&
|
||||
(r.IsGlobal || r.TenantCode == tenantCode));
|
||||
|
||||
if (route == null) return null;
|
||||
|
||||
var instances = await db.ServiceInstances
|
||||
.CountAsync(i => i.ClusterId == route.ClusterId && !i.IsDeleted);
|
||||
|
||||
return MapToServiceDto(route, instances);
|
||||
}
|
||||
|
||||
public async Task<GatewayServiceDto> RegisterServiceAsync(CreateGatewayServiceDto dto)
|
||||
{
|
||||
await using var db = await _dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
var clusterId = $"{dto.ServicePrefix}-service";
|
||||
var pathPattern = $"/{dto.ServicePrefix}/{dto.Version}/{{**path}}";
|
||||
var destinationId = string.IsNullOrEmpty(dto.DestinationId)
|
||||
? $"{dto.ServicePrefix}-1"
|
||||
: dto.DestinationId;
|
||||
|
||||
// Check if route already exists
|
||||
var existingRoute = await db.TenantRoutes
|
||||
.FirstOrDefaultAsync(r =>
|
||||
r.ServiceName == dto.ServicePrefix &&
|
||||
r.IsGlobal == dto.IsGlobal &&
|
||||
(dto.IsGlobal || r.TenantCode == dto.TenantCode));
|
||||
|
||||
if (existingRoute != null)
|
||||
{
|
||||
throw new InvalidOperationException($"Service {dto.ServicePrefix} already registered");
|
||||
}
|
||||
|
||||
// Add instance
|
||||
var instanceId = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
var instance = new GwServiceInstance
|
||||
{
|
||||
Id = instanceId,
|
||||
ClusterId = clusterId,
|
||||
DestinationId = destinationId,
|
||||
Address = dto.ServiceAddress,
|
||||
Weight = dto.Weight,
|
||||
Health = 1,
|
||||
Status = 1,
|
||||
CreatedTime = DateTime.UtcNow
|
||||
};
|
||||
await db.ServiceInstances.AddAsync(instance);
|
||||
|
||||
// Add route
|
||||
var routeId = instanceId + 1;
|
||||
var route = new GwTenantRoute
|
||||
{
|
||||
Id = routeId,
|
||||
TenantCode = dto.IsGlobal ? "" : dto.TenantCode ?? "",
|
||||
ServiceName = dto.ServicePrefix,
|
||||
ClusterId = clusterId,
|
||||
PathPattern = pathPattern,
|
||||
Priority = dto.IsGlobal ? 0 : 10,
|
||||
Status = 1,
|
||||
IsGlobal = dto.IsGlobal,
|
||||
CreatedTime = DateTime.UtcNow
|
||||
};
|
||||
await db.TenantRoutes.AddAsync(route);
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Registered service {Service} at {Address}", dto.ServicePrefix, dto.ServiceAddress);
|
||||
|
||||
return MapToServiceDto(route, 1);
|
||||
}
|
||||
|
||||
public async Task<bool> UnregisterServiceAsync(string serviceName, string? tenantCode = null)
|
||||
{
|
||||
await using var db = await _dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
var route = await db.TenantRoutes
|
||||
.FirstOrDefaultAsync(r =>
|
||||
r.ServiceName == serviceName &&
|
||||
r.IsDeleted == false &&
|
||||
(r.IsGlobal || r.TenantCode == tenantCode));
|
||||
|
||||
if (route == null) return false;
|
||||
|
||||
// Soft delete route
|
||||
route.IsDeleted = true;
|
||||
route.UpdatedTime = DateTime.UtcNow;
|
||||
|
||||
// Soft delete instances
|
||||
var instances = await db.ServiceInstances
|
||||
.Where(i => i.ClusterId == route.ClusterId && !i.IsDeleted)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var instance in instances)
|
||||
{
|
||||
instance.IsDeleted = true;
|
||||
instance.UpdatedTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Unregistered service {Service}", serviceName);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<List<GatewayRouteDto>> GetRoutesAsync(bool globalOnly = false)
|
||||
{
|
||||
await using var db = await _dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
var query = db.TenantRoutes.Where(r => !r.IsDeleted);
|
||||
|
||||
if (globalOnly)
|
||||
query = query.Where(r => r.IsGlobal);
|
||||
|
||||
var routes = await query.OrderByDescending(r => r.Priority).ToListAsync();
|
||||
var clusters = routes.Select(r => r.ClusterId).Distinct().ToList();
|
||||
|
||||
var instances = await db.ServiceInstances
|
||||
.Where(i => clusters.Contains(i.ClusterId) && !i.IsDeleted)
|
||||
.GroupBy(i => i.ClusterId)
|
||||
.ToDictionaryAsync(g => g.Key, g => g.Count());
|
||||
|
||||
return routes.Select(r => new GatewayRouteDto
|
||||
{
|
||||
Id = r.Id,
|
||||
ServiceName = r.ServiceName,
|
||||
ClusterId = r.ClusterId,
|
||||
PathPattern = r.PathPattern,
|
||||
Priority = r.Priority,
|
||||
IsGlobal = r.IsGlobal,
|
||||
TenantCode = r.TenantCode,
|
||||
Status = r.Status,
|
||||
InstanceCount = instances.GetValueOrDefault(r.ClusterId, 0)
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public async Task<GatewayRouteDto> CreateRouteAsync(CreateGatewayRouteDto dto)
|
||||
{
|
||||
await using var db = await _dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
var existing = await db.TenantRoutes
|
||||
.FirstOrDefaultAsync(r =>
|
||||
r.ServiceName == dto.ServiceName &&
|
||||
r.IsGlobal == dto.IsGlobal &&
|
||||
(dto.IsGlobal || r.TenantCode == dto.TenantCode));
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
throw new InvalidOperationException($"Route for {dto.ServiceName} already exists");
|
||||
}
|
||||
|
||||
var route = new GwTenantRoute
|
||||
{
|
||||
Id = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
||||
TenantCode = dto.IsGlobal ? "" : dto.TenantCode ?? "",
|
||||
ServiceName = dto.ServiceName,
|
||||
ClusterId = dto.ClusterId,
|
||||
PathPattern = dto.PathPattern,
|
||||
Priority = dto.Priority,
|
||||
Status = 1,
|
||||
IsGlobal = dto.IsGlobal,
|
||||
CreatedTime = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await db.TenantRoutes.AddAsync(route);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return new GatewayRouteDto
|
||||
{
|
||||
Id = route.Id,
|
||||
ServiceName = route.ServiceName,
|
||||
ClusterId = route.ClusterId,
|
||||
PathPattern = route.PathPattern,
|
||||
Priority = route.Priority,
|
||||
IsGlobal = route.IsGlobal,
|
||||
TenantCode = route.TenantCode,
|
||||
Status = route.Status,
|
||||
InstanceCount = 0
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<List<GatewayInstanceDto>> GetInstancesAsync(string clusterId)
|
||||
{
|
||||
await using var db = await _dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
var instances = await db.ServiceInstances
|
||||
.Where(i => i.ClusterId == clusterId && !i.IsDeleted)
|
||||
.OrderByDescending(i => i.Weight)
|
||||
.ToListAsync();
|
||||
|
||||
return instances.Select(i => new GatewayInstanceDto
|
||||
{
|
||||
Id = i.Id,
|
||||
ClusterId = i.ClusterId,
|
||||
DestinationId = i.DestinationId,
|
||||
Address = i.Address,
|
||||
Weight = i.Weight,
|
||||
Health = i.Health,
|
||||
Status = i.Status,
|
||||
CreatedAt = i.CreatedTime
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public async Task<GatewayInstanceDto> AddInstanceAsync(CreateGatewayInstanceDto dto)
|
||||
{
|
||||
await using var db = await _dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
var existing = await db.ServiceInstances
|
||||
.FirstOrDefaultAsync(i =>
|
||||
i.ClusterId == dto.ClusterId &&
|
||||
i.DestinationId == dto.DestinationId &&
|
||||
!i.IsDeleted);
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
throw new InvalidOperationException($"Instance {dto.DestinationId} already exists in cluster {dto.ClusterId}");
|
||||
}
|
||||
|
||||
var instance = new GwServiceInstance
|
||||
{
|
||||
Id = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
||||
ClusterId = dto.ClusterId,
|
||||
DestinationId = dto.DestinationId,
|
||||
Address = dto.Address,
|
||||
Weight = dto.Weight,
|
||||
Health = 1,
|
||||
Status = 1,
|
||||
CreatedTime = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await db.ServiceInstances.AddAsync(instance);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return new GatewayInstanceDto
|
||||
{
|
||||
Id = instance.Id,
|
||||
ClusterId = instance.ClusterId,
|
||||
DestinationId = instance.DestinationId,
|
||||
Address = instance.Address,
|
||||
Weight = instance.Weight,
|
||||
Health = instance.Health,
|
||||
Status = instance.Status,
|
||||
CreatedAt = instance.CreatedTime
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<bool> RemoveInstanceAsync(long instanceId)
|
||||
{
|
||||
await using var db = await _dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
var instance = await db.ServiceInstances.FindAsync(instanceId);
|
||||
if (instance == null) return false;
|
||||
|
||||
instance.IsDeleted = true;
|
||||
instance.UpdatedTime = DateTime.UtcNow;
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateInstanceWeightAsync(long instanceId, int weight)
|
||||
{
|
||||
await using var db = await _dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
var instance = await db.ServiceInstances.FindAsync(instanceId);
|
||||
if (instance == null) return false;
|
||||
|
||||
instance.Weight = weight;
|
||||
instance.UpdatedTime = DateTime.UtcNow;
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task ReloadGatewayAsync()
|
||||
{
|
||||
_logger.LogInformation("Gateway configuration reloaded");
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static GatewayServiceDto MapToServiceDto(GwTenantRoute route, int instanceCount = 0)
|
||||
{
|
||||
return new GatewayServiceDto
|
||||
{
|
||||
Id = route.Id,
|
||||
ServicePrefix = route.ServiceName,
|
||||
ServiceName = route.ServiceName,
|
||||
ClusterId = route.ClusterId,
|
||||
PathPattern = route.PathPattern,
|
||||
ServiceAddress = "",
|
||||
DestinationId = "",
|
||||
Weight = 1,
|
||||
InstanceCount = instanceCount,
|
||||
IsGlobal = route.IsGlobal,
|
||||
TenantCode = route.TenantCode,
|
||||
Status = route.Status,
|
||||
CreatedAt = route.CreatedTime
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
using Fengling.Console.Models.Dtos;
|
||||
using OpenIddict.Abstractions;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
@ -6,43 +7,15 @@ namespace Fengling.Console.Services;
|
||||
|
||||
public interface IOAuthClientService
|
||||
{
|
||||
Task<(IEnumerable<object> Items, int TotalCount)> GetClientsAsync(int page, int pageSize, string? displayName = null, string? clientId = null, string? status = null);
|
||||
Task<object?> GetClientAsync(string id);
|
||||
Task<object> CreateClientAsync(CreateClientDto dto);
|
||||
Task<(IEnumerable<OAuthClientDto> Items, int TotalCount)> GetClientsAsync(int page, int pageSize, string? displayName = null, string? clientId = null, string? status = null);
|
||||
Task<OAuthClientDto?> GetClientAsync(string id);
|
||||
Task<OAuthClientDto> CreateClientAsync(CreateClientDto dto);
|
||||
Task<object> GenerateSecretAsync(string id);
|
||||
Task<object> UpdateClientAsync(string id, UpdateClientDto dto);
|
||||
Task DeleteClientAsync(string id);
|
||||
object GetClientOptions();
|
||||
}
|
||||
|
||||
public record CreateClientDto
|
||||
{
|
||||
public string ClientId { get; init; } = string.Empty;
|
||||
public string? ClientSecret { get; init; }
|
||||
public string DisplayName { get; init; } = string.Empty;
|
||||
public string[]? RedirectUris { get; init; }
|
||||
public string[]? PostLogoutRedirectUris { get; init; }
|
||||
public string[]? Scopes { get; init; }
|
||||
public string[]? GrantTypes { get; init; }
|
||||
public string? ClientType { get; init; }
|
||||
public string? ConsentType { get; init; }
|
||||
public string? Status { get; init; }
|
||||
public string? Description { get; init; }
|
||||
}
|
||||
|
||||
public record UpdateClientDto
|
||||
{
|
||||
public string? DisplayName { get; init; }
|
||||
public string[]? RedirectUris { get; init; }
|
||||
public string[]? PostLogoutRedirectUris { get; init; }
|
||||
public string[]? Scopes { get; init; }
|
||||
public string[]? GrantTypes { get; init; }
|
||||
public string? ClientType { get; init; }
|
||||
public string? ConsentType { get; init; }
|
||||
public string? Status { get; init; }
|
||||
public string? Description { get; init; }
|
||||
}
|
||||
|
||||
public class OAuthClientService : IOAuthClientService
|
||||
{
|
||||
private readonly IOpenIddictApplicationManager _applicationManager;
|
||||
@ -62,10 +35,10 @@ public class OAuthClientService : IOAuthClientService
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<object> Items, int TotalCount)> GetClientsAsync(int page, int pageSize, string? displayName = null, string? clientId = null, string? status = null)
|
||||
public async Task<(IEnumerable<OAuthClientDto> Items, int TotalCount)> GetClientsAsync(int page, int pageSize, string? displayName = null, string? clientId = null, string? status = null)
|
||||
{
|
||||
var applications = _applicationManager.ListAsync();
|
||||
var clientList = new List<object>();
|
||||
var clientList = new List<OAuthClientDto>();
|
||||
|
||||
await foreach (var application in applications)
|
||||
{
|
||||
@ -83,28 +56,31 @@ public class OAuthClientService : IOAuthClientService
|
||||
var permissions = await _applicationManager.GetPermissionsAsync(application);
|
||||
var redirectUris = await _applicationManager.GetRedirectUrisAsync(application);
|
||||
var postLogoutRedirectUris = await _applicationManager.GetPostLogoutRedirectUrisAsync(application);
|
||||
var applicationId = await _applicationManager.GetIdAsync(application);
|
||||
|
||||
clientList.Add(new
|
||||
clientList.Add(new OAuthClientDto
|
||||
{
|
||||
id = application,
|
||||
clientId = clientIdValue,
|
||||
displayName = displayNameValue,
|
||||
redirectUris = redirectUris.ToArray(),
|
||||
postLogoutRedirectUris = postLogoutRedirectUris.ToArray(),
|
||||
scopes = permissions
|
||||
Id = applicationId ?? clientIdValue ?? string.Empty,
|
||||
ClientId = clientIdValue ?? string.Empty,
|
||||
DisplayName = displayNameValue ?? string.Empty,
|
||||
RedirectUris = redirectUris.ToArray(),
|
||||
PostLogoutRedirectUris = postLogoutRedirectUris.ToArray(),
|
||||
Scopes = permissions
|
||||
.Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.Scope))
|
||||
.Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.Scope.Length)),
|
||||
grantTypes = permissions
|
||||
.Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.Scope.Length))
|
||||
.ToArray(),
|
||||
GrantTypes = permissions
|
||||
.Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.GrantType))
|
||||
.Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.GrantType.Length)),
|
||||
clientType = clientType?.ToString(),
|
||||
consentType = consentType?.ToString(),
|
||||
status = "active"
|
||||
.Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.GrantType.Length))
|
||||
.ToArray(),
|
||||
ClientType = clientType?.ToString(),
|
||||
ConsentType = consentType?.ToString(),
|
||||
Status = "active"
|
||||
});
|
||||
}
|
||||
|
||||
var sortedClients = clientList
|
||||
.OrderByDescending(c => (c as dynamic).clientId)
|
||||
.OrderByDescending(c => c.ClientId)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToList();
|
||||
@ -112,7 +88,7 @@ public class OAuthClientService : IOAuthClientService
|
||||
return (sortedClients, clientList.Count);
|
||||
}
|
||||
|
||||
public async Task<object?> GetClientAsync(string id)
|
||||
public async Task<OAuthClientDto?> GetClientAsync(string id)
|
||||
{
|
||||
var application = await _applicationManager.FindByIdAsync(id);
|
||||
if (application == null)
|
||||
@ -128,26 +104,28 @@ public class OAuthClientService : IOAuthClientService
|
||||
var redirectUris = await _applicationManager.GetRedirectUrisAsync(application);
|
||||
var postLogoutRedirectUris = await _applicationManager.GetPostLogoutRedirectUrisAsync(application);
|
||||
|
||||
return new
|
||||
return new OAuthClientDto
|
||||
{
|
||||
id = id,
|
||||
clientId = clientIdValue,
|
||||
displayName = displayNameValue,
|
||||
redirectUris = redirectUris.ToArray(),
|
||||
postLogoutRedirectUris = postLogoutRedirectUris.ToArray(),
|
||||
scopes = permissions
|
||||
Id = id,
|
||||
ClientId = clientIdValue ?? string.Empty,
|
||||
DisplayName = displayNameValue ?? string.Empty,
|
||||
RedirectUris = redirectUris.ToArray(),
|
||||
PostLogoutRedirectUris = postLogoutRedirectUris.ToArray(),
|
||||
Scopes = permissions
|
||||
.Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.Scope))
|
||||
.Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.Scope.Length)),
|
||||
grantTypes = permissions
|
||||
.Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.Scope.Length))
|
||||
.ToArray(),
|
||||
GrantTypes = permissions
|
||||
.Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.GrantType))
|
||||
.Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.GrantType.Length)),
|
||||
clientType = clientType?.ToString(),
|
||||
consentType = consentType?.ToString(),
|
||||
status = "active"
|
||||
.Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.GrantType.Length))
|
||||
.ToArray(),
|
||||
ClientType = clientType?.ToString(),
|
||||
ConsentType = consentType?.ToString(),
|
||||
Status = "active"
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<object> CreateClientAsync(CreateClientDto dto)
|
||||
public async Task<OAuthClientDto> CreateClientAsync(CreateClientDto dto)
|
||||
{
|
||||
var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132";
|
||||
var token = await GetAuthTokenAsync();
|
||||
@ -182,7 +160,7 @@ public class OAuthClientService : IOAuthClientService
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize<object>(content);
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize<OAuthClientDto>(content);
|
||||
|
||||
_logger.LogInformation("Created OAuth client {ClientId}", dto.ClientId);
|
||||
|
||||
|
||||
@ -1,20 +1,23 @@
|
||||
using Fengling.AuthService.Data;
|
||||
using Fengling.AuthService.Models;
|
||||
using Fengling.Console.Models.Dtos;
|
||||
using Fengling.Console.Repositories;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using System.Security.Claims;
|
||||
using Fengling.Console.Datas;
|
||||
using Fengling.Console.Models.Entities;
|
||||
|
||||
namespace Fengling.Console.Services;
|
||||
|
||||
public interface IRoleService
|
||||
{
|
||||
Task<(IEnumerable<RoleDto> Items, int TotalCount)> GetRolesAsync(int page, int pageSize, string? name = null, string? tenantId = null);
|
||||
Task<(IEnumerable<RoleDto> Items, int TotalCount)> GetRolesAsync(int page, int pageSize, string? name = null,
|
||||
string? tenantId = null);
|
||||
|
||||
Task<RoleDto?> GetRoleAsync(long id);
|
||||
Task<IEnumerable<UserDto>> GetRoleUsersAsync(long id);
|
||||
Task<RoleDto> CreateRoleAsync(CreateRoleDto dto);
|
||||
Task<RoleDto> UpdateRoleAsync(long id, UpdateRoleDto dto);
|
||||
Task DeleteRoleAsync(long id);
|
||||
Task AddUserToRoleAsync(long roleId, long userId);
|
||||
Task RemoveUserFromRoleAsync(long roleId, long userId);
|
||||
}
|
||||
|
||||
@ -40,7 +43,8 @@ public class RoleService : IRoleService
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<RoleDto> Items, int TotalCount)> GetRolesAsync(int page, int pageSize, string? name = null, string? tenantId = null)
|
||||
public async Task<(IEnumerable<RoleDto> Items, int TotalCount)> GetRolesAsync(int page, int pageSize,
|
||||
string? name = null, string? tenantId = null)
|
||||
{
|
||||
var roles = await _repository.GetPagedAsync(page, pageSize, name, tenantId);
|
||||
var totalCount = await _repository.CountAsync(name, tenantId);
|
||||
@ -136,7 +140,8 @@ public class RoleService : IRoleService
|
||||
throw new InvalidOperationException(string.Join(", ", result.Errors.Select(e => e.Description)));
|
||||
}
|
||||
|
||||
await CreateAuditLog("role", "create", "Role", role.Id, role.DisplayName, null, System.Text.Json.JsonSerializer.Serialize(dto));
|
||||
await CreateAuditLog("role", "create", "Role", role.Id, role.DisplayName, null,
|
||||
System.Text.Json.JsonSerializer.Serialize(dto));
|
||||
|
||||
return new RoleDto
|
||||
{
|
||||
@ -173,7 +178,8 @@ public class RoleService : IRoleService
|
||||
|
||||
await _repository.UpdateAsync(role);
|
||||
|
||||
await CreateAuditLog("role", "update", "Role", role.Id, role.DisplayName, oldValue, System.Text.Json.JsonSerializer.Serialize(role));
|
||||
await CreateAuditLog("role", "update", "Role", role.Id, role.DisplayName, oldValue,
|
||||
System.Text.Json.JsonSerializer.Serialize(role));
|
||||
|
||||
var users = await _userManager.GetUsersInRoleAsync(role.Name!);
|
||||
return new RoleDto
|
||||
@ -216,6 +222,29 @@ public class RoleService : IRoleService
|
||||
await CreateAuditLog("role", "delete", "Role", role.Id, role.DisplayName, oldValue);
|
||||
}
|
||||
|
||||
public async Task AddUserToRoleAsync(long roleId, long userId)
|
||||
{
|
||||
var role = await _repository.GetByIdAsync(roleId);
|
||||
if (role == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"Role with ID {roleId} not found");
|
||||
}
|
||||
|
||||
var user = await _userManager.FindByIdAsync(userId.ToString());
|
||||
if (user == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"User with ID {userId} not found");
|
||||
}
|
||||
|
||||
var result = await _userManager.AddToRoleAsync(user, role.Name!);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
throw new InvalidOperationException(string.Join(", ", result.Errors.Select(e => e.Description)));
|
||||
}
|
||||
|
||||
await CreateAuditLog("role", "update", "UserRole", null, $"{role.Name} - {user.UserName}");
|
||||
}
|
||||
|
||||
public async Task RemoveUserFromRoleAsync(long roleId, long userId)
|
||||
{
|
||||
var role = await _repository.GetByIdAsync(roleId);
|
||||
@ -239,10 +268,12 @@ public class RoleService : IRoleService
|
||||
await CreateAuditLog("role", "update", "UserRole", null, $"{role.Name} - {user.UserName}");
|
||||
}
|
||||
|
||||
private async Task CreateAuditLog(string operation, string action, string targetType, long? targetId, string? targetName, string? oldValue = null, string? newValue = null)
|
||||
private async Task CreateAuditLog(string operation, string action, string targetType, long? targetId,
|
||||
string? targetName, string? oldValue = null, string? newValue = null)
|
||||
{
|
||||
var httpContext = _httpContextAccessor.HttpContext;
|
||||
var userName = httpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier) ?? httpContext?.User?.Identity?.Name ?? "system";
|
||||
var userName = httpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier) ??
|
||||
httpContext?.User?.Identity?.Name ?? "system";
|
||||
var tenantId = httpContext?.User?.FindFirstValue("TenantId");
|
||||
|
||||
var log = new AuditLog
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
using Fengling.AuthService.Data;
|
||||
using Fengling.AuthService.Models;
|
||||
using Fengling.Console.Models.Dtos;
|
||||
using Fengling.Console.Repositories;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using System.Security.Claims;
|
||||
using Fengling.Console.Datas;
|
||||
using Fengling.Console.Models.Entities;
|
||||
|
||||
namespace Fengling.Console.Services;
|
||||
|
||||
@ -13,47 +13,31 @@ public interface ITenantService
|
||||
Task<TenantDto?> GetTenantAsync(long id);
|
||||
Task<IEnumerable<UserDto>> GetTenantUsersAsync(long tenantId);
|
||||
Task<IEnumerable<object>> GetTenantRolesAsync(long tenantId);
|
||||
Task<TenantSettingsDto> GetTenantSettingsAsync(string tenantId);
|
||||
Task UpdateTenantSettingsAsync(string tenantId, TenantSettingsDto settings);
|
||||
Task<TenantSettingsDto> GetTenantSettingsAsync(long id);
|
||||
Task UpdateTenantSettingsAsync(long id, TenantSettingsDto settings);
|
||||
Task<TenantDto> CreateTenantAsync(CreateTenantDto dto);
|
||||
Task<TenantDto> UpdateTenantAsync(long id, UpdateTenantDto dto);
|
||||
Task DeleteTenantAsync(long id);
|
||||
}
|
||||
|
||||
public class TenantService : ITenantService
|
||||
public class TenantService(
|
||||
ITenantRepository repository,
|
||||
IUserRepository userRepository,
|
||||
IRoleRepository roleRepository,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
ApplicationDbContext context,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
: ITenantService
|
||||
{
|
||||
private readonly ITenantRepository _repository;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IRoleRepository _roleRepository;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public TenantService(
|
||||
ITenantRepository repository,
|
||||
IUserRepository userRepository,
|
||||
IRoleRepository roleRepository,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
ApplicationDbContext context,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_repository = repository;
|
||||
_userRepository = userRepository;
|
||||
_roleRepository = roleRepository;
|
||||
_userManager = userManager;
|
||||
_context = context;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<TenantDto> Items, int TotalCount)> GetTenantsAsync(int page, int pageSize, string? name = null, string? tenantId = null, string? status = null)
|
||||
{
|
||||
var tenants = await _repository.GetPagedAsync(page, pageSize, name, tenantId, status);
|
||||
var totalCount = await _repository.CountAsync(name, tenantId, status);
|
||||
var tenants = await repository.GetPagedAsync(page, pageSize, name, tenantId, status);
|
||||
var totalCount = await repository.CountAsync(name, tenantId, status);
|
||||
|
||||
var tenantDtos = new List<TenantDto>();
|
||||
foreach (var tenant in tenants)
|
||||
{
|
||||
var userCount = await _repository.GetUserCountAsync(tenant.Id);
|
||||
var userCount = await repository.GetUserCountAsync(tenant.Id);
|
||||
tenantDtos.Add(new TenantDto
|
||||
{
|
||||
Id = tenant.Id,
|
||||
@ -76,7 +60,7 @@ public class TenantService : ITenantService
|
||||
|
||||
public async Task<TenantDto?> GetTenantAsync(long id)
|
||||
{
|
||||
var tenant = await _repository.GetByIdAsync(id);
|
||||
var tenant = await repository.GetByIdAsync(id);
|
||||
if (tenant == null) return null;
|
||||
|
||||
return new TenantDto
|
||||
@ -88,7 +72,7 @@ public class TenantService : ITenantService
|
||||
ContactEmail = tenant.ContactEmail,
|
||||
ContactPhone = tenant.ContactPhone,
|
||||
MaxUsers = tenant.MaxUsers,
|
||||
UserCount = await _repository.GetUserCountAsync(tenant.Id),
|
||||
UserCount = await repository.GetUserCountAsync(tenant.Id),
|
||||
Status = tenant.Status,
|
||||
ExpiresAt = tenant.ExpiresAt,
|
||||
Description = tenant.Description,
|
||||
@ -98,18 +82,18 @@ public class TenantService : ITenantService
|
||||
|
||||
public async Task<IEnumerable<UserDto>> GetTenantUsersAsync(long tenantId)
|
||||
{
|
||||
var tenant = await _repository.GetByIdAsync(tenantId);
|
||||
var tenant = await repository.GetByIdAsync(tenantId);
|
||||
if (tenant == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"Tenant with ID {tenantId} not found");
|
||||
}
|
||||
|
||||
var users = await _userRepository.GetPagedAsync(1, int.MaxValue, null, null, tenantId.ToString());
|
||||
var users = await userRepository.GetPagedAsync(1, int.MaxValue, null, null, tenantId.ToString());
|
||||
var userDtos = new List<UserDto>();
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
var roles = await _userManager.GetRolesAsync(user);
|
||||
var roles = await userManager.GetRolesAsync(user);
|
||||
userDtos.Add(new UserDto
|
||||
{
|
||||
Id = user.Id,
|
||||
@ -130,13 +114,13 @@ public class TenantService : ITenantService
|
||||
|
||||
public async Task<IEnumerable<object>> GetTenantRolesAsync(long tenantId)
|
||||
{
|
||||
var tenant = await _repository.GetByIdAsync(tenantId);
|
||||
var tenant = await repository.GetByIdAsync(tenantId);
|
||||
if (tenant == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"Tenant with ID {tenantId} not found");
|
||||
}
|
||||
|
||||
var roles = await _roleRepository.GetPagedAsync(1, int.MaxValue, null, tenantId.ToString());
|
||||
var roles = await roleRepository.GetPagedAsync(1, int.MaxValue, null, tenantId.ToString());
|
||||
return roles.Select(r => new
|
||||
{
|
||||
id = r.Id,
|
||||
@ -145,12 +129,12 @@ public class TenantService : ITenantService
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<TenantSettingsDto> GetTenantSettingsAsync(string tenantId)
|
||||
public async Task<TenantSettingsDto> GetTenantSettingsAsync(long id)
|
||||
{
|
||||
var tenant = await _repository.GetByTenantIdAsync(tenantId);
|
||||
var tenant = await repository.GetByIdAsync(id);
|
||||
if (tenant == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"Tenant with tenantId '{tenantId}' not found");
|
||||
throw new KeyNotFoundException($"Tenant with ID {id} not found");
|
||||
}
|
||||
|
||||
return new TenantSettingsDto
|
||||
@ -164,12 +148,12 @@ public class TenantService : ITenantService
|
||||
};
|
||||
}
|
||||
|
||||
public async Task UpdateTenantSettingsAsync(string tenantId, TenantSettingsDto settings)
|
||||
public async Task UpdateTenantSettingsAsync(long id, TenantSettingsDto settings)
|
||||
{
|
||||
var tenant = await _repository.GetByTenantIdAsync(tenantId);
|
||||
var tenant = await repository.GetByIdAsync(id);
|
||||
if (tenant == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"Tenant with tenantId '{tenantId}' not found");
|
||||
throw new KeyNotFoundException($"Tenant with ID {id} not found");
|
||||
}
|
||||
|
||||
await CreateAuditLog("tenant", "update", "TenantSettings", tenant.Id, tenant.TenantId, null, System.Text.Json.JsonSerializer.Serialize(settings));
|
||||
@ -191,7 +175,7 @@ public class TenantService : ITenantService
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await _repository.AddAsync(tenant);
|
||||
await repository.AddAsync(tenant);
|
||||
|
||||
await CreateAuditLog("tenant", "create", "Tenant", tenant.Id, tenant.TenantId, null, System.Text.Json.JsonSerializer.Serialize(dto));
|
||||
|
||||
@ -214,7 +198,7 @@ public class TenantService : ITenantService
|
||||
|
||||
public async Task<TenantDto> UpdateTenantAsync(long id, UpdateTenantDto dto)
|
||||
{
|
||||
var tenant = await _repository.GetByIdAsync(id);
|
||||
var tenant = await repository.GetByIdAsync(id);
|
||||
if (tenant == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"Tenant with ID {id} not found");
|
||||
@ -232,7 +216,7 @@ public class TenantService : ITenantService
|
||||
tenant.ExpiresAt = dto.ExpiresAt;
|
||||
tenant.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
await _repository.UpdateAsync(tenant);
|
||||
await repository.UpdateAsync(tenant);
|
||||
|
||||
await CreateAuditLog("tenant", "update", "Tenant", tenant.Id, tenant.TenantId, oldValue, System.Text.Json.JsonSerializer.Serialize(tenant));
|
||||
|
||||
@ -245,7 +229,7 @@ public class TenantService : ITenantService
|
||||
ContactEmail = tenant.ContactEmail,
|
||||
ContactPhone = tenant.ContactPhone,
|
||||
MaxUsers = tenant.MaxUsers,
|
||||
UserCount = await _repository.GetUserCountAsync(tenant.Id),
|
||||
UserCount = await repository.GetUserCountAsync(tenant.Id),
|
||||
Status = tenant.Status,
|
||||
ExpiresAt = tenant.ExpiresAt,
|
||||
Description = tenant.Description,
|
||||
@ -255,7 +239,7 @@ public class TenantService : ITenantService
|
||||
|
||||
public async Task DeleteTenantAsync(long id)
|
||||
{
|
||||
var tenant = await _repository.GetByIdAsync(id);
|
||||
var tenant = await repository.GetByIdAsync(id);
|
||||
if (tenant == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"Tenant with ID {id} not found");
|
||||
@ -263,23 +247,23 @@ public class TenantService : ITenantService
|
||||
|
||||
var oldValue = System.Text.Json.JsonSerializer.Serialize(tenant);
|
||||
|
||||
var users = await _userRepository.GetPagedAsync(1, int.MaxValue, null, null, id.ToString());
|
||||
var users = await userRepository.GetPagedAsync(1, int.MaxValue, null, null, id.ToString());
|
||||
foreach (var user in users)
|
||||
{
|
||||
user.IsDeleted = true;
|
||||
user.UpdatedTime = DateTime.UtcNow;
|
||||
await _context.SaveChangesAsync();
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
tenant.IsDeleted = true;
|
||||
await _repository.UpdateAsync(tenant);
|
||||
await repository.UpdateAsync(tenant);
|
||||
|
||||
await CreateAuditLog("tenant", "delete", "Tenant", tenant.Id, tenant.TenantId, oldValue);
|
||||
}
|
||||
|
||||
private async Task CreateAuditLog(string operation, string action, string targetType, long? targetId, string? targetName, string? oldValue = null, string? newValue = null)
|
||||
{
|
||||
var httpContext = _httpContextAccessor.HttpContext;
|
||||
var httpContext = httpContextAccessor.HttpContext;
|
||||
var userName = httpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier) ?? httpContext?.User?.Identity?.Name ?? "system";
|
||||
var tenantId = httpContext?.User?.FindFirstValue("TenantId");
|
||||
|
||||
@ -299,7 +283,7 @@ public class TenantService : ITenantService
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.AuditLogs.Add(log);
|
||||
await _context.SaveChangesAsync();
|
||||
context.AuditLogs.Add(log);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
using Fengling.AuthService.Data;
|
||||
using Fengling.AuthService.Models;
|
||||
using Fengling.Console.Models.Dtos;
|
||||
using Fengling.Console.Repositories;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using System.Security.Claims;
|
||||
using Fengling.Console.Datas;
|
||||
using Fengling.Console.Models.Entities;
|
||||
|
||||
namespace Fengling.Console.Services;
|
||||
|
||||
@ -17,40 +17,24 @@ public interface IUserService
|
||||
Task DeleteUserAsync(long id);
|
||||
}
|
||||
|
||||
public class UserService : IUserService
|
||||
public class UserService(
|
||||
IUserRepository repository,
|
||||
ITenantRepository tenantRepository,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
RoleManager<ApplicationRole> roleManager,
|
||||
ApplicationDbContext context,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
: IUserService
|
||||
{
|
||||
private readonly IUserRepository _repository;
|
||||
private readonly ITenantRepository _tenantRepository;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly RoleManager<ApplicationRole> _roleManager;
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public UserService(
|
||||
IUserRepository repository,
|
||||
ITenantRepository tenantRepository,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
RoleManager<ApplicationRole> roleManager,
|
||||
ApplicationDbContext context,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_repository = repository;
|
||||
_tenantRepository = tenantRepository;
|
||||
_userManager = userManager;
|
||||
_roleManager = roleManager;
|
||||
_context = context;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<UserDto> Items, int TotalCount)> GetUsersAsync(int page, int pageSize, string? userName = null, string? email = null, string? tenantId = null)
|
||||
{
|
||||
var users = await _repository.GetPagedAsync(page, pageSize, userName, email, tenantId);
|
||||
var totalCount = await _repository.CountAsync(userName, email, tenantId);
|
||||
var users = await repository.GetPagedAsync(page, pageSize, userName, email, tenantId);
|
||||
var totalCount = await repository.CountAsync(userName, email, tenantId);
|
||||
|
||||
var userDtos = new List<UserDto>();
|
||||
foreach (var user in users)
|
||||
{
|
||||
var roles = await _userManager.GetRolesAsync(user);
|
||||
var roles = await userManager.GetRolesAsync(user);
|
||||
userDtos.Add(new UserDto
|
||||
{
|
||||
Id = user.Id,
|
||||
@ -72,10 +56,10 @@ public class UserService : IUserService
|
||||
|
||||
public async Task<UserDto?> GetUserAsync(long id)
|
||||
{
|
||||
var user = await _repository.GetByIdAsync(id);
|
||||
var user = await repository.GetByIdAsync(id);
|
||||
if (user == null) return null;
|
||||
|
||||
var roles = await _userManager.GetRolesAsync(user);
|
||||
var roles = await userManager.GetRolesAsync(user);
|
||||
return new UserDto
|
||||
{
|
||||
Id = user.Id,
|
||||
@ -99,7 +83,7 @@ public class UserService : IUserService
|
||||
|
||||
if (tenantId != 0)
|
||||
{
|
||||
tenant = await _tenantRepository.GetByIdAsync(tenantId);
|
||||
tenant = await tenantRepository.GetByIdAsync(tenantId);
|
||||
if (tenant == null)
|
||||
{
|
||||
throw new InvalidOperationException("Invalid tenant ID");
|
||||
@ -117,7 +101,7 @@ public class UserService : IUserService
|
||||
CreatedTime = DateTime.UtcNow
|
||||
};
|
||||
|
||||
var result = await _userManager.CreateAsync(user, dto.Password);
|
||||
var result = await userManager.CreateAsync(user, dto.Password);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
throw new InvalidOperationException(string.Join(", ", result.Errors.Select(e => e.Description)));
|
||||
@ -127,23 +111,23 @@ public class UserService : IUserService
|
||||
{
|
||||
foreach (var roleId in dto.RoleIds)
|
||||
{
|
||||
var role = await _roleManager.FindByIdAsync(roleId.ToString());
|
||||
var role = await roleManager.FindByIdAsync(roleId.ToString());
|
||||
if (role != null)
|
||||
{
|
||||
await _userManager.AddToRoleAsync(user, role.Name!);
|
||||
await userManager.AddToRoleAsync(user, role.Name!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!dto.IsActive)
|
||||
{
|
||||
await _userManager.SetLockoutEnabledAsync(user, true);
|
||||
await _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.MaxValue);
|
||||
await userManager.SetLockoutEnabledAsync(user, true);
|
||||
await userManager.SetLockoutEndDateAsync(user, DateTimeOffset.MaxValue);
|
||||
}
|
||||
|
||||
await CreateAuditLog("user", "create", "User", user.Id, user.UserName, null, System.Text.Json.JsonSerializer.Serialize(dto));
|
||||
|
||||
var roles = await _userManager.GetRolesAsync(user);
|
||||
var roles = await userManager.GetRolesAsync(user);
|
||||
return new UserDto
|
||||
{
|
||||
Id = user.Id,
|
||||
@ -162,7 +146,7 @@ public class UserService : IUserService
|
||||
|
||||
public async Task<UserDto> UpdateUserAsync(long id, UpdateUserDto dto)
|
||||
{
|
||||
var user = await _repository.GetByIdAsync(id);
|
||||
var user = await repository.GetByIdAsync(id);
|
||||
if (user == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"User with ID {id} not found");
|
||||
@ -178,20 +162,20 @@ public class UserService : IUserService
|
||||
|
||||
if (dto.IsActive)
|
||||
{
|
||||
await _userManager.SetLockoutEnabledAsync(user, false);
|
||||
await _userManager.SetLockoutEndDateAsync(user, null);
|
||||
await userManager.SetLockoutEnabledAsync(user, false);
|
||||
await userManager.SetLockoutEndDateAsync(user, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _userManager.SetLockoutEnabledAsync(user, true);
|
||||
await _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.MaxValue);
|
||||
await userManager.SetLockoutEnabledAsync(user, true);
|
||||
await userManager.SetLockoutEndDateAsync(user, DateTimeOffset.MaxValue);
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
await CreateAuditLog("user", "update", "User", user.Id, user.UserName, oldValue, System.Text.Json.JsonSerializer.Serialize(user));
|
||||
|
||||
var roles = await _userManager.GetRolesAsync(user);
|
||||
var roles = await userManager.GetRolesAsync(user);
|
||||
return new UserDto
|
||||
{
|
||||
Id = user.Id,
|
||||
@ -210,14 +194,14 @@ public class UserService : IUserService
|
||||
|
||||
public async Task ResetPasswordAsync(long id, ResetPasswordDto dto)
|
||||
{
|
||||
var user = await _userManager.FindByIdAsync(id.ToString());
|
||||
var user = await userManager.FindByIdAsync(id.ToString());
|
||||
if (user == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"User with ID {id} not found");
|
||||
}
|
||||
|
||||
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
|
||||
var result = await _userManager.ResetPasswordAsync(user, token, dto.NewPassword);
|
||||
var token = await userManager.GeneratePasswordResetTokenAsync(user);
|
||||
var result = await userManager.ResetPasswordAsync(user, token, dto.NewPassword);
|
||||
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
@ -229,7 +213,7 @@ public class UserService : IUserService
|
||||
|
||||
public async Task DeleteUserAsync(long id)
|
||||
{
|
||||
var user = await _repository.GetByIdAsync(id);
|
||||
var user = await repository.GetByIdAsync(id);
|
||||
if (user == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"User with ID {id} not found");
|
||||
@ -238,14 +222,14 @@ public class UserService : IUserService
|
||||
var oldValue = System.Text.Json.JsonSerializer.Serialize(user);
|
||||
user.IsDeleted = true;
|
||||
user.UpdatedTime = DateTime.UtcNow;
|
||||
await _context.SaveChangesAsync();
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
await CreateAuditLog("user", "delete", "User", user.Id, user.UserName, oldValue);
|
||||
}
|
||||
|
||||
private async Task CreateAuditLog(string operation, string action, string targetType, long? targetId, string? targetName, string? oldValue = null, string? newValue = null)
|
||||
{
|
||||
var httpContext = _httpContextAccessor.HttpContext;
|
||||
var httpContext = httpContextAccessor.HttpContext;
|
||||
var userName = httpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier) ?? httpContext?.User?.Identity?.Name ?? "system";
|
||||
var tenantId = httpContext?.User?.FindFirstValue("TenantId");
|
||||
|
||||
@ -265,7 +249,7 @@ public class UserService : IUserService
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.AuditLogs.Add(log);
|
||||
await _context.SaveChangesAsync();
|
||||
context.AuditLogs.Add(log);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
"Microsoft.AspNetCore": "Debug"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,5 +5,9 @@
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=192.168.100.10;Port=5432;Database=fengling_auth;Username=movingsam;Password=sl52788542",
|
||||
"GatewayConnection" : "Host=192.168.100.10;Port=5432;Database=fengling_gateway;Username=movingsam;Password=sl52788542"
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user