using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.ComponentModel.DataAnnotations; using TakeoutSaaS.Application.App.Tenants.Commands; using TakeoutSaaS.Application.App.Tenants.Dto; 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}/tenant-packages")] public sealed class TenantPackagesController(IMediator mediator) : BaseApiController { /// /// 分页查询租户套餐。 /// /// 查询条件。 /// 取消标记。 /// 租户套餐分页结果。 [HttpGet] [PermissionAuthorize("tenant-package:read")] [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] public async Task>> Search([FromQuery] SearchTenantPackagesQuery query, CancellationToken cancellationToken) { // 1. 查询租户套餐分页 var result = await mediator.Send(query, cancellationToken); // 2. 返回结果 return ApiResponse>.Ok(result); } /// /// 查询套餐使用统计(订阅关联数量、使用租户数量)。 /// /// 套餐 ID 列表(为空表示查询全部)。 /// 取消标记。 /// 套餐使用统计列表。 [HttpGet("usages")] [PermissionAuthorize("tenant-package:read")] [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] public async Task>> Usages( [FromQuery] long[]? tenantPackageIds, CancellationToken cancellationToken) { // 1. 查询使用统计 var result = await mediator.Send(new GetTenantPackageUsagesQuery { TenantPackageIds = tenantPackageIds }, cancellationToken); // 2. 返回结果 return ApiResponse>.Ok(result); } /// /// 查询套餐当前使用租户列表(按有效订阅口径)。 /// /// 套餐 ID。 /// 关键词(可选)。 /// 可选:未来 N 天内到期筛选。 /// 页码(从 1 开始)。 /// 每页大小。 /// 取消标记。 /// 使用租户分页结果。 [HttpGet("{tenantPackageId:long}/tenants")] [PermissionAuthorize("tenant-package:read")] [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] public async Task>> Tenants( long tenantPackageId, [FromQuery] string? keyword, [FromQuery] int? expiringWithinDays, [FromQuery] int page = 1, [FromQuery] int pageSize = 20, CancellationToken cancellationToken = default) { // 1. 查询套餐使用租户分页 var result = await mediator.Send(new GetTenantPackageTenantsQuery { TenantPackageId = tenantPackageId, Keyword = keyword, ExpiringWithinDays = expiringWithinDays, Page = page, PageSize = pageSize }, cancellationToken); // 2. 返回结果 return ApiResponse>.Ok(result); } /// /// 查看套餐详情。 /// /// 套餐 ID。 /// 取消标记。 /// 套餐详情或未找到。 [HttpGet("{tenantPackageId:long}")] [PermissionAuthorize("tenant-package:read")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] public async Task> Detail(long tenantPackageId, CancellationToken cancellationToken) { // 1. 查询套餐详情 var result = await mediator.Send(new GetTenantPackageByIdQuery { TenantPackageId = tenantPackageId }, cancellationToken); // 2. 返回查询结果或 404 return result is null ? ApiResponse.Error(StatusCodes.Status404NotFound, "套餐不存在") : ApiResponse.Ok(result); } /// /// 创建套餐。 /// /// 创建命令。 /// 取消标记。 /// 创建后的套餐。 [HttpPost] [PermissionAuthorize("tenant-package:create")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> Create([FromBody, Required] CreateTenantPackageCommand command, CancellationToken cancellationToken) { // 1. 执行创建 var result = await mediator.Send(command, cancellationToken); // 2. 返回创建结果 return ApiResponse.Ok(result); } /// /// 更新套餐。 /// /// 套餐 ID。 /// 更新命令。 /// 取消标记。 /// 更新后的套餐或未找到。 [HttpPut("{tenantPackageId:long}")] [PermissionAuthorize("tenant-package:update")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] public async Task> Update(long tenantPackageId, [FromBody, Required] UpdateTenantPackageCommand command, CancellationToken cancellationToken) { // 1. 绑定路由 ID command = command with { TenantPackageId = tenantPackageId }; // 2. 执行更新 var result = await mediator.Send(command, cancellationToken); // 3. 返回更新结果或 404 return result is null ? ApiResponse.Error(StatusCodes.Status404NotFound, "套餐不存在") : ApiResponse.Ok(result); } /// /// 删除套餐。 /// /// 套餐 ID。 /// 取消标记。 /// 删除结果。 [HttpDelete("{tenantPackageId:long}")] [PermissionAuthorize("tenant-package:delete")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> Delete(long tenantPackageId, CancellationToken cancellationToken) { // 1. 构建删除命令 var command = new DeleteTenantPackageCommand { TenantPackageId = tenantPackageId }; // 2. 执行删除并返回 var result = await mediator.Send(command, cancellationToken); return ApiResponse.Ok(result); } }