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); } /// /// 后台手动新增租户并直接入驻(创建租户 + 认证 + 订阅 + 管理员账号)。 /// /// 新增后的租户详情。 [HttpPost("manual")] [PermissionAuthorize("tenant:create")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> CreateManually([FromBody] CreateTenantManuallyCommand 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); } /// /// 查询当前租户审核领取信息。 /// /// 领取信息,未领取返回 null。 [HttpGet("{tenantId:long}/review/claim")] [PermissionAuthorize("tenant:review")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> GetReviewClaim(long tenantId, CancellationToken cancellationToken) { // 1. 查询领取信息 var result = await mediator.Send(new GetTenantReviewClaimQuery(tenantId), cancellationToken); // 2. 返回领取信息 return ApiResponse.Ok(result); } /// /// 领取租户入驻审核(领取后仅领取人可操作审核)。 /// /// 领取结果。 [HttpPost("{tenantId:long}/review/claim")] [PermissionAuthorize("tenant:review")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> ClaimReview(long tenantId, CancellationToken cancellationToken) { // 1. 执行领取 var result = await mediator.Send(new ClaimTenantReviewCommand { TenantId = tenantId }, cancellationToken); // 2. 返回领取结果 return ApiResponse.Ok(result); } /// /// 强制接管租户入驻审核(仅超级管理员可用)。 /// /// 接管后的领取信息。 [HttpPost("{tenantId:long}/review/force-claim")] [PermissionAuthorize("tenant:review:force-claim")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> ForceClaimReview(long tenantId, CancellationToken cancellationToken) { // 1. 执行强制接管 var result = await mediator.Send(new ForceClaimTenantReviewCommand { TenantId = tenantId }, cancellationToken); // 2. 返回接管结果 return ApiResponse.Ok(result); } /// /// 释放租户入驻审核领取(仅领取人可释放)。 /// /// 释放后的领取信息,未领取返回 null。 [HttpPost("{tenantId:long}/review/release")] [PermissionAuthorize("tenant:review")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> ReleaseReview(long tenantId, CancellationToken cancellationToken) { // 1. 执行释放 var result = await mediator.Send(new ReleaseTenantReviewClaimCommand { TenantId = tenantId }, cancellationToken); // 2. 返回释放结果 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); } }