using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.ComponentModel.DataAnnotations; using TakeoutSaaS.Application.App.Subscriptions.Commands; using TakeoutSaaS.Application.App.Subscriptions.Dto; using TakeoutSaaS.Application.App.Subscriptions.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}/subscriptions")] public sealed class SubscriptionsController(IMediator mediator) : BaseApiController { /// /// 分页查询订阅列表(支持按状态、套餐、到期时间筛选)。 /// /// 查询条件。 /// 取消标记。 /// 订阅分页结果。 [HttpGet] [PermissionAuthorize("subscription:read")] [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] public async Task>> List( [FromQuery] GetSubscriptionListQuery query, CancellationToken cancellationToken) { // 1. 查询订阅分页 var result = await mediator.Send(query, cancellationToken); // 2. 返回结果 return ApiResponse>.Ok(result); } /// /// 查看订阅详情(含套餐信息、配额使用、变更历史)。 /// /// 订阅 ID。 /// 取消标记。 /// 订阅详情或未找到。 [HttpGet("{subscriptionId:long}")] [PermissionAuthorize("subscription:read")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] public async Task> Detail( long subscriptionId, CancellationToken cancellationToken) { // 1. 查询订阅详情 var result = await mediator.Send(new GetSubscriptionDetailQuery { SubscriptionId = subscriptionId }, cancellationToken); // 2. 返回查询结果或 404 return result is null ? ApiResponse.Error(StatusCodes.Status404NotFound, "订阅不存在") : ApiResponse.Ok(result); } /// /// 更新订阅基础信息(备注、自动续费等)。 /// /// 订阅 ID。 /// 更新命令。 /// 取消标记。 /// 更新后的订阅详情或未找到。 [HttpPut("{subscriptionId:long}")] [PermissionAuthorize("subscription:update")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] public async Task> Update( long subscriptionId, [FromBody, Required] UpdateSubscriptionCommand command, CancellationToken cancellationToken) { // 1. 绑定路由 ID command = command with { SubscriptionId = subscriptionId }; // 2. 执行更新 var result = await mediator.Send(command, cancellationToken); // 3. 返回更新结果或 404 return result is null ? ApiResponse.Error(StatusCodes.Status404NotFound, "订阅不存在") : ApiResponse.Ok(result); } /// /// 延期订阅(增加订阅时长)。 /// /// 订阅 ID。 /// 延期命令。 /// 取消标记。 /// 延期后的订阅详情或未找到。 [HttpPost("{subscriptionId:long}/extend")] [PermissionAuthorize("subscription:extend")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] public async Task> Extend( long subscriptionId, [FromBody, Required] ExtendSubscriptionCommand command, CancellationToken cancellationToken) { // 1. 绑定路由 ID command = command with { SubscriptionId = subscriptionId }; // 2. 执行延期 var result = await mediator.Send(command, cancellationToken); // 3. 返回延期结果或 404 return result is null ? ApiResponse.Error(StatusCodes.Status404NotFound, "订阅不存在") : ApiResponse.Ok(result); } /// /// 变更套餐(支持立即生效或下周期生效)。 /// /// 订阅 ID。 /// 变更套餐命令。 /// 取消标记。 /// 变更后的订阅详情或未找到。 [HttpPost("{subscriptionId:long}/change-plan")] [PermissionAuthorize("subscription:change-plan")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] public async Task> ChangePlan( long subscriptionId, [FromBody, Required] ChangeSubscriptionPlanCommand command, CancellationToken cancellationToken) { // 1. 绑定路由 ID command = command with { SubscriptionId = subscriptionId }; // 2. 执行套餐变更 var result = await mediator.Send(command, cancellationToken); // 3. 返回变更结果或 404 return result is null ? ApiResponse.Error(StatusCodes.Status404NotFound, "订阅不存在") : ApiResponse.Ok(result); } /// /// 变更订阅状态。 /// /// 订阅 ID。 /// 状态变更命令。 /// 取消标记。 /// 变更后的订阅详情或未找到。 [HttpPost("{subscriptionId:long}/status")] [PermissionAuthorize("subscription:update-status")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] public async Task> UpdateStatus( long subscriptionId, [FromBody, Required] UpdateSubscriptionStatusCommand command, CancellationToken cancellationToken) { // 1. 绑定路由 ID command = command with { SubscriptionId = subscriptionId }; // 2. 执行状态变更 var result = await mediator.Send(command, cancellationToken); // 3. 返回变更结果或 404 return result is null ? ApiResponse.Error(StatusCodes.Status404NotFound, "订阅不存在") : ApiResponse.Ok(result); } /// /// 批量延期订阅。 /// /// 批量延期命令。 /// 取消标记。 /// 批量延期结果。 [HttpPost("batch-extend")] [PermissionAuthorize("subscription:batch-extend")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> BatchExtend( [FromBody, Required] BatchExtendSubscriptionsCommand command, CancellationToken cancellationToken) { var result = await mediator.Send(command, cancellationToken); return ApiResponse.Ok(result); } /// /// 批量发送续费提醒。 /// /// 批量发送提醒命令。 /// 取消标记。 /// 批量发送提醒结果。 [HttpPost("batch-remind")] [PermissionAuthorize("subscription:batch-remind")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> BatchRemind( [FromBody, Required] BatchSendReminderCommand command, CancellationToken cancellationToken) { var result = await mediator.Send(command, cancellationToken); return ApiResponse.Ok(result); } }