diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantsController.cs new file mode 100644 index 0000000..ef06c54 --- /dev/null +++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantsController.cs @@ -0,0 +1,42 @@ +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using TakeoutSaaS.Application.App.Tenants.Contracts; +using TakeoutSaaS.Application.App.Tenants.Queries; +using TakeoutSaaS.Module.Authorization.Attributes; +using TakeoutSaaS.Shared.Abstractions.Results; +using TakeoutSaaS.Shared.Web.Api; + +namespace TakeoutSaaS.AdminApi.Controllers; + +/// +/// 租户管理。 +/// +[ApiVersion("1.0")] +[Authorize] +[Route("api/admin/v{version:apiVersion}/tenants")] +public sealed class TenantsController(IMediator mediator) : BaseApiController +{ + /// + /// 获取租户列表(用于下拉选择器)。 + /// + /// 关键字(租户名称/编码)。 + /// 取消标记。 + /// 租户列表。 + [HttpGet] + [PermissionAuthorize("tenant:read")] + [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] + public async Task>> List( + [FromQuery] string? keyword, + CancellationToken cancellationToken) + { + // 1. 构造查询 + var query = new ListTenantsQuery { Keyword = keyword }; + + // 2. 执行查询 + var result = await mediator.Send(query, cancellationToken); + + // 3. 返回租户列表 + return ApiResponse>.Ok(result); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Contracts/TenantListItemDto.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Contracts/TenantListItemDto.cs new file mode 100644 index 0000000..7500979 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Contracts/TenantListItemDto.cs @@ -0,0 +1,37 @@ +using System.Text.Json.Serialization; +using TakeoutSaaS.Domain.Tenants.Enums; +using TakeoutSaaS.Shared.Abstractions.Serialization; + +namespace TakeoutSaaS.Application.App.Tenants.Contracts; + +/// +/// 租户列表项 DTO(用于下拉选择器)。 +/// +public sealed class TenantListItemDto +{ + /// + /// 租户 ID(雪花,序列化为字符串)。 + /// + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long Id { get; init; } + + /// + /// 租户编码。 + /// + public string Code { get; init; } = string.Empty; + + /// + /// 租户名称。 + /// + public string Name { get; init; } = string.Empty; + + /// + /// 租户简称。 + /// + public string? ShortName { get; init; } + + /// + /// 租户状态。 + /// + public TenantStatus Status { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ListTenantsQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ListTenantsQueryHandler.cs new file mode 100644 index 0000000..c511ae2 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ListTenantsQueryHandler.cs @@ -0,0 +1,30 @@ +using MediatR; +using TakeoutSaaS.Application.App.Tenants.Contracts; +using TakeoutSaaS.Application.App.Tenants.Queries; +using TakeoutSaaS.Domain.Tenants.Repositories; + +namespace TakeoutSaaS.Application.App.Tenants.Handlers; + +/// +/// 获取租户列表查询处理器。 +/// +public sealed class ListTenantsQueryHandler(ITenantRepository tenantRepository) + : IRequestHandler> +{ + /// + public async Task> Handle(ListTenantsQuery request, CancellationToken cancellationToken) + { + // 1. 查询租户列表 + var tenants = await tenantRepository.GetAllAsync(request.Keyword, cancellationToken); + + // 2. 映射 DTO + return tenants.Select(t => new TenantListItemDto + { + Id = t.Id, + Code = t.Code, + Name = t.Name, + ShortName = t.ShortName, + Status = t.Status + }).ToArray(); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Queries/ListTenantsQuery.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Queries/ListTenantsQuery.cs new file mode 100644 index 0000000..7ed6481 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Queries/ListTenantsQuery.cs @@ -0,0 +1,15 @@ +using MediatR; +using TakeoutSaaS.Application.App.Tenants.Contracts; + +namespace TakeoutSaaS.Application.App.Tenants.Queries; + +/// +/// 获取租户列表查询(用于下拉选择器)。 +/// +public sealed record ListTenantsQuery : IRequest> +{ + /// + /// 关键字(租户名称/编码)。 + /// + public string? Keyword { get; init; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Tenants/Repositories/ITenantRepository.cs b/src/Domain/TakeoutSaaS.Domain/Tenants/Repositories/ITenantRepository.cs index 504da3a..e766c55 100644 --- a/src/Domain/TakeoutSaaS.Domain/Tenants/Repositories/ITenantRepository.cs +++ b/src/Domain/TakeoutSaaS.Domain/Tenants/Repositories/ITenantRepository.cs @@ -22,4 +22,12 @@ public interface ITenantRepository /// 取消标记。 /// 租户列表(仅返回找到的租户)。 Task> FindByIdsAsync(IReadOnlyCollection tenantIds, CancellationToken cancellationToken = default); + + /// + /// 获取所有租户列表(用于下拉选择器)。 + /// + /// 关键字(租户名称/编码)。 + /// 取消标记。 + /// 租户列表。 + Task> GetAllAsync(string? keyword, CancellationToken cancellationToken = default); } diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfTenantRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfTenantRepository.cs index 2c4f5a3..ae4a260 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfTenantRepository.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfTenantRepository.cs @@ -34,4 +34,23 @@ public sealed class EfTenantRepository(TakeoutAdminDbContext context) : ITenantR .Where(x => tenantIds.Contains(x.Id)) .ToListAsync(cancellationToken); } + + /// + public async Task> GetAllAsync(string? keyword, CancellationToken cancellationToken = default) + { + // 1. 构建基础查询 + var query = context.Tenants + .AsNoTracking() + .Where(x => x.DeletedAt == null); + + // 2. 应用关键字过滤 + if (!string.IsNullOrWhiteSpace(keyword)) + { + var normalized = keyword.Trim(); + query = query.Where(x => x.Name.Contains(normalized) || x.Code.Contains(normalized)); + } + + // 3. 返回列表 + return await query.OrderBy(x => x.Code).ToListAsync(cancellationToken); + } }