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}/tenants")] public sealed class TenantsController(IMediator mediator) : BaseApiController { /// /// 注册租户并初始化套餐。 /// /// 注册的租户信息。 [HttpPost] [PermissionAuthorize("tenant:create")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> Register([FromBody] RegisterTenantCommand command, CancellationToken cancellationToken) { // 1. 注册租户并初始化套餐 var result = await mediator.Send(command, cancellationToken); // 2. 返回注册结果 return ApiResponse.Ok(result); } /// /// 分页查询租户。 /// /// 租户分页结果。 [HttpGet] [PermissionAuthorize("tenant:read")] [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] public async Task>> Search([FromQuery] SearchTenantsQuery query, CancellationToken cancellationToken) { // 1. 查询租户分页 var result = await mediator.Send(query, cancellationToken); // 2. 返回分页数据 return ApiResponse>.Ok(result); } /// /// 查看租户详情。 /// /// 租户详情。 [HttpGet("{tenantId:long}")] [PermissionAuthorize("tenant:read")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> Detail(long tenantId, CancellationToken cancellationToken) { // 1. 查询租户详情 var result = await mediator.Send(new GetTenantByIdQuery(tenantId), cancellationToken); // 2. 返回租户信息 return ApiResponse.Ok(result); } /// /// 提交或更新实名认证资料。 /// /// 提交的实名认证信息。 [HttpPost("{tenantId:long}/verification")] [PermissionAuthorize("tenant:review")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> SubmitVerification( long tenantId, [FromBody] SubmitTenantVerificationCommand body, CancellationToken cancellationToken) { // 1. 合并路由中的租户标识 var command = body with { TenantId = tenantId }; // 2. 提交或更新认证资料 var result = await mediator.Send(command, cancellationToken); // 3. 返回认证结果 return ApiResponse.Ok(result); } /// /// 审核租户。 /// /// 审核后的租户信息。 [HttpPost("{tenantId:long}/review")] [PermissionAuthorize("tenant:review")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> Review(long tenantId, [FromBody] ReviewTenantCommand body, CancellationToken cancellationToken) { // 1. 绑定租户标识 var command = body with { TenantId = tenantId }; // 2. 执行审核 var result = await mediator.Send(command, cancellationToken); // 3. 返回审核结果 return ApiResponse.Ok(result); } /// /// 创建或续费租户订阅。 /// /// 创建或续费的订阅信息。 [HttpPost("{tenantId:long}/subscriptions")] [PermissionAuthorize("tenant:subscription")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> CreateSubscription( long tenantId, [FromBody] CreateTenantSubscriptionCommand body, CancellationToken cancellationToken) { // 1. 绑定租户并创建或续费订阅 var command = body with { TenantId = tenantId }; // 2. 返回订阅结果 var result = await mediator.Send(command, cancellationToken); return ApiResponse.Ok(result); } /// /// 套餐升降配。 /// /// 更新后的订阅信息。 [HttpPut("{tenantId:long}/subscriptions/{subscriptionId:long}/plan")] [PermissionAuthorize("tenant:subscription")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> ChangePlan( long tenantId, long subscriptionId, [FromBody] ChangeTenantSubscriptionPlanCommand body, CancellationToken cancellationToken) { // 1. 绑定租户与订阅标识 var command = body with { TenantId = tenantId, TenantSubscriptionId = subscriptionId }; // 2. 执行升降配 var result = await mediator.Send(command, cancellationToken); // 3. 返回调整后的订阅 return ApiResponse.Ok(result); } /// /// 查询审核日志。 /// /// 租户审核日志分页结果。 [HttpGet("{tenantId:long}/audits")] [PermissionAuthorize("tenant:read")] [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] public async Task>> AuditLogs( long tenantId, [FromQuery] int page = 1, [FromQuery] int pageSize = 20, CancellationToken cancellationToken = default) { // 1. 构造审核日志查询 var query = new GetTenantAuditLogsQuery(tenantId, page, pageSize); // 2. 查询并返回分页结果 var result = await mediator.Send(query, cancellationToken); return ApiResponse>.Ok(result); } /// /// 配额校验并占用额度(门店/账号/短信/配送)。 /// /// 需在请求头携带 X-Tenant-Id 对应的租户。 /// 配额校验结果。 [HttpPost("{tenantId:long}/quotas/check")] [PermissionAuthorize("tenant:quota:check")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> CheckQuota( long tenantId, [FromBody, Required] CheckTenantQuotaCommand body, CancellationToken cancellationToken) { // 1. 绑定租户标识 var command = body with { TenantId = tenantId }; // 2. 校验并占用配额 var result = await mediator.Send(command, cancellationToken); return ApiResponse.Ok(result); } }